From 62f16c6f10e7fc549fe61af46e0c239fb7601013 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 15 Dec 2017 23:53:05 +0000 Subject: [PATCH 001/393] Weight recordings more complete Now the rig and user are recorded when a weight is submitted --- +eui/AlyxPanel.m | 5 ++++- +eui/MControl.m | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index f64206d7..b7d48efe 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -368,7 +368,7 @@ function changeWaterText(obj, src, ~) end end - function recordWeight(obj, weight, subject) + function recordWeight(obj, weight, subject, weighingScale) % Post a subject's weight to Alyx. If no inputs are provided, % create an input dialog for the user to input a weight. If no % subject is provided, use this object's currently selected @@ -376,6 +376,7 @@ function recordWeight(obj, weight, subject) % % See also VIEWSUBJECTHISTORY, VIEWALLSUBJECTS ai = obj.AlyxInstance; + if nargin < 4; weighingScale = hostname; end if nargin < 3; subject = obj.Subject; end if nargin < 2 prompt = {sprintf('weight of %s:', subject)}; @@ -391,6 +392,8 @@ function recordWeight(obj, weight, subject) weight = iff(ischar(weight{1}), str2double(weight{1}), weight{1}); d.subject = subject; d.weight = weight; + d.user = ai.user; + d.weighing_scale = weighingScale; if isempty(ai) % if not logged in, save the weight for later obj.QueuedWeights{end+1} = d; obj.log('Warning: Weight not posted to Alyx; will be posted upon login.'); diff --git a/+eui/MControl.m b/+eui/MControl.m index 45983bba..9571d559 100644 --- a/+eui/MControl.m +++ b/+eui/MControl.m @@ -525,7 +525,8 @@ function beginExp(obj) % (i.e. no Alyx token is set), the user is prompted to log in and % the token is stored in the rig object so that EXPPANEL can % later post any events to Alyx (for example the amount of water - % received during the task). + % received during the task). An Alyx Experiment and, if required, Base + % session are also created here. % % See also SRV.STIMULUSCONTROL, EUI.EXPPANEL, EUI.ALYXPANEL set([obj.BeginExpButton obj.RigOptionsButton], 'Enable', 'off'); % Grey out buttons From db2716f09e27eaf4f482c816aff7445b3b559e2b Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Mon, 18 Dec 2017 16:53:09 +0000 Subject: [PATCH 002/393] Removed weigh scale field * weighing_scale field in weighings is not supposed to be a string, removed for the time being. --- +eui/AlyxPanel.m | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index b7d48efe..e16983fc 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -368,7 +368,7 @@ function changeWaterText(obj, src, ~) end end - function recordWeight(obj, weight, subject, weighingScale) + function recordWeight(obj, weight, subject) % Post a subject's weight to Alyx. If no inputs are provided, % create an input dialog for the user to input a weight. If no % subject is provided, use this object's currently selected @@ -376,7 +376,6 @@ function recordWeight(obj, weight, subject, weighingScale) % % See also VIEWSUBJECTHISTORY, VIEWALLSUBJECTS ai = obj.AlyxInstance; - if nargin < 4; weighingScale = hostname; end if nargin < 3; subject = obj.Subject; end if nargin < 2 prompt = {sprintf('weight of %s:', subject)}; @@ -392,8 +391,7 @@ function recordWeight(obj, weight, subject, weighingScale) weight = iff(ischar(weight{1}), str2double(weight{1}), weight{1}); d.subject = subject; d.weight = weight; - d.user = ai.user; - d.weighing_scale = weighingScale; + d.user = ai.username; if isempty(ai) % if not logged in, save the weight for later obj.QueuedWeights{end+1} = d; obj.log('Warning: Weight not posted to Alyx; will be posted upon login.'); From 08faf099973fcde10b54d7c949eb9b7ebe8555f1 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Mon, 18 Dec 2017 17:09:41 +0000 Subject: [PATCH 003/393] Adjuested water resmaining text WaterRemaining now a property of the class rather than a field within the AlyxInstance --- +eui/AlyxPanel.m | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index e16983fc..5e23a39e 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -49,6 +49,7 @@ WaterRequiredText % Handle to text UI element displaying the water required WaterRemainingText % Handle to text UI element displaying the water remaining LoginTimer % Timer to keep track of how long the user has been logged in, when this expires the user is automatically logged out + WaterRemaining % Holds the current water required for the selected subject end events (NotifyAccess = 'protected') @@ -342,6 +343,7 @@ function dispWaterReq(obj, src, ~) set(obj.WaterRequiredText, 'String', ... sprintf('Subject %s requires %.2f of %.2f today', ... obj.Subject, s.water_requirement_remaining, s.water_requirement_total)); + obj.WaterRemaining = s.water_requirement_remaining; end catch me d = loadjson(me.message); @@ -361,8 +363,8 @@ function changeWaterText(obj, src, ~) % % See also DISPWATERREQ, GIVEWATER ai = obj.AlyxInstance; - if ~isempty(ai) && isfield(ai, 'water_requirement_remaining') && ~isempty(ai.water_requirement_remaining) - rem = ai.water_requirement_remaining; + if ~isempty(ai) && ~isempty(obj.WaterRemaining) + rem = obj.WaterRemaining; curr = str2double(src.String); set(obj.WaterRemainingText, 'String', sprintf('(%.2f)', rem-curr)); end From b3f47efd72937ece2caf62b515cc0203a81e8899 Mon Sep 17 00:00:00 2001 From: nsteinme <nick.steinmetz@gmail.com> Date: Mon, 18 Dec 2017 19:35:02 +0000 Subject: [PATCH 004/393] add some daq controllers, update daq controls to allow vector commands --- +exp/SignalsExp.m | 2 +- +hw/DaqController.m | 2 +- +hw/DaqLever.m | 129 ++++++++++++++++++++++++++++++++++++++ +hw/DaqLick.m | 131 +++++++++++++++++++++++++++++++++++++++ +hw/DaqPiezo.m | 129 ++++++++++++++++++++++++++++++++++++++ +hw/PulseSwitcher.m | 2 +- +hw/SinePulseGenerator.m | 45 ++++++++++++++ 7 files changed, 437 insertions(+), 3 deletions(-) create mode 100644 +hw/DaqLever.m create mode 100644 +hw/DaqLick.m create mode 100644 +hw/DaqPiezo.m create mode 100644 +hw/SinePulseGenerator.m diff --git a/+exp/SignalsExp.m b/+exp/SignalsExp.m index 5ddb002b..81438129 100644 --- a/+exp/SignalsExp.m +++ b/+exp/SignalsExp.m @@ -215,7 +215,7 @@ function useRig(obj, rig) obj.DaqController.ChannelNames)); % Find matching channel from rig hardware file if id % if the output is present, create callback obj.Listeners = [obj.Listeners - obj.Outputs.(outputNames{m}).onValue(@(v)obj.DaqController.command([zeros(1,id-1) v])) % pad value with zeros in order to output to correct channel + obj.Outputs.(outputNames{m}).onValue(@(v)obj.DaqController.command([zeros(size(v,1),id-1) v])) % pad value with zeros in order to output to correct channel obj.Outputs.(outputNames{m}).onValue(@(v)fprintf('delivering output of %.2f\n',v)) ]; elseif strcmp(outputNames{m}, 'reward') % special case; rewardValve is always first signals generator in list diff --git a/+hw/DaqController.m b/+hw/DaqController.m index 3b0a8c08..cca9f947 100644 --- a/+hw/DaqController.m +++ b/+hw/DaqController.m @@ -154,7 +154,7 @@ function command(obj, varargin) end channelNames = obj.ChannelNames(1:n); analogueChannelsIdx = obj.AnalogueChannelsIdx(1:n); - if any(analogueChannelsIdx)&&any(values(analogueChannelsIdx)~=0) + if any(analogueChannelsIdx)&&any(any(values(:,analogueChannelsIdx)~=0)) queue(obj, channelNames(analogueChannelsIdx), waveforms(analogueChannelsIdx)); if foreground startForeground(obj.DaqSession); diff --git a/+hw/DaqLever.m b/+hw/DaqLever.m new file mode 100644 index 00000000..47c9f3f8 --- /dev/null +++ b/+hw/DaqLever.m @@ -0,0 +1,129 @@ +classdef DaqLever < hw.PositionSensor + %HW.DaqLever Gets output from button + % Adopted from DaqRotaryEncoder + % AP 170629 + + properties + % hardcoded for zgood at the moment, not sure where this is normally changed AP 170629 + DaqSession = [] %DAQ session for input (see session-based interface docs) + DaqId = 'Dev2' %DAQ's device ID, e.g. 'Dev1' + DaqChannelId = 'port0/line3' %DAQ's ID for the counter channel. e.g. 'ctr0' + %DaqChannelId = 'ai3'; + end + + properties (Access = protected) + %Created when listenForAvailableData is called, allowing logging of + %positions during DAQ background acquision + DaqListener + DaqInputChannelIdx %Index into acquired input data matrices for our channel + LastDaqValue %Last value obtained from the DAQ counter + %Accumulated cycle number for position (i.e. when the DAQ's counter has + %over- or underflowed its range, this is incremented or decremented + %accordingly) + Cycle + end + + properties (Dependent) + DaqChannelIdx % index into DaqSession's channels for our data + end + + methods + function value = get.DaqChannelIdx(obj) + inputs = find(strcmpi('input', io.daqSessionChannelDirections(obj.DaqSession))); + value = inputs(obj.DaqInputChannelIdx); + end + + function set.DaqChannelIdx(obj, value) + % get directions of all channels on this session + dirs = io.daqSessionChannelDirections(obj.DaqSession); + % logical array flagging all input channels + inputsUptoChannel = strcmp(dirs(1:value), 'Input'); + % ensure the channel we're setting is an input + assert(inputsUptoChannel(value), 'Channel %i is not an input', value); + % find channel number counting inputs only + obj.DaqInputChannelIdx = sum(inputsUptoChannel); + end + + function createDaqChannel(obj) + % this didn't work - doesn't support timers, and something that I + % don't know never starts on stimserver + [ch, idx] = obj.DaqSession.addDigitalChannel(obj.DaqId, obj.DaqChannelId,'InputOnly'); + %[ch, idx] = obj.DaqSession.addAnalogInputChannel(obj.DaqId, obj.DaqChannelId,'Voltage'); + % quadrature encoding where each pulse from the channel updates + % the counter - ie. maximum resolution (see http://www.ni.com/white-paper/7109/en) + obj.DaqChannelIdx = idx; % record the index of the channel + %initialise LastDaqValue with current counter value + daqValue = obj.DaqSession.inputSingleScan(); + obj.LastDaqValue = daqValue(obj.DaqInputChannelIdx); + %reset cycle number + obj.Cycle = 0; + end + + function msg = wiringInfo(obj) + ch = obj.DaqSession.Channels(obj.DaqChannelIdx); + s1 = sprintf('Terminals: A = %s, B = %s\n', ... + ch.TerminalA, ch.TerminalB); + s2 = sprintf('For K�BLER 2400 series wiring is:\n'); + s3 = sprintf('GREEN -> %s, GREY -> %s, BROWN -> +5V, WHITE -> DGND\n',... + ch.TerminalA, ch.TerminalB); + msg = [s1 s2 s3]; + end + + function listenForAvailableData(obj) + % adds a listener to the DAQ session that will receive and process + % data when the DAQ is acquiring data in the background (i.e. + % startBackground() has been called on the session). + deleteListeners(obj); + obj.DaqListener = obj.DaqSession.addlistener('DataAvailable', ... + @(src, event) daqListener(obj, src, event)); + end + + function delete(obj) + deleteListeners(obj); + end + + function deleteListeners(obj) + if ~isempty(obj.DaqListener) + delete(obj.DaqListener); + end; + end + + function x = decodeDaq(obj, newValue) + %correct for 32-bit overflow/underflow + d = diff([obj.LastDaqValue; newValue]); + %decrement cycle for 'underflows', i.e. below 0 to a large value + %increment cycle for 'overflows', i.e. past max value to small values + cycle = obj.Cycle + cumsum(d < -0.5*obj.DaqCounterPeriod)... + - cumsum(d > 0.5*obj.DaqCounterPeriod); + x = obj.DaqCounterPeriod*cycle + newValue; + obj.Cycle = cycle(end); + obj.LastDaqValue = newValue(end); + end + end + + methods %(Access = protected) + function [x, time] = readAbsolutePosition(obj) + if obj.DaqSession.IsRunning + disp('waiting for session'); + obj.DaqSession.wait; + disp('done waiting'); + end + preTime = obj.Clock.now; + daqVal = inputSingleScan(obj.DaqSession); + x = daqVal; % AP 170629 straight digital read from lever + %x = decodeDaq(obj, daqVal(obj.DaqInputChannelIdx)); + postTime = obj.Clock.now; + time = 0.5*(preTime + postTime); % time is mean of before & after + end + end + + methods (Access = protected) + function daqListener(obj, src, event) + acqStartTime = obj.Clock.fromMatlab(event.TriggerTime); + values = decode(obj, event.Data(:,obj.DaqInputChannelIdx)) - obj.ZeroOffset; + times = acqStartTime + event.TimeStamps(:,obj.DaqInputChannelIdx); + logSamples(obj, values, times); + end + end +end + diff --git a/+hw/DaqLick.m b/+hw/DaqLick.m new file mode 100644 index 00000000..636c9ca9 --- /dev/null +++ b/+hw/DaqLick.m @@ -0,0 +1,131 @@ +classdef DaqLick < hw.PositionSensor + %HW.DaqLick Gets output from button + % Adopted from DaqRotaryEncoder + % AP 170629 + + properties + % hardcoded for zgood at the moment, not sure where this is normally changed AP 170629 + % (I think the protocol is to hardcode whatever, change manually, and + % save) + DaqSession = [] %DAQ session for input (see session-based interface docs) + DaqId = 'Dev2' %DAQ's device ID, e.g. 'Dev1' + DaqChannelId = 'port0/line2' %DAQ's ID for the counter channel. e.g. 'ctr0' + %DaqChannelId = 'ai3'; + end + + properties (Access = protected) + %Created when listenForAvailableData is called, allowing logging of + %positions during DAQ background acquision + DaqListener + DaqInputChannelIdx %Index into acquired input data matrices for our channel + LastDaqValue %Last value obtained from the DAQ counter + %Accumulated cycle number for position (i.e. when the DAQ's counter has + %over- or underflowed its range, this is incremented or decremented + %accordingly) + Cycle + end + + properties (Dependent) + DaqChannelIdx % index into DaqSession's channels for our data + end + + methods + function value = get.DaqChannelIdx(obj) + inputs = find(strcmpi('input', io.daqSessionChannelDirections(obj.DaqSession))); + value = inputs(obj.DaqInputChannelIdx); + end + + function set.DaqChannelIdx(obj, value) + % get directions of all channels on this session + dirs = io.daqSessionChannelDirections(obj.DaqSession); + % logical array flagging all input channels + inputsUptoChannel = strcmp(dirs(1:value), 'Input'); + % ensure the channel we're setting is an input + assert(inputsUptoChannel(value), 'Channel %i is not an input', value); + % find channel number counting inputs only + obj.DaqInputChannelIdx = sum(inputsUptoChannel); + end + + function createDaqChannel(obj) + % this didn't work - doesn't support timers, and something that I + % don't know never starts on stimserver + [ch, idx] = obj.DaqSession.addDigitalChannel(obj.DaqId, obj.DaqChannelId,'InputOnly'); + %[ch, idx] = obj.DaqSession.addAnalogInputChannel(obj.DaqId, obj.DaqChannelId,'Voltage'); + % quadrature encoding where each pulse from the channel updates + % the counter - ie. maximum resolution (see http://www.ni.com/white-paper/7109/en) + obj.DaqChannelIdx = idx; % record the index of the channel + %initialise LastDaqValue with current counter value + daqValue = obj.DaqSession.inputSingleScan(); + obj.LastDaqValue = daqValue(obj.DaqInputChannelIdx); + %reset cycle number + obj.Cycle = 0; + end + + function msg = wiringInfo(obj) + ch = obj.DaqSession.Channels(obj.DaqChannelIdx); + s1 = sprintf('Terminals: A = %s, B = %s\n', ... + ch.TerminalA, ch.TerminalB); + s2 = sprintf('For K�BLER 2400 series wiring is:\n'); + s3 = sprintf('GREEN -> %s, GREY -> %s, BROWN -> +5V, WHITE -> DGND\n',... + ch.TerminalA, ch.TerminalB); + msg = [s1 s2 s3]; + end + + function listenForAvailableData(obj) + % adds a listener to the DAQ session that will receive and process + % data when the DAQ is acquiring data in the background (i.e. + % startBackground() has been called on the session). + deleteListeners(obj); + obj.DaqListener = obj.DaqSession.addlistener('DataAvailable', ... + @(src, event) daqListener(obj, src, event)); + end + + function delete(obj) + deleteListeners(obj); + end + + function deleteListeners(obj) + if ~isempty(obj.DaqListener) + delete(obj.DaqListener); + end; + end + + function x = decodeDaq(obj, newValue) + %correct for 32-bit overflow/underflow + d = diff([obj.LastDaqValue; newValue]); + %decrement cycle for 'underflows', i.e. below 0 to a large value + %increment cycle for 'overflows', i.e. past max value to small values + cycle = obj.Cycle + cumsum(d < -0.5*obj.DaqCounterPeriod)... + - cumsum(d > 0.5*obj.DaqCounterPeriod); + x = obj.DaqCounterPeriod*cycle + newValue; + obj.Cycle = cycle(end); + obj.LastDaqValue = newValue(end); + end + end + + methods %(Access = protected) + function [x, time] = readAbsolutePosition(obj) + if obj.DaqSession.IsRunning + disp('waiting for session'); + obj.DaqSession.wait; + disp('done waiting'); + end + preTime = obj.Clock.now; + daqVal = inputSingleScan(obj.DaqSession); + x = daqVal; % AP 170629 straight digital read from lever + %x = decodeDaq(obj, daqVal(obj.DaqInputChannelIdx)); + postTime = obj.Clock.now; + time = 0.5*(preTime + postTime); % time is mean of before & after + end + end + + methods (Access = protected) + function daqListener(obj, src, event) + acqStartTime = obj.Clock.fromMatlab(event.TriggerTime); + values = decode(obj, event.Data(:,obj.DaqInputChannelIdx)) - obj.ZeroOffset; + times = acqStartTime + event.TimeStamps(:,obj.DaqInputChannelIdx); + logSamples(obj, values, times); + end + end +end + diff --git a/+hw/DaqPiezo.m b/+hw/DaqPiezo.m new file mode 100644 index 00000000..c83500f9 --- /dev/null +++ b/+hw/DaqPiezo.m @@ -0,0 +1,129 @@ +classdef DaqPiezo < hw.PositionSensor + %HW.DaqPiezo Gets output from button + % Adopted from DaqRotaryEncoder + % AP 170629 + + properties + % hardcoded for zgood at the moment, not sure where this is normally changed AP 170629 + DaqSession = [] %DAQ session for input (see session-based interface docs) + DaqId = 'Dev2' %DAQ's device ID, e.g. 'Dev1' + DaqChannelId = 'port0/line3' %DAQ's ID for the counter channel. e.g. 'ctr0' + %DaqChannelId = 'ai3'; + end + + properties (Access = protected) + %Created when listenForAvailableData is called, allowing logging of + %positions during DAQ background acquision + DaqListener + DaqInputChannelIdx %Index into acquired input data matrices for our channel + LastDaqValue %Last value obtained from the DAQ counter + %Accumulated cycle number for position (i.e. when the DAQ's counter has + %over- or underflowed its range, this is incremented or decremented + %accordingly) + Cycle + end + + properties (Dependent) + DaqChannelIdx % index into DaqSession's channels for our data + end + + methods + function value = get.DaqChannelIdx(obj) + inputs = find(strcmpi('input', io.daqSessionChannelDirections(obj.DaqSession))); + value = inputs(obj.DaqInputChannelIdx); + end + + function set.DaqChannelIdx(obj, value) + % get directions of all channels on this session + dirs = io.daqSessionChannelDirections(obj.DaqSession); + % logical array flagging all input channels + inputsUptoChannel = strcmp(dirs(1:value), 'Input'); + % ensure the channel we're setting is an input + assert(inputsUptoChannel(value), 'Channel %i is not an input', value); + % find channel number counting inputs only + obj.DaqInputChannelIdx = sum(inputsUptoChannel); + end + + function createDaqChannel(obj) + % this didn't work - doesn't support timers, and something that I + % don't know never starts on stimserver + [ch, idx] = obj.DaqSession.addDigitalChannel(obj.DaqId, obj.DaqChannelId,'InputOnly'); + %[ch, idx] = obj.DaqSession.addAnalogInputChannel(obj.DaqId, obj.DaqChannelId,'Voltage'); + % quadrature encoding where each pulse from the channel updates + % the counter - ie. maximum resolution (see http://www.ni.com/white-paper/7109/en) + obj.DaqChannelIdx = idx; % record the index of the channel + %initialise LastDaqValue with current counter value + daqValue = obj.DaqSession.inputSingleScan(); + obj.LastDaqValue = daqValue(obj.DaqInputChannelIdx); + %reset cycle number + obj.Cycle = 0; + end + + function msg = wiringInfo(obj) + ch = obj.DaqSession.Channels(obj.DaqChannelIdx); + s1 = sprintf('Terminals: A = %s, B = %s\n', ... + ch.TerminalA, ch.TerminalB); + s2 = sprintf('For K�BLER 2400 series wiring is:\n'); + s3 = sprintf('GREEN -> %s, GREY -> %s, BROWN -> +5V, WHITE -> DGND\n',... + ch.TerminalA, ch.TerminalB); + msg = [s1 s2 s3]; + end + + function listenForAvailableData(obj) + % adds a listener to the DAQ session that will receive and process + % data when the DAQ is acquiring data in the background (i.e. + % startBackground() has been called on the session). + deleteListeners(obj); + obj.DaqListener = obj.DaqSession.addlistener('DataAvailable', ... + @(src, event) daqListener(obj, src, event)); + end + + function delete(obj) + deleteListeners(obj); + end + + function deleteListeners(obj) + if ~isempty(obj.DaqListener) + delete(obj.DaqListener); + end; + end + + function x = decodeDaq(obj, newValue) + %correct for 32-bit overflow/underflow + d = diff([obj.LastDaqValue; newValue]); + %decrement cycle for 'underflows', i.e. below 0 to a large value + %increment cycle for 'overflows', i.e. past max value to small values + cycle = obj.Cycle + cumsum(d < -0.5*obj.DaqCounterPeriod)... + - cumsum(d > 0.5*obj.DaqCounterPeriod); + x = obj.DaqCounterPeriod*cycle + newValue; + obj.Cycle = cycle(end); + obj.LastDaqValue = newValue(end); + end + end + + methods %(Access = protected) + function [x, time] = readAbsolutePosition(obj) + if obj.DaqSession.IsRunning + disp('waiting for session'); + obj.DaqSession.wait; + disp('done waiting'); + end + preTime = obj.Clock.now; + daqVal = inputSingleScan(obj.DaqSession); + x = daqVal; % AP 170629 straight digital read from lever + %x = decodeDaq(obj, daqVal(obj.DaqInputChannelIdx)); + postTime = obj.Clock.now; + time = 0.5*(preTime + postTime); % time is mean of before & after + end + end + + methods (Access = protected) + function daqListener(obj, src, event) + acqStartTime = obj.Clock.fromMatlab(event.TriggerTime); + values = decode(obj, event.Data(:,obj.DaqInputChannelIdx)) - obj.ZeroOffset; + times = acqStartTime + event.TimeStamps(:,obj.DaqInputChannelIdx); + logSamples(obj, values, times); + end + end +end + diff --git a/+hw/PulseSwitcher.m b/+hw/PulseSwitcher.m index e5b0e5f3..4321bf60 100644 --- a/+hw/PulseSwitcher.m +++ b/+hw/PulseSwitcher.m @@ -21,7 +21,7 @@ end function samples = waveform(obj, sampleRate, command) - [dt, npulses, f] = obj.ParamsFun(command); + [dt, npulses, f] = obj.ParamsFun(command(1)); wavelength = 1/f; duty = dt/wavelength; assert(duty <= (1 + 1e-3), 'Pulse width larger than wavelength (duty=%.2f)', duty); diff --git a/+hw/SinePulseGenerator.m b/+hw/SinePulseGenerator.m new file mode 100644 index 00000000..95576183 --- /dev/null +++ b/+hw/SinePulseGenerator.m @@ -0,0 +1,45 @@ +classdef SinePulseGenerator < hw.ControlSignalGenerator + %HW.PULSESWITCHER Generates a train of pulses + % Detailed explanation goes here + + properties + Offset + end + + methods + function obj = SinePulseGenerator(offset) + obj.DefaultValue = 0; + obj.Offset = offset; + end + + function samples = waveform(obj, sampleRate, pars) + dt = pars(1); + + if numel(pars)==3 + f = pars(2); + amp = pars(3); + else + f = 40; + amp = 1; + end + + % first construct one cycle at this frequency + oneCycleDt = 1/f; + t = linspace(0, oneCycleDt - 1/sampleRate, sampleRate*oneCycleDt); + samples = amp/2*(-cos(2*pi*f*t) + 1); + + % if dt is greater than the duration of that cycle, then put zeros in + % the middle + if dt>oneCycleDt + nSamp = round(dt*sampleRate)-numel(samples); + m = round(numel(samples)/2); + samples = [samples(1:m) amp*ones(1,nSamp) samples(m+1:end)]; + end + + % add a zero so it turns off at the end + samples = [samples'; 0]; + end + end + +end + From e78f5e8feb6fea503c3f0c8942de8b7a282c1e89 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 19 Dec 2017 12:21:45 +0000 Subject: [PATCH 005/393] Further Alyx integration and tl fix * Timeline now adds inputs for easier indexing in analysis code * Alyx session created now moved to dat.newExp as this is a more logical place for it * launchSessionURL button should now work on the Alyx panel. If a base session doesn't exist when the button is pressed, one is created. --- +dat/newExp.m | 73 ++++++++++++++++++++++++++++++++++++++++++++---- +eui/AlyxPanel.m | 65 +++++++++++++++++++----------------------- +eui/MControl.m | 64 ++++-------------------------------------- +hw/Timeline.m | 2 +- 4 files changed, 103 insertions(+), 101 deletions(-) diff --git a/+dat/newExp.m b/+dat/newExp.m index 035d351e..0e63070a 100644 --- a/+dat/newExp.m +++ b/+dat/newExp.m @@ -1,6 +1,19 @@ -function [expRef, expSeq] = newExp(subject, expDate, expParams) +function [expRef, expSeq] = newExp(subject, expDate, expParams, AlyxInstance) %DAT.NEWEXP Create a new unique experiment in the database -% [ref, seq] = DAT.NEWEXP(subject, expDate, expParams) TODO +% [ref, seq] = DAT.NEWEXP(subject, expDate, expParams[, AlyxInstance]) +% Create a new experiment by creating the relevant folder tree in the +% local and main data repositories in the following format: +% +% subject/ +% |_ YYYY-MM-DD/ +% |_ expSeq/ +% +% If experiment parameters are passed into the function, they are saved +% here. If an instance of Alyx is passed and a base session for the +% experiment date is not found, one is created in the Alyx database. +% A corresponding subsession is also created. +% +% See also DAT.PATHS % % Part of Rigbox @@ -16,6 +29,11 @@ expParams = []; end +if nargin < 4 + % no instance of Alyx, don't create session on Alyx + AlyxInstance = []; +end + if ischar(expDate) % if the passed expDate is a string, parse it into a datenum expDate = datenum(expDate, 'yyyy-mm-dd'); @@ -29,8 +47,7 @@ [~, dateList, seqList] = dat.listExps(subject); % filter the list by expdate -expDate = floor(expDate); -filterIdx = dateList == expDate; +filterIdx = dateList == floor(expDate); % find the next sequence number expSeq = max(seqList(filterIdx)) + 1; @@ -40,7 +57,7 @@ end % expInfo repository is the reference location for which experiments exist -[expPath, expRef] = dat.expPath(subject, expDate, expSeq, 'expInfo'); +[expPath, expRef] = dat.expPath(subject, floor(expDate), expSeq, 'expInfo'); % ensure nothing went wrong in making a "unique" ref and path to hold assert(~any(file.exists(expPath)), ... sprintf('Something went wrong as experiment folders already exist for "%s".', expRef)); @@ -48,6 +65,52 @@ % now make the folder(s) to hold the new experiment assert(all(cellfun(@(p) mkdir(p), expPath)), 'Creating experiment directories failed'); +if ~strcmp(subject,'default') % Ignore fake subject + % if the Alyx Instance is set, find or create BASE session + expDate = alyx.datestr(expDate); % date in Alyx format + if ~isempty(AlyxInstance) + % Get list of base sessions + sessions = alyx.getData(AlyxInstance,... + ['sessions?type=Base&subject=' subject]); + + %If the date of this latest base session is not the same date as + %today, then create a new base session for today + if isempty(sessions) || ~strcmp(sessions{end}.start_time(1:10), expDate(1:10)) + d = struct; + d.subject = subject; + d.procedures = {'Behavior training/tasks'}; + d.narrative = 'auto-generated session'; + d.start_time = expDate; + d.type = 'Base'; + + base_submit = alyx.postData(AlyxInstance, 'sessions', d); + assert(isfield(base_submit,'subject'),... + 'Submitted base session did not return appropriate values'); + + %Now retrieve the sessions again + sessions = alyx.getData(AlyxInstance,... + ['sessions?type=Base&subject=' subject]); + end + latest_base = sessions{end}; + else % If not logged in to Alyx... + latest_base.url = []; % set the base url to null + end + + %Now create a new SUBSESSION, using the same experiment number + d = struct; + d.subject = subject; + d.procedures = {'Behavior training/tasks'}; + d.narrative = 'auto-generated session'; + d.start_time = expDate; + d.type = 'Experiment'; + d.parent_session = latest_base.url; + d.number = expSeq; + + subsession = alyx.postData(AlyxInstance, 'sessions', d); + assert(isfield(subsession,'subject'),... + 'Failed to create new sub-session in Alyx for %s', subject); +end + % if the parameters had an experiment definition function, save a copy in % the experiment's folder if isfield(expParams, 'defFunction') diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index 5e23a39e..02889e23 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -408,55 +408,46 @@ function recordWeight(obj, weight, subject) end function launchSessionURL(obj) - % Launch the Webpage for the current session in the default Web - % browser. If no session exists for today's date, a new base - % and/or subsession is created accordingly. - % TODO: Do we really want to create a session if one doesn't - % exist? + % Launch the Webpage for the current base session in the + % default Web browser. If no session exists for today's date, + % a new base session is created accordingly. + % + % See also LAUNCHSUBJECTURL ai = obj.AlyxInstance; % determine whether there is a session for this subj and date thisDate = alyx.datestr(now); - sessions = alyx.getData(ai, ['sessions?type=Experiment&subject=' obj.Subject]); + sessions = alyx.getData(ai, ['sessions?type=Base&subject=' obj.Subject]); - % If the date of this latest session is not the same date as - % today, then create a new session for today + % If the date of this latest base session is not the same date + % as today, then create a new one for today if isempty(sessions) || ~strcmp(sessions{end}.start_time(1:10), thisDate(1:10)) % Ask user whether he/she wants to create new session % Construct a questdlg with three options - choice = questdlg('Would you like to create a new session?', ... - ['No session exists for ' datestr(now, 'yyyy-mm-dd')], ... + choice = questdlg('Would you like to create a new base session?', ... + ['No base session exists for ' datestr(now, 'yyyy-mm-dd')], ... 'Yes','No','No'); % Handle response switch choice case 'Yes' - % Check if base session exists - baseSessions = alyx.getData(ai, ['sessions?type=Experiment&subject=' obj.Subject]); - if isempty(baseSessions) || ~strcmp(baseSessions{end}.start_time(1:10), thisDate(1:10)) - % Create our base session - d = struct; - d.subject = obj.Subject; - d.procedures = {'Behavior training/tasks'}; - d.narrative = 'auto-generated session'; - d.start_time = thisDate; - d.type = 'Base'; - - base_submit = alyx.postData(ai, 'sessions', d); - if ~isfield(base_submit,'subject') % fail - warning('Submitted base session did not return appropriate values'); - warning('Submitted data below:'); - disp(d) - warning('Return values below:'); - disp(base_submit) - return - else % success - obj.log(['Created new base session in Alyx for ' obj.Subject]); - end + % Create our base session + d = struct; + d.subject = obj.Subject; + d.procedures = {'Behavior training/tasks'}; + d.narrative = 'auto-generated session'; + d.start_time = thisDate; + d.type = 'Base'; + + thisSess = alyx.postData(ai, 'sessions', d); + if ~isfield(thisSess,'subject') % fail + warning('Submitted base session did not return appropriate values'); + warning('Submitted data below:'); + disp(d) + warning('Return values below:'); + disp(thisSess) + return + else % success + obj.log(['Created new base session in Alyx for ' obj.Subject]); end - % Now create a new SUBSESSION, using the same experiment number - % d = struct; - % d.subject = obj.Subject; - % d.start_time = alyx.datestr(now); - % d.users = {ai.username}; case 'No' return end diff --git a/+eui/MControl.m b/+eui/MControl.m index 9571d559..0cde0300 100644 --- a/+eui/MControl.m +++ b/+eui/MControl.m @@ -544,64 +544,12 @@ function beginExp(obj) services = rig.Services(rig.SelectedServices); % Add these services to the parameters obj.Parameters.set('services', services(:),... - 'List of experiment services to use during the experiment'); - [expRef, seq] = dat.newExp(obj.NewExpSubject.Selected, now, obj.Parameters.Struct); % Create new experiment reference - % Set up new session on Alyx - if ~isempty(obj.AlyxPanel.AlyxInstance)&&~strcmp(obj.NewExpSubject.Selected,'default') - %Find/create BASE session, then create subsession - thisDate = alyx.datestr(now); - sessions = alyx.getData(obj.AlyxPanel.AlyxInstance,... - ['sessions?type=Base&subject=' obj.NewExpSubject.Selected]); - - %If the date of this latest base session is not the same date as - %today, then create a new base session for today - if isempty(sessions) || ~strcmp(sessions{end}.start_time(1:10), thisDate(1:10)) - d = struct; - d.subject = obj.NewExpSubject.Selected; - d.procedures = {'Behavior training/tasks'}; - d.narrative = 'auto-generated session'; - d.start_time = thisDate; - d.type = 'Base'; - - base_submit = alyx.postData(obj.AlyxPanel.AlyxInstance, 'sessions', d); - if ~isfield(base_submit,'subject') - warning('Submitted base session did not return appropriate values'); - warning('Submitted data below:'); - disp(d) - warning('Return values below:'); - disp(base_submit) - return - else - obj.log(['Created new base session in Alyx for ' obj.NewExpSubject.Selected]); - end - end - - %Now retrieve the sessions again - sessions = alyx.getData(obj.AlyxPanel.AlyxInstance,... - ['sessions?type=Base&subject=' obj.NewExpSubject.Selected]); - latest_base = sessions{end}; - - %Now create a new SUBSESSION, using the same experiment number - d = struct; - d.subject = obj.NewExpSubject.Selected; - d.procedures = {'Behavior training/tasks'}; - d.narrative = 'auto-generated session'; - d.start_time = thisDate; - d.type = 'Experiment'; - d.parent_session = latest_base.url; - d.number = seq; - - subsession = alyx.postData(obj.AlyxPanel.AlyxInstance, 'sessions', d); - if ~isfield(subsession,'subject') - obj.log(['Failed to create new sub-session in Alyx for ' obj.NewExpSubject.Selected]); - disp(d) - end - obj.log(['Created new sub-session in Alyx for ', obj.NewExpSubject.Selected]); - % Add a copy of the AlyxInstance to the rig object for later - % water registration, &c. - rig.AlyxInstance = obj.AlyxPanel.AlyxInstance; - rig.AlyxInstance.subsessionURL = subsession.url; - end + 'List of experiment services to use during the experiment'); + expRef = dat.newExp(obj.NewExpSubject.Selected, now, obj.Parameters.Struct); % Create new experiment reference + % Add a copy of the AlyxInstance to the rig object for later + % water registration, &c. + rig.AlyxInstance = AlyxInstance; + rig.AlyxInstance.subsessionURL = subsession.url; panel = eui.ExpPanel.live(obj.ActiveExpsGrid, expRef, rig, obj.Parameters.Struct); obj.LastExpPanel = panel; diff --git a/+hw/Timeline.m b/+hw/Timeline.m index 5efa5d2f..9d92d5ca 100644 --- a/+hw/Timeline.m +++ b/+hw/Timeline.m @@ -522,7 +522,7 @@ function init(obj) inputSession.NotifyWhenDataAvailableExceeds = obj.DaqSamplesPerNotify; % when to process data obj.Sessions('main') = inputSession; for i = 1:length(use) - in = obj.Inputs(idx(i)); % get channel info, etc. + in = inputOptions(strcmp({obj.Inputs.name}, obj.UseInputs(i))); % get channel info, etc. switch in.measurement case 'Voltage' ch = obj.Sessions('main').addAnalogInputChannel(obj.DaqIds, in.daqChannelID, in.measurement); From c082d3018a4f3e2fa81d6ee578d5ebeb6bd5e4fc Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 19 Dec 2017 15:06:48 +0000 Subject: [PATCH 006/393] subsession url assigned properly subsession url is now assigned properly when starting an experiment --- +dat/newExp.m | 9 ++++++--- +eui/MControl.m | 8 +++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/+dat/newExp.m b/+dat/newExp.m index 0e63070a..df5c6ca1 100644 --- a/+dat/newExp.m +++ b/+dat/newExp.m @@ -1,6 +1,6 @@ -function [expRef, expSeq] = newExp(subject, expDate, expParams, AlyxInstance) +function [expRef, expSeq, url] = newExp(subject, expDate, expParams, AlyxInstance) %DAT.NEWEXP Create a new unique experiment in the database -% [ref, seq] = DAT.NEWEXP(subject, expDate, expParams[, AlyxInstance]) +% [ref, seq, url] = DAT.NEWEXP(subject, expDate, expParams[, AlyxInstance]) % Create a new experiment by creating the relevant folder tree in the % local and main data repositories in the following format: % @@ -82,6 +82,7 @@ d.narrative = 'auto-generated session'; d.start_time = expDate; d.type = 'Base'; +% d.users = {AlyxInstance.username}; base_submit = alyx.postData(AlyxInstance, 'sessions', d); assert(isfield(base_submit,'subject'),... @@ -91,7 +92,7 @@ sessions = alyx.getData(AlyxInstance,... ['sessions?type=Base&subject=' subject]); end - latest_base = sessions{end}; + latest_base = sessions{end}; else % If not logged in to Alyx... latest_base.url = []; % set the base url to null end @@ -105,10 +106,12 @@ d.type = 'Experiment'; d.parent_session = latest_base.url; d.number = expSeq; +% d.users = {AlyxInstance.username}; subsession = alyx.postData(AlyxInstance, 'sessions', d); assert(isfield(subsession,'subject'),... 'Failed to create new sub-session in Alyx for %s', subject); + url = subsession.url; end % if the parameters had an experiment definition function, save a copy in diff --git a/+eui/MControl.m b/+eui/MControl.m index 0cde0300..abf5b918 100644 --- a/+eui/MControl.m +++ b/+eui/MControl.m @@ -545,11 +545,13 @@ function beginExp(obj) % Add these services to the parameters obj.Parameters.set('services', services(:),... 'List of experiment services to use during the experiment'); - expRef = dat.newExp(obj.NewExpSubject.Selected, now, obj.Parameters.Struct); % Create new experiment reference + % Create new experiment reference + [expRef, ~, url] = dat.newExp(obj.NewExpSubject.Selected, now,... + obj.Parameters.Struct, obj.AlyxPanel.AlyxInstance); % Add a copy of the AlyxInstance to the rig object for later % water registration, &c. - rig.AlyxInstance = AlyxInstance; - rig.AlyxInstance.subsessionURL = subsession.url; + rig.AlyxInstance = obj.AlyxPanel.AlyxInstance; + rig.AlyxInstance.subsessionURL = url; panel = eui.ExpPanel.live(obj.ActiveExpsGrid, expRef, rig, obj.Parameters.Struct); obj.LastExpPanel = panel; From 43aa391e44470bc475c187ce33089623a7ed8dcf Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 19 Dec 2017 16:34:55 +0000 Subject: [PATCH 007/393] Fixed error when running default mouse url output now defined in all circumstances --- +dat/newExp.m | 2 ++ 1 file changed, 2 insertions(+) diff --git a/+dat/newExp.m b/+dat/newExp.m index df5c6ca1..ae7a3e76 100644 --- a/+dat/newExp.m +++ b/+dat/newExp.m @@ -112,6 +112,8 @@ assert(isfield(subsession,'subject'),... 'Failed to create new sub-session in Alyx for %s', subject); url = subsession.url; +else + url = []; end % if the parameters had an experiment definition function, save a copy in From 7e03ec60fbe9a1e000a244f4a1e456dadae0ca7d Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 19 Dec 2017 17:40:12 +0000 Subject: [PATCH 008/393] reverted Timeline as 'fix' didn't work --- +hw/Timeline.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/+hw/Timeline.m b/+hw/Timeline.m index 9d92d5ca..7d514568 100644 --- a/+hw/Timeline.m +++ b/+hw/Timeline.m @@ -522,7 +522,7 @@ function init(obj) inputSession.NotifyWhenDataAvailableExceeds = obj.DaqSamplesPerNotify; % when to process data obj.Sessions('main') = inputSession; for i = 1:length(use) - in = inputOptions(strcmp({obj.Inputs.name}, obj.UseInputs(i))); % get channel info, etc. + in = obj.Inputs(idx(i)); % get channel info, etc. switch in.measurement case 'Voltage' ch = obj.Sessions('main').addAnalogInputChannel(obj.DaqIds, in.daqChannelID, in.measurement); From 7882a77ad53a69f6356c60a081e96420f25f5c8a Mon Sep 17 00:00:00 2001 From: nsteinme <nick.steinmetz@gmail.com> Date: Sun, 7 Jan 2018 18:27:17 +0000 Subject: [PATCH 009/393] modernize mpep listener - incomplete --- +hw/Timeline.m | 6 +++- cortexlab/+tl/bindMpepServerWithWS.m | 50 ++++++++++++++++++---------- 2 files changed, 38 insertions(+), 18 deletions(-) diff --git a/+hw/Timeline.m b/+hw/Timeline.m index 7d514568..f6f141e5 100644 --- a/+hw/Timeline.m +++ b/+hw/Timeline.m @@ -522,7 +522,9 @@ function init(obj) inputSession.NotifyWhenDataAvailableExceeds = obj.DaqSamplesPerNotify; % when to process data obj.Sessions('main') = inputSession; for i = 1:length(use) - in = obj.Inputs(idx(i)); % get channel info, etc. + in = obj.Inputs(idx(i)); % get channel info, etc. + fprintf(1, 'adding channel %s on %s\n', in.name, in.daqChannelID); + switch in.measurement case 'Voltage' ch = obj.Sessions('main').addAnalogInputChannel(obj.DaqIds, in.daqChannelID, in.measurement); @@ -603,7 +605,9 @@ function process(obj, ~, event) fwrite(obj.DataFID, datToWrite', obj.AquiredDataType); % Write to file end % if plotting the channels live, plot the new data + if obj.LivePlot; obj.livePlot(event.Data); end + end function livePlot(obj, data) diff --git a/cortexlab/+tl/bindMpepServerWithWS.m b/cortexlab/+tl/bindMpepServerWithWS.m index 33d8996b..187583cf 100644 --- a/cortexlab/+tl/bindMpepServerWithWS.m +++ b/cortexlab/+tl/bindMpepServerWithWS.m @@ -45,6 +45,12 @@ communicator.EventMode = false; communicator.open(); +%% initialize timeline + +rig = hw.devices([], false); +tlObj = rig.timeline; +tls.tlObj = tlObj; + %% Helper functions function closeConns() @@ -61,10 +67,10 @@ function process() function processListener(listener) sz = pnet(listener.socket, 'readpacket', 1000, 'noblock'); if sz > 0 - t = tl.time(false); % save the time we got the UDP packet + t = tlObj.time(false); % save the time we got the UDP packet msg = pnet(listener.socket, 'read'); - if tl.running - tl.record([listener.name 'UDP'], msg, t); % record the UDP event in Timeline + if tlObj.IsRunning + tlObj.record([listener.name 'UDP'], msg, t); % record the UDP event in Timeline end listener.callback(listener, msg); % call special handling function end @@ -77,6 +83,10 @@ function processMpep(listener, msg) log('%s: ''%s'' from %s:%i', listener.name, msg, ipstr, port); % parse the message info = dat.mpepMessageParse(msg); + + % !!! Get alyx instance here!! + ai = []; + failed = false; % flag for preventing UDP echo %% Experiment-level events start/stop timeline switch lower(info.instruction) @@ -85,10 +95,10 @@ function processMpep(listener, msg) try % start Timeline communicator.send('status', { 'starting', info.expRef}); - tl.start(info.expRef); + tlObj.start(info.expRef, ai); % re-record the UDP event in Timeline since it wasn't started % when we tried earlier. Treat it as having arrived at time zero. - tl.record('mpepUDP', msg, 0); + tlObj.record('mpepUDP', msg, 0); catch ex % flag up failure so we do not echo the UDP message back below failed = true; @@ -96,11 +106,11 @@ function processMpep(listener, msg) end case 'expend' - tl.stop(); % stop Timeline + tlObj.stop(); % stop Timeline communicator.send('status', { 'completed', info.expRef}); case 'expinterrupt' - tl.stop(); % stop Timeline + tlObj.stop(); % stop Timeline communicator.send('status', { 'completed', info.expRef}); end if ~failed @@ -135,20 +145,26 @@ function listen() if firstPress(quitKey) running = false; end - if firstPress(manualStartKey) && ~tl.running - [mouseName, ~] = dat.subjectSelector(); + if firstPress(manualStartKey) && ~tlObj.IsRunning + + % first get an alyx instance + ai = alyx.loginWindow(); + + [mouseName, ~] = dat.subjectSelector([],ai); + if ~isempty(mouseName) clear expParams; expParams.experimentType = 'timelineManualStart'; - newExpRef = dat.newExp(mouseName, now, expParams); + newExpRef = dat.newExp(mouseName, now, expParams, ai); %[subjectRef, expDate, expSequence] = dat.parseExpRef(newExpRef); %newExpRef = dat.constructExpRef(mouseName, now, expNum); communicator.send('status', { 'starting', newExpRef}); - tl.start(newExpRef); + tlObj.start(newExpRef, ai); end - elseif firstPress(manualStartKey) && tl.running && ~isempty(newExpRef) - - tl.stop(); + KbQueueFlush; + elseif firstPress(manualStartKey) && tlObj.IsRunning && ~isempty(newExpRef) + fprintf(1, 'stopping timeline\n'); + tlObj.stop(); communicator.send('status', { 'completed', newExpRef}); newExpRef = []; end @@ -172,8 +188,8 @@ function handleMessage(id, data, host) % client disconnected log('WS: ''%s'' disconnected', host); else - command = data{1}; - args = data(2:end); + command = data{1} + args = data(2:end) if ~strcmp(command, 'status') % log the command received log('WS: Received ''%s''', command); @@ -181,7 +197,7 @@ function handleMessage(id, data, host) switch command case 'status' % status request - if ~tl.running + if ~tlObj.IsRunning communicator.send(id, {'idle'}); else communicator.send(id, {'running'}); From 3b1fe577bd5155fa97072717d9b059951082ee95 Mon Sep 17 00:00:00 2001 From: nsteinme <nick.steinmetz@gmail.com> Date: Sun, 7 Jan 2018 18:36:19 +0000 Subject: [PATCH 010/393] fix channel ordering bug --- +dat/findNextSeqNum.m | 24 +++++++++++ +dat/subjectSelector.m | 98 ++++++++++++++++++++++++++++++++++++++++++ +hw/Timeline.m | 5 ++- 3 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 +dat/findNextSeqNum.m create mode 100644 +dat/subjectSelector.m diff --git a/+dat/findNextSeqNum.m b/+dat/findNextSeqNum.m new file mode 100644 index 00000000..45450073 --- /dev/null +++ b/+dat/findNextSeqNum.m @@ -0,0 +1,24 @@ + + +function expSeq = findNextSeqNum(subject, varargin) +% function expSeq = findNextSeqNum(subject[, date]) +if isempty(varargin) + expDate = now; +else + expDate = varargin{1}; +end + + +% retrieve list of experiments for subject +[~, dateList, seqList] = dat.listExps(subject); + +% filter the list by expdate +expDate = floor(expDate); +filterIdx = dateList == expDate; + +% find the next sequence number +expSeq = max(seqList(filterIdx)) + 1; +if isempty(expSeq) + % if none today, max will have returned [], so override this to 1 + expSeq = 1; +end \ No newline at end of file diff --git a/+dat/subjectSelector.m b/+dat/subjectSelector.m new file mode 100644 index 00000000..8437dad5 --- /dev/null +++ b/+dat/subjectSelector.m @@ -0,0 +1,98 @@ +function [subjectName, expNum] = subjectSelector(varargin) +% function [subjectName, expNum] = subjectSelector([parentFig], [alyxInstance]) +% make a popup window that will allow selection of a subject and expNum +% +% If you provide an alyxInstance, it will populate with a list of subjects +% from alyx; otherwise, from dat.listSubjects + +subjectName = []; +expNum = 1; + +f = figure(); +set(f, 'MenuBar', 'none', 'Name', 'Select subject', 'NumberTitle', 'off','Resize', 'off', ... + 'WindowStyle', 'modal'); +w = 300; +h = 50; + +if nargin>0 && ~isempty(varargin{1}) + parentPos = get(varargin{1}, 'Position'); +else + parentPos = get(f, 'Position'); +end + +newPos = [parentPos(1)+parentPos(3)/2-w/2, parentPos(2)+parentPos(4)/2-h/2, w, h]; +set(f, 'Position', newPos); + +txtChooseSubject = uicontrol('Style', 'text', 'Parent', f, ... + 'Position',[10 h-30 90 25], ... + 'String', 'Choose subject:', 'HorizontalAlignment', 'right'); + +txtChooseExpNum = uicontrol('Style', 'text', 'Parent', f, ... + 'Position',[10 h-55 90 25], ... + 'String', 'Choose exp num:', 'HorizontalAlignment', 'right'); + +subjectDropdown = uicontrol('Style', 'popupmenu', 'Parent', f, ... + 'Position',[110 h-25 90 25], ... + 'Background', [1 1 1], 'Callback', @pickExpNum); + +if nargin>1 + ai = varargin{2}; + + s = alyx.getData(ai, 'subjects?stock=False&alive=True'); + + respUser = cellfun(@(x)x.responsible_user, s, 'uni', false); + subjNames = cellfun(@(x)x.nickname, s, 'uni', false); + + thisUserSubs = sort(subjNames(strcmp(respUser, ai.username))); + otherUserSubs = sort(subjNames); + % note that we leave this User's mice also in + % otherUserSubs, in case they get confused and look + % there. + + newSubs = [{'default'}, thisUserSubs, otherUserSubs]; + set(subjectDropdown, 'String', newSubs); +else + set(subjectDropdown, 'String', dat.listSubjects); +end + +edtExpNum = uicontrol('Style', 'text', 'Parent', f, ... + 'Position',[110 h-50 90 25], ... + 'String', num2str(expNum), 'Background', [1 1 1]); + + +uicontrol('Style', 'pushbutton', 'String', 'OK', 'Position', ... + [210 h-25 90 25],'Callback', @ok); +uicontrol('Style', 'pushbutton', 'String', 'Cancel', 'Position', ... + [210 h-50 90 25],'Callback', @cancel); + +uiwait(); + + function ok(~,~) + + subjectList = get(subjectDropdown, 'String'); + subjectName = subjectList{get(subjectDropdown, 'Value')}; + expNum = str2num(get(edtExpNum, 'String')); + delete(f) + + end + + function cancel(~,~) + + subjectName = []; + expNum = []; + delete(f) + + end + + function pickExpNum(~,~) + + subjectList = get(subjectDropdown, 'String'); + subjectName = subjectList{get(subjectDropdown, 'Value')}; + try + expNumSuggestion = dat.findNextSeqNum(subjectName); + set(edtExpNum, 'String', num2str(expNumSuggestion)); + catch + end + + end +end \ No newline at end of file diff --git a/+hw/Timeline.m b/+hw/Timeline.m index f6f141e5..e084c8e0 100644 --- a/+hw/Timeline.m +++ b/+hw/Timeline.m @@ -522,7 +522,8 @@ function init(obj) inputSession.NotifyWhenDataAvailableExceeds = obj.DaqSamplesPerNotify; % when to process data obj.Sessions('main') = inputSession; for i = 1:length(use) - in = obj.Inputs(idx(i)); % get channel info, etc. + in = obj.Inputs(strcmp({obj.Inputs.name}, obj.UseInputs(i))); +% in = obj.Inputs(idx(i)); % get channel info, etc. fprintf(1, 'adding channel %s on %s\n', in.name, in.daqChannelID); switch in.measurement @@ -538,7 +539,7 @@ function init(obj) % we assume quadrature encoding (X4) for position measurement ch.EncoderType = 'X4'; end - obj.Inputs(idx(i)).arrayColumn = i; + obj.Inputs(strcmp({obj.Inputs.name}, obj.UseInputs(i))).arrayColumn = i; end end From 06a190c569a81ba497fbd66715bbc9f53852e4b3 Mon Sep 17 00:00:00 2001 From: nsteinme <nick.steinmetz@gmail.com> Date: Mon, 8 Jan 2018 17:35:03 +0000 Subject: [PATCH 011/393] remove empty fields when converting alyx instance to string --- +dat/parseAlyxInstance.m | 3 +++ 1 file changed, 3 insertions(+) diff --git a/+dat/parseAlyxInstance.m b/+dat/parseAlyxInstance.m index 4be14277..1fb53e93 100644 --- a/+dat/parseAlyxInstance.m +++ b/+dat/parseAlyxInstance.m @@ -22,6 +22,9 @@ if isfield(ai, 'water_requirement_remaining') ai = rmfield(ai, 'water_requirement_remaining'); end + fnai = fieldnames(ai); + ise = cellfun(@(fn)isempty(ai.(fn)), fnai); + if any(ise); ai = rmfield(ai, fnai(ise)); end; c = cellfun(@(fn) ai.(fn), fieldnames(ai), 'UniformOutput', false); % get fieldnames ref = strjoin([ref; c],'\'); % join into single string for UDP, otherwise just output the expRef end From 8643d422c5c0ef236eef29b35feb0de7aef3e271 Mon Sep 17 00:00:00 2001 From: nsteinme <nick.steinmetz@gmail.com> Date: Mon, 8 Jan 2018 17:39:13 +0000 Subject: [PATCH 012/393] update io.MpepDataHosts for sending alyx info --- +srv/expServer.m | 2 +- cortexlab/+io/MpepUDPDataHosts.m | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/+srv/expServer.m b/+srv/expServer.m index d954527c..e629a017 100644 --- a/+srv/expServer.m +++ b/+srv/expServer.m @@ -169,7 +169,7 @@ function handleMessage(id, data, host) end case 'run' % exp run request - [expRef, preDelay, postDelay, Alyx] = args{:}; + [expRef, preDelay, postDelay, Alyx] = args{:}; if dat.expExists(expRef) log('Starting experiment ''%s''', expRef); communicator.send(id, []); diff --git a/cortexlab/+io/MpepUDPDataHosts.m b/cortexlab/+io/MpepUDPDataHosts.m index 4b42cd20..821b215f 100644 --- a/cortexlab/+io/MpepUDPDataHosts.m +++ b/cortexlab/+io/MpepUDPDataHosts.m @@ -20,6 +20,7 @@ DigitalOutDaqChannelId Verbose = false % whether to output I/O messages etc Timeline % An instance of timeline for for recording UDP messages + AlyxInstance end properties (SetAccess = protected) @@ -170,7 +171,7 @@ function stimEnded(obj, num) msg = sprintf('StimEnd %s %d %d 1 %d', subject, seriesNum, expNum, num); broadcast(obj, msg); - if ~isempty(obj.Timeline)&&obj.Timeline.IsRunning + if ~isempty(obj.Timeline)&&isfield(obj.Timeline, 'IsRunning')&&obj.Timeline.IsRunning obj.Timeline.record('mpepUDP', msg); % record the UDP event in Timeline end dt = toc; @@ -200,9 +201,15 @@ function expEnded(obj) obj.ExpRef = []; end - function start(obj, expRef) + function start(obj, ref) + [expRef, ai] = dat.parseAlyxInstance(ref); + obj.AlyxInstance = ai; + [subject, seriesNum, expNum] = dat.expRefToMpep(obj.ExpRef); + alyxmsg = sprintf('alyx %s %d %d %s', subject, seriesNum, expNum, ref); + confirmedBroadcast(obj, alyxmsg); % equivalent to startExp(expRef) expStarted(obj, expRef); + end function stop(obj) @@ -230,7 +237,7 @@ function stop(obj) function confirmedBroadcast(obj, msg) broadcast(obj, msg); validateResponses(obj); - if ~isempty(obj.Timeline)&&obj.Timeline.IsRunning + if ~isempty(obj.Timeline)&&isfield(obj.Timeline, 'IsRunning')&&obj.Timeline.IsRunning obj.Timeline.record('mpepUDP', msg); % record the UDP event in Timeline end end From 96dd74b6b74929cc4d080aee047e47a26a4ab94a Mon Sep 17 00:00:00 2001 From: nsteinme <nick.steinmetz@gmail.com> Date: Mon, 8 Jan 2018 18:12:15 +0000 Subject: [PATCH 013/393] add other vis.checkers --- cortexlab/+vis/checker4.m | 126 ++++++++++++++++++++++++++++++++++++++ cortexlab/+vis/checker5.m | 126 ++++++++++++++++++++++++++++++++++++++ cortexlab/+vis/checker6.m | 126 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 378 insertions(+) create mode 100644 cortexlab/+vis/checker4.m create mode 100644 cortexlab/+vis/checker5.m create mode 100644 cortexlab/+vis/checker6.m diff --git a/cortexlab/+vis/checker4.m b/cortexlab/+vis/checker4.m new file mode 100644 index 00000000..be837609 --- /dev/null +++ b/cortexlab/+vis/checker4.m @@ -0,0 +1,126 @@ +function elem = checker3(t) +%vis.checker A grid of rectangles +% Detailed explanation goes here + +elem = t.Node.Net.subscriptableOrigin('checker'); + +%% make initial layers to be used as templates +maskTemplate = vis.emptyLayer(); +maskTemplate.isPeriodic = false; +maskTemplate.interpolation = 'nearest'; +maskTemplate.show = true; +maskTemplate.colourMask = [false false false true]; + +maskTemplate.textureId = 'checkerMaskPixel'; +[maskTemplate.rgba, maskTemplate.rgbaSize] = vis.rgba(0, 0); +maskTemplate.blending = '1-source'; % allows us to lay down our zero alpha value + +stencilTemplate = maskTemplate; +stencilTemplate.textureId = 'checkerStencilPixel'; +[stencilTemplate.rgba, stencilTemplate.rgbaSize] = vis.rgba(1, 1); +stencilTemplate.blending = 'none'; + +% pattern layer uses the alpha values laid down by mask layers +patternLayer = vis.emptyLayer(); +patternLayer.textureId = sprintf('~checker%i', randi(2^32)); +patternLayer.isPeriodic = false; +patternLayer.interpolation = 'nearest'; +patternLayer.blending = 'destination'; % use the alpha mask gets laid down before this + +%% construct signals used to assemble layers +% N rows by cols signal is derived from the size of the pattern array but +% we skip repeats so that pattern changes don't update the mask layers +% unless the size has acutally changed +nRowsByCols = elem.pattern.flatten().map(@size).skipRepeats(); +aziRange = elem.azimuthRange.flatten(); +altRange = elem.altitudeRange.flatten(); +sizeFrac = elem.rectSizeFrac.flatten(); +% signal containing the masking layers +gridMaskLayers = mapn(nRowsByCols, aziRange, altRange, sizeFrac, ... + maskTemplate, stencilTemplate, @gridMask); +% signal contain the checker layer +checkerLayer = scan(elem.pattern.flatten(), @updatePattern,... + elem.colour.flatten(), @updateColour,... + elem.azimuthRange.flatten(), @updateAzi,... + elem.altitudeRange.flatten(), @updateAlt,... + elem.show.flatten(), @updateShow,... + patternLayer); % initial value +%% set default attribute values +elem.layers = [gridMaskLayers checkerLayer]; +elem.azimuthRange = [-132 132]; +elem.altitudeRange = [-36 36]; +elem.rectSizeFrac = [1 1]; % horizontal and vertical size of each rectangle +elem.pattern = [ + 1 -1 1 -1 + -1 0 0 0 + 1 0 0 0 + -1 1 -1 1]; + elem.show = true; +end + +%% helper functions +function layer = updatePattern(layer, pattern) +% map pattern from -1 -> 1 range to 0->255, cast to 8 bit integers, then +% convert to RGBA texture format. +[layer.rgba, layer.rgbaSize] = vis.rgbaFromUint8(uint8(127.5*(1 + pattern)), 1); +end + +function layer = updateColour(layer, colour) +layer.maxColour = [colour 1]; +end + +function layer = updateAzi(layer, aziRange) +layer.size(1) = abs(diff(aziRange)); +layer.texOffset(1) = mean(aziRange); +end + +function layer = updateAlt(layer, altRange) +layer.size(2) = abs(diff(altRange)); +layer.texOffset(2) = mean(altRange); +end + +function layer = updateShow(layer, show) +layer.show = show; +end + +function layers = gridMask(nRowsByCols, aziRange, altRange, sizeFrac, mask, stencil) +gridDims = [abs(diff(aziRange)) abs(diff(altRange))]; +cellSize = gridDims./flip(nRowsByCols); +nCols = nRowsByCols(2) + 1; +nRows = nRowsByCols(1) + 1; +midAzi = mean(aziRange); +midAlt = mean(altRange); +%% base layer to imprint area the checker can draw on (by applying an alpha mask) +stencil.texOffset = [midAzi midAlt]; +stencil.size = gridDims; +if any(sizeFrac < 1) + %% layers for lines making up mask grid - masks out margins around each square + % make layers for vertical lines + if nCols > 1 + azi = linspace(aziRange(1), aziRange(2), nCols); + else + azi = midAzi; + end + collayers = repmat(mask, 1, nCols); + for vi = 1:nCols + collayers(vi).texOffset = [azi(vi) midAlt]; + end + [collayers.size] = deal([(1 - sizeFrac(1))*cellSize(1) gridDims(2)]); + % make layers for horizontal lines + if nRows > 1 + alt = linspace(altRange(1), altRange(2), nRows); + else + alt = midAlt; + end + rowlayers = repmat(mask, 1, nRows); + for hi = 1:nRows + rowlayers(hi).texOffset = [midAzi alt(hi)]; + end + [rowlayers.size] = deal([gridDims(1) (1 - sizeFrac(2))*cellSize(2)]); + %% combine the layers and return + layers = [stencil collayers rowlayers]; +else % no mask grid needed as each cell is full size + layers = stencil; +end + +end \ No newline at end of file diff --git a/cortexlab/+vis/checker5.m b/cortexlab/+vis/checker5.m new file mode 100644 index 00000000..be37a1f4 --- /dev/null +++ b/cortexlab/+vis/checker5.m @@ -0,0 +1,126 @@ +function elem = checker5(t) +%vis.checker A grid of rectangles +% Detailed explanation goes here + +elem = t.Node.Net.subscriptableOrigin('checker'); + +%% make initial layers to be used as templates +maskTemplate = vis.emptyLayer(); +maskTemplate.isPeriodic = false; +maskTemplate.interpolation = 'nearest'; +maskTemplate.show = true; +maskTemplate.colourMask = [false false false true]; + +maskTemplate.textureId = 'checkerMaskPixel'; +[maskTemplate.rgba, maskTemplate.rgbaSize] = vis.rgba(0, 0); +maskTemplate.blending = '1-source'; % allows us to lay down our zero alpha value + +stencilTemplate = maskTemplate; +stencilTemplate.textureId = 'checkerStencilPixel'; +[stencilTemplate.rgba, stencilTemplate.rgbaSize] = vis.rgba(1, 1); +stencilTemplate.blending = 'none'; + +% pattern layer uses the alpha values laid down by mask layers +patternLayer = vis.emptyLayer(); +patternLayer.textureId = sprintf('~checker%i', randi(2^32)); +patternLayer.isPeriodic = false; +patternLayer.interpolation = 'nearest'; +patternLayer.blending = 'destination'; % use the alpha mask gets laid down before this + +%% construct signals used to assemble layers +% N rows by cols signal is derived from the size of the pattern array but +% we skip repeats so that pattern changes don't update the mask layers +% unless the size has acutally changed +nRowsByCols = elem.pattern.flatten().map(@size).skipRepeats(); +aziRange = elem.azimuthRange.flatten(); +altRange = elem.altitudeRange.flatten(); +sizeFrac = elem.rectSizeFrac.flatten(); +% signal containing the masking layers +gridMaskLayers = mapn(nRowsByCols, aziRange, altRange, sizeFrac, ... + maskTemplate, stencilTemplate, @gridMask); +% signal contain the checker layer +checkerLayer = scan(elem.pattern.flatten(), @updatePattern,... + elem.colour.flatten(), @updateColour,... + elem.azimuthRange.flatten(), @updateAzi,... + elem.altitudeRange.flatten(), @updateAlt,... + elem.show.flatten(), @updateShow,... + patternLayer); % initial value +%% set default attribute values +elem.layers = [gridMaskLayers checkerLayer]; +elem.azimuthRange = [-132 132]; +elem.altitudeRange = [-36 36]; +elem.rectSizeFrac = [1 1]; % horizontal and vertical size of each rectangle +elem.pattern = [ + 1 -1 1 -1 + -1 0 0 0 + 1 0 0 0 + -1 1 -1 1]; + elem.show = true; +end + +%% helper functions +function layer = updatePattern(layer, pattern) +% map pattern from -1 -> 1 range to 0->255, cast to 8 bit integers, then +% convert to RGBA texture format. +[layer.rgba, layer.rgbaSize] = vis.rgbaFromUint8_PC(uint8(127.5*(1 + pattern)), 1); +end + +function layer = updateColour(layer, colour) +layer.maxColour = [colour 1]; +end + +function layer = updateAzi(layer, aziRange) +layer.size(1) = abs(diff(aziRange)); +layer.texOffset(1) = mean(aziRange); +end + +function layer = updateAlt(layer, altRange) +layer.size(2) = abs(diff(altRange)); +layer.texOffset(2) = mean(altRange); +end + +function layer = updateShow(layer, show) +layer.show = show; +end + +function layers = gridMask(nRowsByCols, aziRange, altRange, sizeFrac, mask, stencil) +gridDims = [abs(diff(aziRange)) abs(diff(altRange))]; +cellSize = gridDims./fliplr(nRowsByCols); +nCols = nRowsByCols(2) + 1; +nRows = nRowsByCols(1) + 1; +midAzi = mean(aziRange); +midAlt = mean(altRange); +%% base layer to imprint area the checker can draw on (by applying an alpha mask) +stencil.texOffset = [midAzi midAlt]; +stencil.size = gridDims; +if any(sizeFrac < 1) + %% layers for lines making up mask grid - masks out margins around each square + % make layers for vertical lines + if nCols > 1 + azi = linspace(aziRange(1), aziRange(2), nCols); + else + azi = midAzi; + end + collayers = repmat(mask, 1, nCols); + for vi = 1:nCols + collayers(vi).texOffset = [azi(vi) midAlt]; + end + [collayers.size] = deal([(1 - sizeFrac(1))*cellSize(1) gridDims(2)]); + % make layers for horizontal lines + if nRows > 1 + alt = linspace(altRange(1), altRange(2), nRows); + else + alt = midAlt; + end + rowlayers = repmat(mask, 1, nRows); + for hi = 1:nRows + rowlayers(hi).texOffset = [midAzi alt(hi)]; + end + [rowlayers.size] = deal([gridDims(1) (1 - sizeFrac(2))*cellSize(2)]); + %% combine the layers and return + layers = [stencil collayers rowlayers]; +else % no mask grid needed as each cell is full size + layers = stencil; +end + +end \ No newline at end of file diff --git a/cortexlab/+vis/checker6.m b/cortexlab/+vis/checker6.m new file mode 100644 index 00000000..4901b88c --- /dev/null +++ b/cortexlab/+vis/checker6.m @@ -0,0 +1,126 @@ +function elem = checker3(t) +%vis.checker A grid of rectangles +% Detailed explanation goes here + +elem = t.Node.Net.subscriptableOrigin('checker'); + +%% make initial layers to be used as templates +maskTemplate = vis.emptyLayer(); +maskTemplate.isPeriodic = false; +maskTemplate.interpolation = 'nearest'; +maskTemplate.show = true; +maskTemplate.colourMask = [false false false true]; + +maskTemplate.textureId = 'checkerMaskPixel'; +[maskTemplate.rgba, maskTemplate.rgbaSize] = vis.rgba(0, 0); +maskTemplate.blending = '1-source'; % allows us to lay down our zero alpha value + +stencilTemplate = maskTemplate; +stencilTemplate.textureId = 'checkerStencilPixel'; +[stencilTemplate.rgba, stencilTemplate.rgbaSize] = vis.rgba(1, 1); +stencilTemplate.blending = 'none'; + +% pattern layer uses the alpha values laid down by mask layers +patternLayer = vis.emptyLayer(); +patternLayer.textureId = sprintf('~checker%i', randi(2^32)); +patternLayer.isPeriodic = false; +patternLayer.interpolation = 'nearest'; +patternLayer.blending = 'destination'; % use the alpha mask gets laid down before this + +%% construct signals used to assemble layers +% N rows by cols signal is derived from the size of the pattern array but +% we skip repeats so that pattern changes don't update the mask layers +% unless the size has acutally changed +nRowsByCols = elem.pattern.flatten().map(@size).skipRepeats(); +aziRange = elem.azimuthRange.flatten(); +altRange = elem.altitudeRange.flatten(); +sizeFrac = elem.rectSizeFrac.flatten(); +% signal containing the masking layers +gridMaskLayers = mapn(nRowsByCols, aziRange, altRange, sizeFrac, ... + maskTemplate, stencilTemplate, @gridMask); +% signal contain the checker layer +checkerLayer = scan(elem.pattern.flatten(), @updatePattern,... + elem.colour.flatten(), @updateColour,... + elem.azimuthRange.flatten(), @updateAzi,... + elem.altitudeRange.flatten(), @updateAlt,... + elem.show.flatten(), @updateShow,... + patternLayer); % initial value +%% set default attribute values +elem.layers = [gridMaskLayers checkerLayer]; +elem.azimuthRange = [-135 135]; +elem.altitudeRange = [-37.5 37.5]; +elem.rectSizeFrac = [1 1]; % horizontal and vertical size of each rectangle +elem.pattern = [ + 1 -1 1 -1 + -1 0 0 0 + 1 0 0 0 + -1 1 -1 1]; + elem.show = true; +end + +%% helper functions +function layer = updatePattern(layer, pattern) +% map pattern from -1 -> 1 range to 0->255, cast to 8 bit integers, then +% convert to RGBA texture format. +[layer.rgba, layer.rgbaSize] = vis.rgbaFromUint8(uint8(127.5*(1 + pattern)), 1); +end + +function layer = updateColour(layer, colour) +layer.maxColour = [colour 1]; +end + +function layer = updateAzi(layer, aziRange) +layer.size(1) = abs(diff(aziRange)); +layer.texOffset(1) = mean(aziRange); +end + +function layer = updateAlt(layer, altRange) +layer.size(2) = abs(diff(altRange)); +layer.texOffset(2) = mean(altRange); +end + +function layer = updateShow(layer, show) +layer.show = show; +end + +function layers = gridMask(nRowsByCols, aziRange, altRange, sizeFrac, mask, stencil) +gridDims = [abs(diff(aziRange)) abs(diff(altRange))]; +cellSize = gridDims./flip(nRowsByCols); +nCols = nRowsByCols(2) + 1; +nRows = nRowsByCols(1) + 1; +midAzi = mean(aziRange); +midAlt = mean(altRange); +%% base layer to imprint area the checker can draw on (by applying an alpha mask) +stencil.texOffset = [midAzi midAlt]; +stencil.size = gridDims; +if any(sizeFrac < 1) + %% layers for lines making up mask grid - masks out margins around each square + % make layers for vertical lines + if nCols > 1 + azi = linspace(aziRange(1), aziRange(2), nCols); + else + azi = midAzi; + end + collayers = repmat(mask, 1, nCols); + for vi = 1:nCols + collayers(vi).texOffset = [azi(vi) midAlt]; + end + [collayers.size] = deal([(1 - sizeFrac(1))*cellSize(1) gridDims(2)]); + % make layers for horizontal lines + if nRows > 1 + alt = linspace(altRange(1), altRange(2), nRows); + else + alt = midAlt; + end + rowlayers = repmat(mask, 1, nRows); + for hi = 1:nRows + rowlayers(hi).texOffset = [midAzi alt(hi)]; + end + [rowlayers.size] = deal([gridDims(1) (1 - sizeFrac(2))*cellSize(2)]); + %% combine the layers and return + layers = [stencil collayers rowlayers]; +else % no mask grid needed as each cell is full size + layers = stencil; +end + +end \ No newline at end of file From 84c9a2e3218f5bd79cb6fbe0c3d0b5b041d6775f Mon Sep 17 00:00:00 2001 From: nsteinme <nick.steinmetz@gmail.com> Date: Mon, 8 Jan 2018 20:02:36 +0000 Subject: [PATCH 014/393] update tlserver to send alyx info by websockets --- cortexlab/+tl/bindMpepServerWithWS.m | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/cortexlab/+tl/bindMpepServerWithWS.m b/cortexlab/+tl/bindMpepServerWithWS.m index 187583cf..b6401089 100644 --- a/cortexlab/+tl/bindMpepServerWithWS.m +++ b/cortexlab/+tl/bindMpepServerWithWS.m @@ -37,6 +37,7 @@ tls.close = @closeConns; tls.process = @process; tls.listen = @listen; +tls.AlyxInstance = []; listenPort = io.WSJCommunicator.DefaultListenPort; communicator = io.WSJCommunicator.server(listenPort); @@ -82,20 +83,26 @@ function processMpep(listener, msg) ipstr = sprintf('%i.%i.%i.%i', ip{:}); log('%s: ''%s'' from %s:%i', listener.name, msg, ipstr, port); % parse the message - info = dat.mpepMessageParse(msg); - - % !!! Get alyx instance here!! - ai = []; + info = dat.mpepMessageParse(msg); failed = false; % flag for preventing UDP echo %% Experiment-level events start/stop timeline switch lower(info.instruction) + case 'alyx' + fprintf(1, 'received alyx token message\n'); + idx = find(msg==' ', 1, 'last'); + [expref, ai] = dat.parseAlyxInstance(msg(idx+1:end)); + disp(ai) + + tls.AlyxInstance = ai; case 'expstart' % create a file path & experiment ref based on experiment info try % start Timeline + communicator.send('AlyxSend', {tls.AlyxInstance}); communicator.send('status', { 'starting', info.expRef}); - tlObj.start(info.expRef, ai); + + tlObj.start(info.expRef, tls.AlyxInstance); % re-record the UDP event in Timeline since it wasn't started % when we tried earlier. Treat it as having arrived at time zero. tlObj.record('mpepUDP', msg, 0); From 566884dd8b79cc05f829f551061864cdf9117579 Mon Sep 17 00:00:00 2001 From: nsteinme <nick.steinmetz@gmail.com> Date: Tue, 9 Jan 2018 11:58:02 +0000 Subject: [PATCH 015/393] add docs --- +dat/findNextSeqNum.m | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/+dat/findNextSeqNum.m b/+dat/findNextSeqNum.m index 45450073..f5f40796 100644 --- a/+dat/findNextSeqNum.m +++ b/+dat/findNextSeqNum.m @@ -1,9 +1,14 @@ function expSeq = findNextSeqNum(subject, varargin) -% function expSeq = findNextSeqNum(subject[, date]) +% expSeq = findNextSeqNum(subject[, date]) +% +% Returns the next experiment number (aka Sequence number) that should be +% chosen for the given subject. Optionally specify a particular date to +% consider. + if isempty(varargin) - expDate = now; + expDate = now; %default to today else expDate = varargin{1}; end From 6e274b15029924cab9e64b16800d074ab51ae796 Mon Sep 17 00:00:00 2001 From: nsteinme <nick.steinmetz@gmail.com> Date: Tue, 9 Jan 2018 12:20:39 +0000 Subject: [PATCH 016/393] move whichExpNums to cortexlab, update dat.listSubjects and .subjectSelector, add docs --- +dat/listSubjects.m | 44 ++++++++++++++++++++----- +dat/subjectSelector.m | 22 +++++-------- {+dat => cortexlab/+dat}/whichExpNums.m | 23 +++++++++++-- 3 files changed, 63 insertions(+), 26 deletions(-) rename {+dat => cortexlab/+dat}/whichExpNums.m (63%) diff --git a/+dat/listSubjects.m b/+dat/listSubjects.m index f3aff6ec..dbac4793 100644 --- a/+dat/listSubjects.m +++ b/+dat/listSubjects.m @@ -1,17 +1,43 @@ -function subjects = listSubjects() +function subjects = listSubjects(varargin) %DAT.LISTSUBJECTS Lists recorded subjects -% subjects = DAT.LISTSUBJECTS() Lists the experimental subjects present +% subjects = DAT.LISTSUBJECTS([alyxInstance]) Lists the experimental subjects present % in experiment info repository ('expInfo'). % +% Optional input argument of an alyx instance will enable generating this +% list from alyx rather than from the directory structure on zserver +% % Part of Rigbox % 2013-03 CB created +% 2018-01 NS added alyx compatibility -% The master 'expInfo' repository is the reference for the existence of -% experiments, as given by the folder structure -expInfoPath = dat.reposPath('expInfo', 'master'); - -dirs = file.list(expInfoPath, 'dirs'); -subjects = setdiff(dirs, {'misc'}); %exclude the misc directory - +if nargin>0 && ~isempty(varargin{1}) % user provided an alyx instance + ai = varargin{1}; % an alyx instance + + % get list of all living, non-stock mice from alyx + s = alyx.getData(ai, 'subjects?stock=False&alive=True'); + + % determine the user for each mouse + respUser = cellfun(@(x)x.responsible_user, s, 'uni', false); + + % get cell array of subject names + subjNames = cellfun(@(x)x.nickname, s, 'uni', false); + + % determine which subjects belong to this user + thisUserSubs = sort(subjNames(strcmp(respUser, ai.username))); + + % all the subjects + otherUserSubs = sort(subjNames(~strcmp(respUser, ai.username))); + + % the full, ordered list + subjects = [{'default'}, thisUserSubs, otherUserSubs]'; +else + + % The master 'expInfo' repository is the reference for the existence of + % experiments, as given by the folder structure + expInfoPath = dat.reposPath('expInfo', 'master'); + + dirs = file.list(expInfoPath, 'dirs'); + subjects = setdiff(dirs, {'misc'}); %exclude the misc directory +end end \ No newline at end of file diff --git a/+dat/subjectSelector.m b/+dat/subjectSelector.m index 8437dad5..23df89b9 100644 --- a/+dat/subjectSelector.m +++ b/+dat/subjectSelector.m @@ -4,6 +4,12 @@ % % If you provide an alyxInstance, it will populate with a list of subjects % from alyx; otherwise, from dat.listSubjects +% +% example usage: +% >> alyxInstance = alyx.loginWindow(); +% >> [subj, expNum] = subjectSelector([], alyxInstance); +% +% Created by NS 2017 subjectName = []; expNum = 1; @@ -37,20 +43,8 @@ if nargin>1 ai = varargin{2}; - - s = alyx.getData(ai, 'subjects?stock=False&alive=True'); - - respUser = cellfun(@(x)x.responsible_user, s, 'uni', false); - subjNames = cellfun(@(x)x.nickname, s, 'uni', false); - - thisUserSubs = sort(subjNames(strcmp(respUser, ai.username))); - otherUserSubs = sort(subjNames); - % note that we leave this User's mice also in - % otherUserSubs, in case they get confused and look - % there. - - newSubs = [{'default'}, thisUserSubs, otherUserSubs]; - set(subjectDropdown, 'String', newSubs); + + set(subjectDropdown, 'String', dat.listSubjects(ai)); else set(subjectDropdown, 'String', dat.listSubjects); end diff --git a/+dat/whichExpNums.m b/cortexlab/+dat/whichExpNums.m similarity index 63% rename from +dat/whichExpNums.m rename to cortexlab/+dat/whichExpNums.m index 2cc4f05d..68127da9 100644 --- a/+dat/whichExpNums.m +++ b/cortexlab/+dat/whichExpNums.m @@ -1,6 +1,22 @@ function [expNums, blocks, hasBlock, pars, isMpep, tl, hasTimeline] = ... whichExpNums(mouseName, thisDate) - +% [expNums, blocks, hasBlock, pars, isMpep, tl, hasTimeline] = ... +% whichExpNums(mouseName, thisDate) +% +% Attempt to automatically determine what experiments of what types were +% run for a subject on a given date. +% +% Returns: +% - expNums - list of the experiment numbers that exist +% - blocks - cell array of the Block structs +% - hasBlock - boolean array indicating whether each experiment had a block +% - pars - cell array of parameters structs +% - isMpep - boolean array of whether the experiment was mpep type +% - tl - cell array of Timeline structs +% - hasTimeline - boolean array of whether timeline was present for each +% experiment +% +% Created by NS 2017 rootExp = dat.expFilePath(mouseName, thisDate, 1, 'Timeline', 'master'); expInf = fileparts(fileparts(rootExp)); @@ -26,6 +42,8 @@ hasBlock(e) = true; end + % if there is a parameters file, load it and determine whether it is + % mpep type dPars = dat.expFilePath(mouseName, thisDate, expNums(e), 'parameters', 'master'); if exist(dPars) load(dPars) @@ -37,8 +55,7 @@ end - % if there is a timeline, load it and get photodiode events, mpep UDP - % events. + % if there is a timeline, load it dTL = dat.expFilePath(mouseName, thisDate, expNums(e), 'Timeline', 'master'); if exist(dTL) fprintf(1, 'expNum %d has timeline\n', e); From 2c2c57f5ba82a8da7b1595a5ea7f49e171f1561f Mon Sep 17 00:00:00 2001 From: nsteinme <nick.steinmetz@gmail.com> Date: Wed, 10 Jan 2018 10:41:41 +0000 Subject: [PATCH 017/393] update eyetracking path --- +dat/paths.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/+dat/paths.m b/+dat/paths.m index 89539761..dc0997ca 100644 --- a/+dat/paths.m +++ b/+dat/paths.m @@ -37,7 +37,8 @@ % for calcium widefield imaging p.widefieldRepository = fullfile(server1Name, 'data', 'GCAMP'); % Repository for storing eye tracking movies -p.eyeTrackingRepository = fullfile(server1Name, 'data', 'EyeCamera'); +% p.eyeTrackingRepository = fullfile(server1Name, 'data', 'EyeCamera'); +p.eyeTrackingRepository = p.mainRepository; % electrophys repositories p.lfpRepository = fullfile(server1Name, 'Data', 'Cerebus'); From fd9e21f44e6f9395dc25bdbbc8ad559006602e13 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 10 Jan 2018 13:46:49 +0000 Subject: [PATCH 018/393] Updated readme Readme is now a little more thorough and better formatted --- +dat/parseAlyxInstance.m | 6 +- +srv/RemoteMPEPService.m | 303 ----------------------------- +srv/expServer.m | 2 +- cortexlab/+srv/RemoteMPEPService.m | 263 +++++++++++++++---------- readme.txt | 102 ++++++++-- 5 files changed, 251 insertions(+), 425 deletions(-) delete mode 100644 +srv/RemoteMPEPService.m diff --git a/+dat/parseAlyxInstance.m b/+dat/parseAlyxInstance.m index 1fb53e93..2eb09711 100644 --- a/+dat/parseAlyxInstance.m +++ b/+dat/parseAlyxInstance.m @@ -22,9 +22,9 @@ if isfield(ai, 'water_requirement_remaining') ai = rmfield(ai, 'water_requirement_remaining'); end - fnai = fieldnames(ai); - ise = cellfun(@(fn)isempty(ai.(fn)), fnai); - if any(ise); ai = rmfield(ai, fnai(ise)); end; + fname = fieldnames(ai); % get fieldnames + emp = structfun(@isempty, ai); % find empty fields + if any(emp); ai = rmfield(ai, fname(emp)); end % remove the empty fields c = cellfun(@(fn) ai.(fn), fieldnames(ai), 'UniformOutput', false); % get fieldnames ref = strjoin([ref; c],'\'); % join into single string for UDP, otherwise just output the expRef end diff --git a/+srv/RemoteMPEPService.m b/+srv/RemoteMPEPService.m deleted file mode 100644 index 01e86853..00000000 --- a/+srv/RemoteMPEPService.m +++ /dev/null @@ -1,303 +0,0 @@ -classdef RemoteMPEPService < srv.Service - %SRV.REMOTETLSERVICE UDP-based service for starting and stopping Timeline - % A UDP interface that uses the udp function of the Instument Control - % Toolbox. Unlike SRV.PRIMITIVEUDPSERVICE, this can send and recieve - % messages asynchronously and can be used both to start remote services - % and, service side, to listen for remote start/stop commands. - % - % To send a message simply use sentUDP(msg). Use confirmedSend(msg) to - % send send a message and await a confirmation (the same message echoed - % back). To receive messaged only, simply use bind() and add a - % listener to the MessageReceived event. - % - % Examples: - % remoteTL = srv.BasicUDPService('tl-host', 10000, 10000); - % remoteTL.start('2017-10-27-1-default'); % Start remote service with - % an experiment reference - % remoteTL.stop; remoteTL.delete; % Clean up after stopping remote - % rig - % - % experimentRig = srv.BasicUDPService('mainRigHostName', 10000, 10000); - % experimentRig.bind(); % Connect to the remote rig - % remoteStatus = requestStatus(experimentRig); % Get the status of - % the experimental rig - % lh = events.listener(experimentRig, 'MessageReceived', - % @(srv, evt)processMessage(srv, evt)); % Add a listener to do - % something when a message is received. - % - % See also SRV.PRIMITIVEUDPSERVICE, UDP. - % - % Part of Rigbox - - % 2017-10 MW created - - properties (GetObservable, SetAccess = protected) - Status % Status of remote service - end - - properties - LocalStatus % Local status to send upon request - ResponseTimeout = Inf % How long to wait for confirmation of receipt - Timeline % Holds an instance of Timeline - Callbacks % Holds callback functions for each instruction - end - - properties (SetObservable, AbortSet = true) - RemoteHost % Host name of the remote service - ListenPort = 10000 % Localhost port number to listen for messages on - RemotePort = 10000 % Which port to send messages to remote service on - EnablePortSharing = 'off' % If set to 'on' other applications can use the listen port - end - - properties (SetAccess = protected) - RemoteIP % The IP address of the remote service - Socket % A handle to the udp object - LastSentMessage = '' % A copy of the message sent from this host - LastReceivedMessage = '' % A copy of the message received by this host - end - - properties (Access = private) - Listener % A listener for the MessageReceived event - ResponseTimer % A timer object set when expecting a confirmation message (if ResponseTimeout < Inf) - AwaitingConfirmation = false % True when awaiting a confirmation message - ConfirmID % A random integer to confirm UDP status response. See requestStatus() - end - - events (NotifyAccess = 'protected') - MessageReceived % Notified by receiveUDP() when a UDP message is received - end - - methods - function delete(obj) - % To be called before destroying BasicUDPService object. Deletes all - % timers, sockets and listeners Tidy up after ourselves by closing - % the listening sockets - if ~isempty(obj.Socket) - fclose(obj.Socket); % Close the connection - delete(obj.Socket); % Delete the socket - obj.Socket = []; % Delete udp object - obj.Listener = []; % Delete any listeners to that object - if ~isempty(obj.ResponseTimer) % If there is a timer object - stop(obj.ResponseTimer) % Stop the timer.. - delete(obj.ResponseTimer) % Delete the timer... - obj.ResponseTimer = []; % ... and remove it - end - end - end - - function obj = RemoteMPEPService(remoteHost, remotePort, listenPort) - % SRV.REMOTETLSERVICE([remoteHost, remotePort, listenPort]) - % remoteHost is the hostname of the service with which to send and - % receive messages. - paths = dat.paths(hostname); % Get list of paths for timeline - obj.Callbacks = struct('Instruction', {'ExpStart', 'BlockStart',... - 'StimStart', 'StimEnd', 'BlockEnd', 'ExpEnd', 'ExpInterrupt'}, 'Callback', @nop); - obj.Timeline = load(fullfile(paths.rigConfig, 'hardware.mat'), 'timeline'); % Load timeline object - if nargin < 1; remoteHost = ''; end % Set local port - obj.RemoteHost = remoteHost; % Set hostname - obj.RemoteIP = ipaddress(remoteHost); % Get IP address - if nargin >= 3; obj.ListenPort = listenPort; end % Set local port - if nargin >= 2; obj.RemotePort = remotePort; end % Set remote port - obj.Socket = udp(obj.RemoteIP,... % Create udp object - 'RemotePort', obj.RemotePort, 'LocalPort', obj.ListenPort); - obj.Socket.ReadAsyncMode = 'continuous'; - obj.Socket.BytesAvailableFcnCount = 10; % Number of bytes in buffer required to trigger BytesAvailableFcn - obj.Socket.BytesAvailableFcn = @obj.receiveUDP; % Add callback to receiveUDP when enough bytes arrive in the buffer - % Add listener to MessageReceived event, notified when receiveUDP is - % called. This event can be listened to by anyone. - obj.Listener = event.listener(obj, 'MessageReceived', @processMsg); - % Add listener for when the observable properties are set - obj.addlistener({'RemoteHost', 'ListenPort', 'RemotePort', 'EnablePortSharing'},... - 'PostSet',@(src,~)obj.update(src)); - % Add listener for when the remote service's status is requested - obj.addlistener('Status', 'PreGet', @obj.requestStatus); - end - - function obj = addListener(obj, name, listenPort, callback) - if nargin<3; callback = @nop; end - if listenPort==obj.ListenPorts - error('Listen port already added'); - end - obj.Sockets(end+1) = udp(obj.RemoteIP, 'RemotePort', obj.RemotePort, 'LocalPort', listenPort); - obj.Sockets(end).BytesAvailableFcn = @(~,~)obj.receiveUDP(src,evt); - obj.Sockets(end).Tag = name; - obj.ListenPorts(end+1) = listenPort; - obj.Callbacks(end+1) = callback; - end - - function update(obj, src) - % Callback for setting udp relevant properties. Some properties can - % only be set when the socket is closed. - % Check if socket is open - isOpen = strcmp(obj.Socket.Status, 'open'); - % Close connection before setting, if required to do so - if any(strcmp(src.name, {'RemoteHost', 'LocalPort', 'EnablePortSharing'}))&&isOpen - fclose(obj.Socket); - end - % Set all the relevant properties - obj.RemoteIP = ipaddress(obj.RemoteHost); - obj.Socket.LocalPort = obj.ListenPort; - obj.Socket.RemotePort = obj.RemotePort; - obj.Socket.RemoteHost = obj.RemoteIP; - if isOpen; bind(obj); end % If socket was open before, re-open - end - - function bind(obj, names) - if isempty(obj.Sockets) - warning('No sockets to bind') - return - end - if nargin<2 - % Close all sockets, in case they are open - arrayfun(@fclose, obj.Sockets) - % Open the connection to allow messages to be sent and received - arrayfun(@fopen, obj.Sockets) - else - names = ensureCell(names); - hosts = arrayfun(@(s)s.Tag, obj.Sockets); - idx = cellfun(@(n)find(strcmp(n,hosts)), names); - arrayfun(@fopen, obj.Sockets(idx)) - end - end - - function start(obj, ref) - % Send start message to remotehost and await confirmation - [expRef, AlyxInstance] = parseAlyxInstance(ref); - % Convert expRef to MPEP style - [subject, seriesNum, expNum] = dat.expRefToMpep(expRef); - % Build start message - msg = sprintf('ExpStart %s %d %d', subject, seriesNum, expNum); - % Send the start message - obj.confirmedSend(msg, obj.RemoteHost); - % Wait for response - while obj.AwaitingConfirmation; pause(0.2); end -% % Start a block (we only use one per experiment) -% msg = sprintf('BlockStart %s %d %d 1', subject, seriesNum, expNum); -% obj.confirmedSend(msg, obj.RemoteHost); -% % Wait for response -% while obj.AwaitingConfirmation; pause(0.2); end - end - - function stop(obj) - % Send stop message to remotehost and await confirmation - obj.confirmedSend(sprintf('STOP*%s', obj.RemoteHost)); - end - - function requestStatus(obj) - % Request a status update from the remote service - obj.ConfirmID = randi(1e6); - obj.sendUDP(sprintf('WHAT%i*%s', obj.ConfirmID, obj.RemoteHost)); - disp('Requested status update from remote service') - end - - function confirmedSend(obj, msg) - sendUDP(obj, msg) - obj.AwaitingConfirmation = true; - % Add timer to impose a response timeout - if ~isinf(obj.ResponseTimeout) - obj.ResponseTimer = timer('StartDelay', obj.ResponseTimout,... - 'TimerFcn', @(~,~)obj.processMsg); - start(obj.ResponseTimer) % start the timer - end - end - - function receiveUDP(obj, src, evt) - obj.LastReceivedMessage = strtrim(fscanf(obj.Socket)); - notify(obj, 'MessageReceived') - hosts = arrayfun(@(s)s.Tag, obj.Sockets); - feval(obj.Callbacks{strcmp(hosts, src.Tag)}, src, evt); - end - - function sendUDP(obj, msg) - % Ensure socket is open before sending message - if strcmp(obj.Socket.Status, 'closed'); bind(obj); end - fprintf(obj.Socket, msg); % Send message - obj.LastSentMessage = msg; % Save a copy of the message - disp(['Sent message to ' obj.RemoteHost]) % Display success - end - - function echo(obj, src, ~) - % Echo message - fclose(src); - src.RemoteHost = src.DatagramAddress; - src.RemotePort = src.DatagramPort; - fopen(src); - fprintf(obj.Socket, obj.LastReceivedMessage); % Send message - obj.LastSentMessage = obj.LastReceivedMessage; % Save a copy of the message - disp(['Echo''d message to ' src.Tag]) % Display success - end - end - - methods (Access = protected) - function processMPEPMsg(obj, src, ~) - % Parse the message into its constituent parts - msg = dat.mpepMessageParse(obj.LastReceivedMessage); - % Check that the message was from the correct host, otherwise ignore - if strcmp(response.host, obj.RemoteHost) - warning('Received message from %s, ignoring', response.host); - return - end - if obj.AwaitingConfirmation - % Check the confirmation message is the same as the sent message - assert(~isempty(response)||... % something received - strcmp(response.status, 'WHAT')||... % status update - strcmp(obj.LastReceivedMessage, obj.LastSentMessage),... % is echo - 'Confirmation failed') - % We no longer need the timer, stop and delete it - if ~isempty(obj.ResponseTimer) - stop(obj.ResponseTimer) - delete(obj.ResponseTimer) - obj.ResponseTimer = []; - end - end - % At the moment we just disply some stuff, other functions listening - % to the MessageReceived event can do their thing - switch response.status - case 'GOGO' - if obj.AwaitingConfirmation - obj.Status = 'running'; - disp(['Service on ' obj.RemoteHost ' running']) - else - disp('Received start request') - obj.LocalStatus = 'starting'; - obj.Timeline.start(dat.parseAlyxInstance(response.body)) - obj.LocalStatus = 'running'; - obj.sendUDP(obj.LastReceivedMessage) - end - case 'STOP' - if obj.AwaitingConfirmation - obj.Status = 'stopped'; - disp(['Service on ' obj.RemoteHost ' stopped']) - else - disp('Received stop request') - obj.LocalStatus = 'stopping'; - obj.Timeline.stop - obj.sendUDP(obj.LastReceivedMessage) - end - case 'WHAT' - % TODO fix status updates so that they're meaningful - parsed = regexp(response.body, '(?<id>\d+)(?<update>[a-z]*)', 'names'); - if obj.AwaitingConfirmation - try - assert(strcmp(parsed.id, int2str(obj.ConfirmID)), 'Rigbox:srv:unexpectedUDPResponse',... - 'Received UDP message ID did not match sent'); - switch parsed.update - case {'running' 'starting'} - obj.Status = 'running'; - otherwise - obj.Status = 'idle'; - end - catch - obj.Status = 'unavailable'; - end - else % Received status request NB: Currently no way of determining status - obj.sendUDP([parsed.status parsed.id obj.LocalStatus]) - end - otherwise - disp(['Received ''' obj.LastReceivedMessage ''' from ' obj.RemoteHost]) - end - % Reset AwaitingConfirmation - obj.AwaitingConfirmation = false; - end - end -end \ No newline at end of file diff --git a/+srv/expServer.m b/+srv/expServer.m index e629a017..d954527c 100644 --- a/+srv/expServer.m +++ b/+srv/expServer.m @@ -169,7 +169,7 @@ function handleMessage(id, data, host) end case 'run' % exp run request - [expRef, preDelay, postDelay, Alyx] = args{:}; + [expRef, preDelay, postDelay, Alyx] = args{:}; if dat.expExists(expRef) log('Starting experiment ''%s''', expRef); communicator.send(id, []); diff --git a/cortexlab/+srv/RemoteMPEPService.m b/cortexlab/+srv/RemoteMPEPService.m index baa2281e..e71acd0d 100644 --- a/cortexlab/+srv/RemoteMPEPService.m +++ b/cortexlab/+srv/RemoteMPEPService.m @@ -39,19 +39,19 @@ LocalStatus % Local status to send upon request ResponseTimeout = Inf % How long to wait for confirmation of receipt Timeline % Holds an instance of Timeline - Callbacks % Holds callback functions for each instruction + Callbacks = {@obj.processMsg, @nop} % Holds callback functions for each instruction end properties (SetObservable, AbortSet = true) RemoteHost % Host name of the remote service - ListenPort = 10000 % Localhost port number to listen for messages on - RemotePort = 10000 % Which port to send messages to remote service on + ListenPorts % Localhost port number to listen for messages on + RemotePort = 1103 % Which port to send messages to remote service on EnablePortSharing = 'off' % If set to 'on' other applications can use the listen port end properties (SetAccess = protected) RemoteIP % The IP address of the remote service - Socket % A handle to the udp object + Sockets % A handle to the udp object LastSentMessage = '' % A copy of the message sent from this host LastReceivedMessage = '' % A copy of the message received by this host end @@ -72,10 +72,10 @@ function delete(obj) % To be called before destroying BasicUDPService object. Deletes all % timers, sockets and listeners Tidy up after ourselves by closing % the listening sockets - if ~isempty(obj.Socket) - fclose(obj.Socket); % Close the connection - delete(obj.Socket); % Delete the socket - obj.Socket = []; % Delete udp object + if ~isempty(obj.Sockets) + cellfun(@fclose, obj.Sockets); % Close the connection + delete(obj.Sockets); % Delete the socket + obj.Sockets = []; % Delete udp object obj.Listener = []; % Delete any listeners to that object if ~isempty(obj.ResponseTimer) % If there is a timer object stop(obj.ResponseTimer) % Stop the timer.. @@ -85,40 +85,69 @@ function delete(obj) end end - function obj = RemoteTLService(remoteHost, remotePort, listenPort) - % SRV.REMOTETLSERVICE(remoteHost [remotePort, listenPort]) + function obj = RemoteMPEPService(name, listenPort, callback) + % SRV.REMOTETLSERVICE([remoteHost, remotePort, listenPort]) % remoteHost is the hostname of the service with which to send and % receive messages. paths = dat.paths(hostname); % Get list of paths for timeline - obj.Callbacks = struct('Instruction', {'ExpStart', 'BlockStart',... - 'StimStart', 'StimEnd', 'BlockEnd', 'ExpEnd', 'ExpInterrupt'}, 'Callback', @nop); - obj.Timeline = load(fullfile(paths.rigConfig, 'hardware.mat'), 'timeline'); % Load timeline object - obj.RemoteHost = remoteHost; % Set hostname - obj.RemoteIP = ipaddress(remoteHost); % Get IP address - if nargin >= 3; obj.ListenPort = listenPort; end % Set local port - if nargin >= 2; obj.RemotePort = remotePort; end % Set remote port - obj.Socket = udp(obj.RemoteIP,... % Create udp object - 'RemotePort', obj.RemotePort, 'LocalPort', obj.ListenPort); - obj.Socket.ReadAsyncMode = 'continuous'; - obj.Socket.BytesAvailableFcnCount = 10; % Number of bytes in buffer required to trigger BytesAvailableFcn - obj.Socket.BytesAvailableFcn = @obj.receiveUDP; % Add callback to receiveUDP when enough bytes arrive in the buffer +% obj.Callbacks = struct('Instruction', {'ExpStart', 'BlockStart',... +% 'StimStart', 'StimEnd', 'BlockEnd', 'ExpEnd', 'ExpInterrupt'}, 'Callback', @nop); + load(fullfile(paths.rigConfig, 'hardware.mat'), 'timeline'); % Load timeline object + obj.Timeline = timeline; + obj.addListener(name, listenPort, callback); +% if nargin < 1; remoteHost = ''; end % Set local port +% obj.RemoteHost = remotehost; % Set hostname +% obj.RemoteIP = ipaddress(remoteHost); % Get IP address +% if nargin >= 3; obj.ListenPort = listenPort; end % Set local port +% if nargin >= 2; obj.RemotePort = remotePort; end % Set remote port +% obj.Socket = udp(obj.RemoteIP,... % Create udp object +% 'RemotePort', obj.RemotePort, 'LocalPort', obj.ListenPort); +% obj.Socket.BytesAvailableFcn = @obj.receiveUDP; % Add callback to receiveUDP when enough bytes arrive in the buffer % Add listener to MessageReceived event, notified when receiveUDP is % called. This event can be listened to by anyone. - obj.Listener = event.listener(obj, 'MessageReceived', @processMsg); +% obj.Listener = event.listener(obj, 'MessageReceived', @processMsg); % Add listener for when the observable properties are set - obj.addlistener({'RemoteHost', 'ListenPort', 'RemotePort', 'EnablePortSharing'},... - 'PostSet',@obj.update); +% obj.addlistener({'RemoteHost', 'ListenPort', 'RemotePort', 'EnablePortSharing'},... +% 'PostSet',@(src,~)obj.update(src)); % Add listener for when the remote service's status is requested - obj.addlistener('Status', 'PreGet', @obj.requestStatus); +% obj.addlistener('Status', 'PreGet', @obj.requestStatus); end - function update(obj, evt, ~) + function obj = addListener(obj, name, listenPort, callback) + if nargin<3; callback = @nop; end + if listenPort==obj.ListenPorts + error('Listen port already added'); + end + idx = length(obj.Sockets)+1; + obj.Sockets{idx} = udp(name, 'RemotePort', obj.RemotePort,... + 'LocalPort', listenPort, 'ReadAsyncMode', 'continuous'); + obj.Sockets{idx}.BytesAvailableFcnCount = 10; % Number of bytes in buffer required to trigger BytesAvailableFcn + obj.Sockets{idx}.BytesAvailableFcn = @(~,~)obj.receiveUDP(src,evt); + obj.Sockets{idx}.Tag = name; + obj.ListenPorts{idx} = listenPort; + obj.Callbacks{idx} = callback; + end + + function obj = removeHost(obj, name) + %TODO + if nargin<3; callback = @nop; end + if listenPort==obj.ListenPorts + error('Listen port already added'); + end + obj.Sockets(end+1) = udp(obj.RemoteIP, 'RemotePort', obj.RemotePort, 'LocalPort', listenPort); + obj.Sockets(end).BytesAvailableFcn = @(~,~)obj.receiveUDP(src,evt); + obj.Sockets(end).Tag = name; + obj.ListenPorts(end+1) = listenPort; + obj.Callbacks(end+1) = callback; + end + + function update(obj, src) % Callback for setting udp relevant properties. Some properties can % only be set when the socket is closed. % Check if socket is open isOpen = strcmp(obj.Socket.Status, 'open'); % Close connection before setting, if required to do so - if any(strcmp(evt.name, {'RemoteHost', 'LocalPort', 'EnablePortSharing'}))&&isOpen + if any(strcmp(src.name, {'RemoteHost', 'LocalPort', 'EnablePortSharing'}))&&isOpen fclose(obj.Socket); end % Set all the relevant properties @@ -129,15 +158,41 @@ function update(obj, evt, ~) if isOpen; bind(obj); end % If socket was open before, re-open end - function bind(obj) - % Open the connection to allow messages to be sent and received - if ~isempty(obj.Socket); fclose(obj.Socket); end - fopen(obj.Socket); + function bind(obj, names) + if isempty(obj.Sockets) + warning('No sockets to bind') + return + end + if nargin<2 + % Close all sockets, in case they are open + cellfun(@fclose, obj.Sockets) + % Open the connection to allow messages to be sent and received + cellfun(@fopen, obj.Sockets) + else + names = ensureCell(names); + hosts = arrayfun(@(s)s.Tag, obj.Sockets); + idx = cellfun(@(n)find(strcmp(n,hosts)), names); + arrayfun(@fopen, obj.Sockets(idx)) + end + log('Polling for UDP messages'); end - function start(obj, expRef) + function start(obj, ref) % Send start message to remotehost and await confirmation - obj.confirmedSend(sprintf('GOGO%s*%s', expRef, obj.RemoteHost)); + [expRef, AlyxInstance] = parseAlyxInstance(ref); + % Convert expRef to MPEP style + [subject, seriesNum, expNum] = dat.expRefToMpep(expRef); + % Build start message + msg = sprintf('ExpStart %s %d %d', subject, seriesNum, expNum); + % Send the start message + obj.confirmedSend(msg, obj.RemoteHost); + % Wait for response + while obj.AwaitingConfirmation; pause(0.2); end +% % Start a block (we only use one per experiment) +% msg = sprintf('BlockStart %s %d %d 1', subject, seriesNum, expNum); +% obj.confirmedSend(msg, obj.RemoteHost); +% % Wait for response +% while obj.AwaitingConfirmation; pause(0.2); end end function stop(obj) @@ -163,11 +218,18 @@ function confirmedSend(obj, msg) end end - function receiveUDP(obj) - obj.LastReceivedMessage = fscanf(obj.Socket); - % Remove any more accumulated inputs to the listener - obj.Socket.flushinput(); + function receiveUDP(obj, src, evt) + obj.LastReceivedMessage = strtrim(fscanf(obj.Socket)); + % Let everyone know a message was recieved notify(obj, 'MessageReceived') + hosts = arrayfun(@(s)s.Tag, obj.Sockets); + if ~isempty(obj.Timeline)&&obj.Timeline.IsRunning + t = obj.Timeline.time; % Note the time + % record the UDP event in Timeline + obj.Timeline.record([hosts 'UDP'], msg, t); + end + % Pass message to callback function for precessing + feval(obj.Callbacks{strcmp(hosts, src.Tag)}, src, evt); end function sendUDP(obj, msg) @@ -177,78 +239,73 @@ function sendUDP(obj, msg) obj.LastSentMessage = msg; % Save a copy of the message disp(['Sent message to ' obj.RemoteHost]) % Display success end + + function echo(obj, src, ~) + % Echo message + fclose(src); + src.RemoteHost = src.DatagramAddress; + src.RemotePort = src.DatagramPort; + fopen(src); + fprintf(obj.Socket, obj.LastReceivedMessage); % Send message + obj.LastSentMessage = obj.LastReceivedMessage; % Save a copy of the message + disp(['Echo''d message to ' src.Tag]) % Display success + end end methods (Access = protected) - function processMsg(obj, ~, ~) - % Parse the message into its constituent parts - msg = dat.mpepMessageParse; - % Check that the message was from the correct host, otherwise ignore - if strcmp(response.host, obj.RemoteHost) - warning('Received message from %s, ignoring', response.host); - return + function processMsg(obj, src, ~) + %PROCESSMSG Processes messages from expServer and MPEP + % As the remote host me be either expServer or MPEP, we first + % determine the type of message. Parse the message into its + % constituent parts +% if strcmp(obj.LastReceivedMessage(1:4), {'WHAT', 'GOGO', 'ALYX', 'STOP'}) + try % Try to process message as MPEP command + msg = dat.mpepMessageParse(obj.LastReceivedMessage); + catch + msg = regexp(obj.LastReceivedMessage,... + '(?<intruction>[A-Z]{4})(?<body>.*)\*(?<host>\w*)', 'names'); + % If the message body contains and expRef, explicity set this + if regexp(msg.body,dat.expRefRegExp); msg.expRef = msg.body; end end - if obj.AwaitingConfirmation - % Check the confirmation message is the same as the sent message - assert(~isempty(response)||... % something received - strcmp(response.status, 'WHAT')||... % status update - strcmp(obj.LastReceivedMessage, obj.LastSentMessage),... % is echo - 'Confirmation failed') - % We no longer need the timer, stop and delete it - if ~isempty(obj.ResponseTimer) - stop(obj.ResponseTimer) - delete(obj.ResponseTimer) - obj.ResponseTimer = []; - end - end - % At the moment we just disply some stuff, other functions listening - % to the MessageReceived event can do their thing - switch response.status - case 'GOGO' - if obj.AwaitingConfirmation - obj.Status = 'running'; - disp(['Service on ' obj.RemoteHost ' running']) - else - disp('Received start request') - obj.LocalStatus = 'starting'; - obj.Timeline.start(dat.parseAlyxInstance(response.body)) - obj.LocalStatus = 'running'; - obj.sendUDP(obj.LastReceivedMessage) - end - case 'STOP' - if obj.AwaitingConfirmation - obj.Status = 'stopped'; - disp(['Service on ' obj.RemoteHost ' stopped']) - else - disp('Received stop request') - obj.LocalStatus = 'stopping'; - obj.Timeline.stop - obj.sendUDP(obj.LastReceivedMessage) - end - case 'WHAT' - % TODO fix status updates so that they're meaningful - parsed = regexp(response.body, '(?<id>\d+)(?<update>[a-z]*)', 'names'); - if obj.AwaitingConfirmation + + % Process the instruction + switch lower(msg.instruction) + case {'expstart', 'gogo'} try - assert(strcmp(parsed.id, int2str(obj.ConfirmID)), 'Rigbox:srv:unexpectedUDPResponse',... - 'Received UDP message ID did not match sent'); - switch parsed.update - case {'running' 'starting'} - obj.Status = 'running'; - otherwise - obj.Status = 'idle'; - end - catch - obj.Status = 'unavailable'; + % Start Timeline + log('Received start request') + obj.LocalStatus = 'starting'; + obj.Timeline.start(dat.parseAlyxInstance(msg.expRef)) + obj.LocalStatus = 'running'; + obj.echo(src); + % re-record the UDP event in Timeline since it wasn't started + % when we tried earlier. Treat it as having arrived at time zero. + obj.Timeline.record('mpepUDP', obj.LastReceivedMessage, 0); + catch ex + % flag up failure so we do not echo the UDP message back below + failed = true; + disp(getReport(ex)); end - else % Received status request NB: Currently no way of determining status - obj.sendUDP([parsed.status parsed.id obj.LocalStatus]) - end - otherwise - disp(['Received ''' obj.LastReceivedMessage ''' from ' obj.RemoteHost]) + case {'expend', 'stop', 'expinterrupt'} + obj.Timeline.stop(); % stop Timeline + case 'what' + % TODO fix status updates so that they're meaningful + parsed = regexp(msg.body, '(?<id>\d+)(?<update>[a-z]*)', 'names'); + obj.sendUDP([parsed.status parsed.id obj.LocalStatus]) + case 'alyx' + % TODO Add Alyx token request + obj.sendUDP() + otherwise + % TODO RemoteHost + log(['Received ''' obj.LastReceivedMessage ''' from ' obj.RemoteHost]) end - % Reset AwaitingConfirmation - obj.AwaitingConfirmation = false; end + + function log(varargin) + message = sprintf(varargin{:}); + timestamp = datestr(now, 'dd-mm-yyyy HH:MM:SS'); + fprintf('[%s] %s\n', timestamp, message); + end + end end \ No newline at end of file diff --git a/readme.txt b/readme.txt index 501b335e..0e01d560 100644 --- a/readme.txt +++ b/readme.txt @@ -1,22 +1,94 @@ -In order to install on any computer: +---------- +# Rigbox -- run the Rigbox/addRigboxPaths.m -- install the GUI Layout Toolbox from here: https://uk.mathworks.com/matlabcentral/fileexchange/47982-gui-layout-toolbox -- double check that the added paths (including those to the Toolbox) are above the paths to zserver +Rigbox is a (mostly) object-oriented MATLAB software package for designing and controlling behavioural experiments. Principally, the steering wheel setup we developed to probe mouse behaviour. It requires two computers, one for stimulus presentation ('the stimulus server') and another for controlling and monitoring the experiment ('mc'). -Main changes: +## Getting Started -- handles to objects are no longer numerical -- the UI is now using the most recent version of GUI Layout Toolbox -- all code now works in the latest version of MATLAB +The following is a brief description of how to install Rigbox on your experimental rig. However detailed, step-by-step information can be found [here](https://www.ucl.ac.uk/cortexlab/tools/wheel). -Little fixes: +## Prerequisites +Rigbox has a number of essential and optional software dependencies, listed below: +* Windows 7 or later +* [MATLAB](https://uk.mathworks.com/downloads/web_downloads/?s_iid=hp_ff_t_downloads) 2016a or later + * [Psychophsics Toolbox](https://github.com/Psychtoolbox-3/Psychtoolbox-3/releases) v3 or later + * [NI-DAQmx support package](https://uk.mathworks.com/hardware-support/nidaqmx.html) + * [GUI Layout Toolbox](https://uk.mathworks.com/matlabcentral/fileexchange/47982-gui-layout-toolbox) v2 or later + * Data Acquisition Toolbox + * Signal Processing Toolbox + * Instrument Control Toolbox -- checkbox in param editor now functions correctly (added line 382 +eui.ParamEditor/addParamUI) -- more documentation, particularly for the UI elements -- saved parameters dropdown now ordered in mc +Additionally, Rigbox works with a number of extra repositories: +* [Signals](https://github.com/dendritic/signals) (for running bespoke experiment designs) + * Statistics and Machine Learning Toolbox + * [Microsoft Visual C++ Redistributable for Visual Studio 2015](https://www.microsoft.com/en-us/download/details.aspx?id=48145) +* [Alyx-matlab](https://github.com/cortex-lab/alyx-matlab) (for registering data to, and retrieving from, an Alyx database + * [Missing HTTP v1](https://github.com/psexton/missing-http/releases/tag/missing-http-1.0.0) or later + * [JSONlab](https://uk.mathworks.com/matlabcentral/fileexchange/33381-jsonlab--a-toolbox-to-encode-decode-json-files) -To do: +## Installing +1. To install Rigbox, first ensure that all the above dependencies are installed. +2. Pull the latest Rigbox-lite branch. This branch is currently the 'cleanest' one, however in the future it will likely be merged with the master branch. +3. In MATLAB run 'addRigboxPaths.m' and restart the program. +4. Set the correct paths by following the instructions in Rigbox\+dat\paths.m on both computers. +5. On the stimulus server, load the hardware.mat file in Rigbox\Repositories\code\config\exampleRig and edit according to your specific hardware setup (link to detailed instructions above, under 'Getting started'). -- rename the cortexlab folder and move +exp to ExpDefinitions -- add specific path for ExpDefinitions in dat.paths (see line 115 in MControl) \ No newline at end of file +## Running an experiment + +On the stimulus server, run: +> srv.expServer + +On the mc computer, run: +> mc + +This opens a GUI that will allow you to choose a subject, edit some of the experimental parameters and press 'Start' to begin the basic steering wheel task on the stimulus server. + +# Code organization +Below is a list of the principle directories and their general purpose. +## +dat +The data package contains all the code pertaining to the organization and logging of data. It contains functions that generate and parse unique experiment reference ids, that return the file paths where subject data and rig configuration information is stored. Other functions include those that manage experimental log entries and parameter profiles. This package is akin to a lab notebook. + +## +eui +This package contains the code pertaining to the Rigbox user interface. It contains code for constructing the mc GUI (MControl.m), and for plotting live experiment data or generating tables for viewing experiment parameters and subject logs. This package is exclusively used by the mc computer. + +## +exp +The experiment package is for the initialization and running of behavioural experiments. These files define a framework for event- and state-based experiments. Actions such as visual stimulus presentation or reward delivery can be controlled by experiment phases, and experiment phases are managed by an event-handling system (e.g. ResponseEventInfo). + +The package also triggers auxiliary services (e.g. starting remote acquisition software), and loads parameters for presentation each trail. The principle two base classes that control these experiments are Experiment and its Signals counterpart, SignalsExp. + +This package is almost exclusively used by the stimulus server + +## +hw +The hardware package is for configuring, and interfacing with, hardware such as screens, DAQ devices, weighing scales and lick detectors. Withing this is the +ptb package which contains classes for interacting with PsychToolbox. + +The devices file loads and initializes all the hardware for a specific experimental rig. There are also classes for unifying system and hardware clocks. + +## +psy +This package contains simple functions for processing and plotting psychometric data + +## +srv +This package contains the expServer function as well as classes that manage communications between rig computers. + +The Service base class allows the stimulus server to start and stop auxiliary acquisition systems at the beginning and end of experiments + +The StimulusControl class is used by the mc computer to manage the stimulus server + +NB: Lower-level communication protocol code is found in the +io package + +## cb-tools\burgbox +Burgbox contains many simply helper functions that are used by the main packages. Within this directory are further packages: +* +bui --- Classes for managing graphics objects such as axes +* +aud --- Functions for interacting with PsychoPortAudio +* +file --- Functions for simplifying directory and file management, for instance returning the modified dates for specified folders or filtering an array of directories by those that exist +* +fun --- Convenience functions for working with function handles in MATLAB, e.g. functions similar cellfun that are agnostic of input type, or ones that cache function outputs +* +img --- Classes that deal with image and frame data (DEPRICATED) +* +io --- Lower-level communications classes for managing UDP and TCP/IP Web sockets +* +plt --- A few small plotting functions (DEPRICATED) +* +vis --- Functions for returning various windowed visual stimuli (i.g. gabor gratings) +* +ws --- An early Web socket package using SuperWebSocket (DEPRICATED) + +## cortexlab +The cortexlab directory is intended for functions and classes that are rig or lab specific, for instance code that allows compatibility with other stimulus presentation packages used by cortexlab (i.e. MPEP) + +## Authors +The majority of the Rigbox code was written by [Chris Burgess](https://github.com/dendritic/) in 2013. It is now maintained and developed by a number of people at [CortexLab](https://www.ucl.ac.uk/cortexlab). From 37a80ad70b11a20ff67ffcc80668632c59ba7cd5 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 10 Jan 2018 13:52:36 +0000 Subject: [PATCH 019/393] readme now in markdown converted from text to markdown --- readme.txt => readme.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename readme.txt => readme.md (100%) diff --git a/readme.txt b/readme.md similarity index 100% rename from readme.txt rename to readme.md From adf5c070c617e8e17c94e4179535606d734b3d84 Mon Sep 17 00:00:00 2001 From: Kenneth Harris <kenneth.harris@ucl.ac.uk> Date: Wed, 10 Jan 2018 14:29:11 +0000 Subject: [PATCH 020/393] Added UML file --- Rigbox UML.pdf | Bin 0 -> 89221 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Rigbox UML.pdf diff --git a/Rigbox UML.pdf b/Rigbox UML.pdf new file mode 100644 index 0000000000000000000000000000000000000000..df1cb6cbf11a8abf17c99991fc875a8339236d3e GIT binary patch literal 89221 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�apU@!nL XZ);EQf2SJ?C5eIpgoKo|?gRb>(qS3q literal 0 HcmV?d00001 From 71fc6c5d3e626ca4661fc4e65759fea4f2a8c62a Mon Sep 17 00:00:00 2001 From: nsteinme <nick.steinmetz@gmail.com> Date: Wed, 10 Jan 2018 16:59:59 +0000 Subject: [PATCH 021/393] added paths for tape backup --- +dat/paths.m | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/+dat/paths.m b/+dat/paths.m index dc0997ca..9d69d38e 100644 --- a/+dat/paths.m +++ b/+dat/paths.m @@ -18,6 +18,8 @@ % server3Name = '\\zserver3.cortexlab.net'; % 2017-02-18 MW - Currently % unused by Rigbox server4Name = '\\zserver4.cortexlab.net'; +basketName = '\\basket.cortexlab.net'; % for working analyses +lugaroName = '\\lugaro.cortexlab.net'; % for tape backup %% defaults % path containing rigbox config folders @@ -52,6 +54,18 @@ % repository for all experiment definitions p.expDefinitions = fullfile(server1Name, 'Code', 'Rigging', 'ExpDefinitions'); +% repository for working analyses that are not meant to be stored +% permanently +p.workingAnalysisRepository = fullfile(basketName, 'data'); + +% for tape backups, first files go here: +p.tapeStagingRepository = fullfile(lugaroName, 'bigdrive', 'staging'); + +% then they go here: +p.tapeArchiveRepository = fullfile(lugaroName, 'bigdrive', 'toarchive'); + + + %% load rig-specific overrides from config file, if any customPathsFile = fullfile(p.rigConfig, 'paths.mat'); if file.exists(customPathsFile) From e03899350f0958d2ff99f2b62d28321c66302d74 Mon Sep 17 00:00:00 2001 From: nsteinme <nick.steinmetz@gmail.com> Date: Thu, 11 Jan 2018 11:42:40 +0000 Subject: [PATCH 022/393] update alyx handling for mpepListenerWithWS, add plotting options to Timeline --- +hw/Timeline.m | 39 ++++++++++++++++++++-------- cortexlab/+tl/bindMpepServerWithWS.m | 16 +++++++++--- 2 files changed, 40 insertions(+), 15 deletions(-) diff --git a/+hw/Timeline.m b/+hw/Timeline.m index e084c8e0..3739c1bf 100644 --- a/+hw/Timeline.m +++ b/+hw/Timeline.m @@ -83,6 +83,7 @@ AquiredDataType = 'double' % default data type for the acquired data array (i.e. Data.rawDAQData) UseTimeline = false % used by expServer. If true, timeline is started by default (otherwise can be toggled with the t key) LivePlot = false % if true the data are plotted as the data are aquired + LivePlotParams = []; WriteBufferToDisk = false % if true the data buffer is written to disk as they're aquired NB: in the future this will happen by default end @@ -616,23 +617,28 @@ function livePlot(obj, data) % TL.LIVEPLOT(source, event) plots the data aquired by the % DAQ while the PlotLive property is true. if isempty(obj.Axes) - figure(); % create a figure for plotting aquired data + f = figure(); % create a figure for plotting aquired data obj.Axes = gca; % store a handle to the axes -% set(figure_handle, 'Position', [21 81 1033 1726]); % set the figure position + if isfield(obj.LivePlotParams, 'figPosition') && ~isempty(obj.LivePlotParams.figPosition) + set(f, 'Position', obj.LivePlotParams.figPosition); % set the figure position + end end % get the names of the inputs being recorded names = pick({obj.Inputs.name}, find([obj.Inputs.arrayColumn] > -1), 'cell'); nSamps = size(data,1); % Get the number of samples in this chunck nChans = size(data,2); % Get the number of channels - traceSep = 7; % ??? + traceSep = 7; % unit is Volts - for most channels the max is 5V so this is a good separation offsets = (1:nChans)*traceSep; - scales = ones(1, nChans); - if length(scales)<nChans - sc = ones(1,nChans); - sc(1:length(scales)) = scales; - scales = sc; clear sc; - end + + % scales control a vertical scaling of each trace + % (multiplicative) and can be set manually in the config. A + % nicer future version would put a scroll wheel callback on the + % figure and scale by scrolling the one that's hovered over + scales = ones(1, nChans); + if isfield(obj.LivePlotParams, 'figScales') && ~isempty(obj.LivePlotParams.figScales) + scales(1:numel(obj.LivePlotParams.figScales)) = obj.LivePlotParams.figScales; + end traces = get(obj.Axes, 'Children'); if isempty(traces) @@ -643,8 +649,13 @@ function livePlot(obj, data) set(obj.Axes, 'YTickLabel', names); end + % get the measurement type of each channel, since Position-type + % inputs are plotted differently. + meas = {obj.Inputs.measurement}; + meas = meas(ismember({obj.Inputs.name}, obj.UseInputs)); + for t = 1:length(traces) - if strcmp(obj.Inputs(t).measurement, 'Position') + if strcmp(meas{t}, 'Position') % if a position sensor (i.e. rotary encoder) scale % by the first point and allow negative values if any(data(:,t)>2^31); data(data(:,t)>2^31,t) = data(data(:,t)>2^31,t)-2^32; end @@ -653,7 +664,13 @@ function livePlot(obj, data) yy = get(traces(end-t+1), 'YData'); % get current data for trace yy(1:end-nSamps) = yy(nSamps+1:end); % add the new chuck for channel % scale and offset the traces - if strcmp(obj.Inputs(t).measurement, 'Position') + if strcmp(meas{t}, 'Position') + % for position-type inputs, plot velocity (take the + % diff, and smooth) rather than absolute. this is + % necessary to prevent the value from wandering way off + % the range and making it impossible to see any of the + % other traces. Plus it is probably more useful, + % anyway. yy(end-nSamps+1:end) = conv(diff([data(1,t); data(:,t)]),... gausswin(50)./sum(gausswin(50)), 'same') * scales(t) + offsets(t); else diff --git a/cortexlab/+tl/bindMpepServerWithWS.m b/cortexlab/+tl/bindMpepServerWithWS.m index b6401089..0adbafca 100644 --- a/cortexlab/+tl/bindMpepServerWithWS.m +++ b/cortexlab/+tl/bindMpepServerWithWS.m @@ -154,17 +154,25 @@ function listen() end if firstPress(manualStartKey) && ~tlObj.IsRunning - % first get an alyx instance - ai = alyx.loginWindow(); - + if isempty(tls.AlyxInstance) + % first get an alyx instance + ai = alyx.loginWindow(); + else + ai = tls.AlyxInstance; + end + [mouseName, ~] = dat.subjectSelector([],ai); if ~isempty(mouseName) clear expParams; expParams.experimentType = 'timelineManualStart'; - newExpRef = dat.newExp(mouseName, now, expParams, ai); + [newExpRef, newExpSeq, subsessionURL] = dat.newExp(mouseName, now, expParams, ai); + ai.subsessionURL = subsessionURL; + tls.AlyxInstance = ai; + %[subjectRef, expDate, expSequence] = dat.parseExpRef(newExpRef); %newExpRef = dat.constructExpRef(mouseName, now, expNum); + communicator.send('AlyxSend', {tls.AlyxInstance}); communicator.send('status', { 'starting', newExpRef}); tlObj.start(newExpRef, ai); end From 5bcf00a5307fa67842b3ba43d810f2ee1a1dddbb Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 11 Jan 2018 17:08:07 +0000 Subject: [PATCH 023/393] Weight record fix + better custom ExpPanels * Water now submitted regardless of experiment panel function * The weight is now properly queued to Alex when recorded while logged out --- +eui/AlyxPanel.m | 2 +- +eui/ExpPanel.m | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index 02889e23..a0cea8a2 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -393,11 +393,11 @@ function recordWeight(obj, weight, subject) weight = iff(ischar(weight{1}), str2double(weight{1}), weight{1}); d.subject = subject; d.weight = weight; - d.user = ai.username; if isempty(ai) % if not logged in, save the weight for later obj.QueuedWeights{end+1} = d; obj.log('Warning: Weight not posted to Alyx; will be posted upon login.'); else % otherwise immediately post to Alyx + d.user = ai.username; try w = alyx.postData(ai, 'weighings/', d); obj.log('Alyx weight posting succeeded: %.2f for %s', w.weight, w.subject); diff --git a/+eui/ExpPanel.m b/+eui/ExpPanel.m index a3b2c86a..a9a53d01 100644 --- a/+eui/ExpPanel.m +++ b/+eui/ExpPanel.m @@ -225,11 +225,6 @@ function expStopped(obj, rig, evt) subject = obj.SubjectRef; if ~isempty(ai)&&~strcmp(subject,'default') switch class(obj) - case 'eui.SqueakExpPanel' - infoFields = {obj.InfoFields.String}; - inc = cellfun(@(x) any(strfind(x(:)','�l')), {obj.InfoFields.String}); % Find event values ending with 'ul'. - reward = cell2mat(cellfun(@str2num,strsplit(infoFields{find(inc,1)},'�l'),'UniformOutput',0)); - amount = iff(isempty(reward),0,@()reward); case 'eui.ChoiceExpPanel' if ~isfield(obj.Block.trial,'feedbackType'); return; end % No completed trials if any(strcmp(obj.Parameters.TrialSpecificNames,'rewardVolume')) % Reward is trial specific @@ -242,7 +237,10 @@ function expStopped(obj, rig, evt) end if numel(amount)>1; amount = amount(1); end % Take first element (second being laser) otherwise - return + infoFields = {obj.InfoFields.String}; + inc = cellfun(@(x) any(strfind(x(:)','�l')), {obj.InfoFields.String}); % Find event values ending with 'ul'. + reward = cell2mat(cellfun(@str2num,strsplit(infoFields{find(inc,1)},'�l'),'UniformOutput',0)); + amount = iff(isempty(reward),0,@()reward); end if ~any(amount); return; end % Return if no water was given try From e09264b0043ef37ff75a4d96fba3d0083fa1b1bf Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Mon, 15 Jan 2018 17:27:57 +0000 Subject: [PATCH 024/393] Fix to plotting Alyx plotting bug * AlyxPanel's use of axes when weights were recorded was interfereing with ExpPanel. This has been fixed by explicitly defining which axes to hold, etc. * Custom ExpPanel ignored now if the field is empty --- +eui/AlyxPanel.m | 40 ++++++++++++++++++++-------------------- +eui/ExpPanel.m | 5 +++-- +eui/MControl.m | 2 +- 3 files changed, 24 insertions(+), 23 deletions(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index a0cea8a2..2f0852e6 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -506,39 +506,39 @@ function viewSubjectHistory(obj, ax) end plot(ax, dates, [records.weight_measured], '.-'); - hold on; + hold(ax, 'on'); plot(ax, dates, [records.weight_expected]*0.7, 'r', 'LineWidth', 2.0); plot(ax, dates, [records.weight_expected]*0.8, 'LineWidth', 2.0, 'Color', [244, 191, 66]/255); - box off; - xlim([min(dates) max(dates)]); + box(ax, 'off'); + xlim(ax, [min(dates) max(dates)]); if nargin == 1 set(ax, 'XTickLabel', arrayfun(@(x)datestr(x, 'dd-mmm'), get(ax, 'XTick'), 'uni', false)) else - ax.XTickLabel = arrayfun(@(x)datestr(x, 'dd-mmm'), get(ax.Handle, 'XTick'), 'uni', false); + ax.XTickLabel = arrayfun(@(x)datestr(x, 'dd-mmm'), get(ax, 'XTick'), 'uni', false); end - ylabel('weight (g)'); + ylabel(ax, 'weight (g)'); if nargin==1 ax = axes('Parent', plotBox); - plot(dates, [records.weight_measured]./[records.weight_expected], '.-'); - hold on; - plot(dates, 0.7*ones(size(dates)), 'r', 'LineWidth', 2.0); - plot(dates, 0.8*ones(size(dates)), 'LineWidth', 2.0, 'Color', [244, 191, 66]/255); - box off; - xlim([min(dates) max(dates)]); + plot(ax, dates, [records.weight_measured]./[records.weight_expected], '.-'); + hold(ax, 'on'); + plot(ax, dates, 0.7*ones(size(dates)), 'r', 'LineWidth', 2.0); + plot(ax, dates, 0.8*ones(size(dates)), 'LineWidth', 2.0, 'Color', [244, 191, 66]/255); + box(ax, 'off'); + xlim(ax, [min(dates) max(dates)]); set(ax, 'XTickLabel', arrayfun(@(x)datestr(x, 'dd-mmm'), get(ax, 'XTick'), 'uni', false)) - ylabel('weight as pct (%)'); + ylabel(ax, 'weight as pct (%)'); axWater = axes('Parent',plotBox); - plot(dates, [records.water_given]+[records.hydrogel_given], '.-'); - hold on; - plot(dates, [records.hydrogel_given], '.-'); - plot(dates, [records.water_given], '.-'); - plot(dates, [records.water_expected], 'r', 'LineWidth', 2.0); - box off; - xlim([min(dates) max(dates)]); + plot(axWater, dates, [records.water_given]+[records.hydrogel_given], '.-'); + hold(axWater, 'on'); + plot(axWater, dates, [records.hydrogel_given], '.-'); + plot(axWater, dates, [records.water_given], '.-'); + plot(axWater, dates, [records.water_expected], 'r', 'LineWidth', 2.0); + box(axWater, 'off'); + xlim(axWater, [min(dates) max(dates)]); set(axWater, 'XTickLabel', arrayfun(@(x)datestr(x, 'dd-mmm'), get(axWater, 'XTick'), 'uni', false)) - ylabel('water/hydrogel (mL)'); + ylabel(axWater, 'water/hydrogel (mL)'); % Create table of useful weight and water information, % sorted by date diff --git a/+eui/ExpPanel.m b/+eui/ExpPanel.m index a9a53d01..5ccb95b6 100644 --- a/+eui/ExpPanel.m +++ b/+eui/ExpPanel.m @@ -61,10 +61,11 @@ warning(ex.getReport()); end params = exp.Parameters(paramsStruct); % Get parameters - if isfield(params.Struct, 'expPanelFun') % Can define your own experiment panel + % Can define your own experiment panel + if isfield(params.Struct, 'expPanelFun')&&~isempty(params.Struct.expPanelFun) if isempty(which(params.Struct.expPanelFun)); addpath(fileparts(params.Struct.defFunction)); end p = feval(params.Struct.expPanelFun, parent, ref, params, logEntry); - else + else % otherwise use the default switch params.Struct.type case {'SingleTargetChoiceWorld' 'ChoiceWorld' 'DiscWorld' 'SurroundChoiceWorld'} p = eui.ChoiceExpPanel(parent, ref, params, logEntry); diff --git a/+eui/MControl.m b/+eui/MControl.m index abf5b918..0f09f9ff 100644 --- a/+eui/MControl.m +++ b/+eui/MControl.m @@ -570,7 +570,7 @@ function updateWeightPlot(obj) datenums = floor([entries.date]); obj.WeightAxes.clear(); if ~isempty(obj.AlyxPanel.AlyxInstance)&&~strcmp(obj.LogSubject.Selected,'default') - obj.AlyxPanel.viewSubjectHistory(obj.WeightAxes) + obj.AlyxPanel.viewSubjectHistory(obj.WeightAxes.Handle) rotateticklabel(obj.WeightAxes.Handle, 45); else if numel(datenums) > 0 From 0550d5b70b52275eeae812457128de6fdadc0524 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 16 Jan 2018 17:40:27 +0000 Subject: [PATCH 025/393] Fix'd Alyx xlim error Error when setting xlim for new mice resolved. --- +eui/AlyxPanel.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index 2f0852e6..6b9704b5 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -510,7 +510,7 @@ function viewSubjectHistory(obj, ax) plot(ax, dates, [records.weight_expected]*0.7, 'r', 'LineWidth', 2.0); plot(ax, dates, [records.weight_expected]*0.8, 'LineWidth', 2.0, 'Color', [244, 191, 66]/255); box(ax, 'off'); - xlim(ax, [min(dates) max(dates)]); + if numel(dates) > 1; xlim(ax, [min(dates) max(dates)]); end if nargin == 1 set(ax, 'XTickLabel', arrayfun(@(x)datestr(x, 'dd-mmm'), get(ax, 'XTick'), 'uni', false)) else From 9049cde114d5cb75dc979dd30b124d754d22b0e8 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 17 Jan 2018 14:27:18 +0000 Subject: [PATCH 026/393] Comments submission change Newline charecters added --- +dat/updateLogEntry.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/+dat/updateLogEntry.m b/+dat/updateLogEntry.m index ad512209..1d5a86a7 100644 --- a/+dat/updateLogEntry.m +++ b/+dat/updateLogEntry.m @@ -12,7 +12,7 @@ function updateLogEntry(subject, id, newEntry) if isfield(newEntry, 'AlyxInstance')&&~isempty(newEntry.comments) data = struct('subject', dat.parseExpRef(newEntry.value.ref),... - 'narrative', newEntry.comments); + 'narrative', mat2DStrTo1D(newEntry.comments)); alyx.putData(newEntry.AlyxInstance,... newEntry.AlyxInstance.subsessionURL, data); newEntry = rmfield(newEntry, 'AlyxInstance'); From 46173ff67942bafef6a0d436b87a07a43970b3fc Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 18 Jan 2018 13:03:23 +0000 Subject: [PATCH 027/393] registerFile updated --- +exp/Experiment.m | 3 ++- +exp/SignalsExp.m | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/+exp/Experiment.m b/+exp/Experiment.m index e86502e7..b0229875 100644 --- a/+exp/Experiment.m +++ b/+exp/Experiment.m @@ -780,7 +780,8 @@ function saveData(obj) [subject,~,~] = dat.parseExpRef(obj.Data.expRef); if strcmp(subject,'default'); return; end % Register saved files - alyx.registerFile(subject,[],'Block',savepaths{end},'zserver',obj.AlyxInstance); + alyx.registerFile(savepaths{end}, 'mat',... + obj.AlyxInstance.subsessionURL, 'Block', [], obj.AlyxInstance); % Save the session end time alyx.putData(obj.AlyxInstance, obj.AlyxInstance.subsessionURL,... struct('end_time', alyx.datestr(now), 'subject', subject)); diff --git a/+exp/SignalsExp.m b/+exp/SignalsExp.m index 81438129..4932a1e4 100644 --- a/+exp/SignalsExp.m +++ b/+exp/SignalsExp.m @@ -835,7 +835,8 @@ function saveData(obj) [subject,~,~] = dat.parseExpRef(obj.Data.expRef); if strcmp(subject,'default'); return; end % Register saved files - alyx.registerFile(subject,[],'Block',savepaths{end},'zserver',obj.AlyxInstance); + alyx.registerFile(savepaths{end}, 'mat',... + obj.AlyxInstance.subsessionURL, 'Block', [], obj.AlyxInstance); % Save the session end time alyx.putData(obj.AlyxInstance, obj.AlyxInstance.subsessionURL,... struct('end_time', alyx.datestr(now), 'subject', subject)); From 0f4c94198455b80463d95438b9c2180773f4afbd Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 18 Jan 2018 13:06:09 +0000 Subject: [PATCH 028/393] Parameters now registered to Alyx --- +dat/newExp.m | 13 ++++++++++++- +hw/Timeline.m | 3 ++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/+dat/newExp.m b/+dat/newExp.m index ae7a3e76..9c01f24b 100644 --- a/+dat/newExp.m +++ b/+dat/newExp.m @@ -128,6 +128,17 @@ % now save the experiment parameters variable superSave(dat.expFilePath(expRef, 'parameters'), struct('parameters', expParams)); - +% save a copy in json +% if exist('savejson', 'file') +% % save server copy only +% jsonPath = fullfile(fileparts(dat.expFilePath(expRef, 'parameters', 'master'))); +% savejson('parameters', expParams, jsonPath, 'Parameters.json'); +% else +% warning('JSONlab not found - hardware information not saved to ALF') +% end + +% Register our parameter set to Alyx +alyx.registerFile(dat.expFilePath(expRef, 'parameters', 'master'), 'mat',... + url, 'Parameters', [], AlyxInstance); end \ No newline at end of file diff --git a/+hw/Timeline.m b/+hw/Timeline.m index 3739c1bf..49302b58 100644 --- a/+hw/Timeline.m +++ b/+hw/Timeline.m @@ -457,7 +457,8 @@ function stop(obj) [subject,~,~] = dat.parseExpRef(obj.Data.expRef); if ~isempty(obj.AlyxInstance) && ~strcmp(subject,'default') try - alyx.registerFile(subject,[],'Timeline',obj.Data.savePaths{end},'zserver',obj.AlyxInstance); + alyx.registerFile(obj.Data.savePaths{end}, 'alf',... + obj.AlyxInstance.subsessionURL, 'Timeline', [], obj.AlyxInstance); catch warning('couldnt register files to alyx'); end From 9243c5cafcc7157c5ecc94cfcd4197996910874c Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 18 Jan 2018 19:08:02 +0000 Subject: [PATCH 029/393] Fix'd bug with default subject Can continue to run when logged out if subject is default --- +dat/newExp.m | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/+dat/newExp.m b/+dat/newExp.m index 9c01f24b..c5184e88 100644 --- a/+dat/newExp.m +++ b/+dat/newExp.m @@ -138,7 +138,9 @@ % end % Register our parameter set to Alyx -alyx.registerFile(dat.expFilePath(expRef, 'parameters', 'master'), 'mat',... - url, 'Parameters', [], AlyxInstance); +if ~strcmp(subject,'default') + alyx.registerFile(dat.expFilePath(expRef, 'parameters', 'master'), 'mat',... + url, 'Parameters', [], AlyxInstance); +end end \ No newline at end of file From 4758d874701d5648e8d3bfb854efd3653672cc70 Mon Sep 17 00:00:00 2001 From: nsteinme <nick.steinmetz@gmail.com> Date: Sat, 20 Jan 2018 18:28:32 +0000 Subject: [PATCH 030/393] add a signal generator for step changes in output level --- +hw/DaqSingleScan.m | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 +hw/DaqSingleScan.m diff --git a/+hw/DaqSingleScan.m b/+hw/DaqSingleScan.m new file mode 100644 index 00000000..2bb53282 --- /dev/null +++ b/+hw/DaqSingleScan.m @@ -0,0 +1,29 @@ +classdef DaqSingleScan < hw.ControlSignalGenerator + %HW.DaqSingleScan Outputs a single value, just changing the level of the + %analog output + % + % + + properties + Scale % multiplicatively scale the output + % for instance, make this a conversion factor between your + % desired output units (like mm of a galvo, or mW of a laser) and + % voltage + end + + methods + function obj = DaqSingleScan(scale) + obj.DefaultValue = 0; + obj.Scale = scale; + end + + function samples = waveform(obj, v) + % just take the first value (if multiple were provided) and output + % it, scaled, as a single number. This will result in the analog + % output channel switching to that value and staying there. + samples = v(1)*obj.Scale; + end + end + +end + From 0be784746a1a9c71df6a1eb375f545471a8be282 Mon Sep 17 00:00:00 2001 From: nsteinme <nick.steinmetz@gmail.com> Date: Mon, 22 Jan 2018 17:30:50 +0000 Subject: [PATCH 031/393] improve alyx warning message --- +exp/SignalsExp.m | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/+exp/SignalsExp.m b/+exp/SignalsExp.m index 81438129..fb478b62 100644 --- a/+exp/SignalsExp.m +++ b/+exp/SignalsExp.m @@ -839,8 +839,9 @@ function saveData(obj) % Save the session end time alyx.putData(obj.AlyxInstance, obj.AlyxInstance.subsessionURL,... struct('end_time', alyx.datestr(now), 'subject', subject)); - catch - warning('couldnt register files to alyx because no subsession found'); + catch ex + warning('couldnt register files to alyx'); + disp(ex) end end From ee0498b2c39c39af8cfe8820019ff648e3c8f624 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 24 Jan 2018 11:08:06 +0000 Subject: [PATCH 032/393] Updated newExp to save in JSON * newExp also registers parameters file in JSON is available, otherwise the mat file is registered instead. * updateLogEntry now saves with all newlines replaced with '\n'. --- +dat/newExp.m | 41 ++++++++++++++++++++++++++--------------- +dat/updateLogEntry.m | 2 +- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/+dat/newExp.m b/+dat/newExp.m index c5184e88..6e2c0720 100644 --- a/+dat/newExp.m +++ b/+dat/newExp.m @@ -9,9 +9,10 @@ % |_ expSeq/ % % If experiment parameters are passed into the function, they are saved -% here. If an instance of Alyx is passed and a base session for the -% experiment date is not found, one is created in the Alyx database. -% A corresponding subsession is also created. +% here, as a mat and in JSON (if possible). If an instance of Alyx is +% passed and a base session for the experiment date is not found, one is +% created in the Alyx database. A corresponding subsession is also +% created and the parameters file is registered with the sub-session. % % See also DAT.PATHS % @@ -128,19 +129,29 @@ % now save the experiment parameters variable superSave(dat.expFilePath(expRef, 'parameters'), struct('parameters', expParams)); -% save a copy in json -% if exist('savejson', 'file') -% % save server copy only -% jsonPath = fullfile(fileparts(dat.expFilePath(expRef, 'parameters', 'master'))); -% savejson('parameters', expParams, jsonPath, 'Parameters.json'); -% else -% warning('JSONlab not found - hardware information not saved to ALF') -% end - -% Register our parameter set to Alyx -if ~strcmp(subject,'default') - alyx.registerFile(dat.expFilePath(expRef, 'parameters', 'master'), 'mat',... + +try % save a copy of parameters in json + % First, change all functions to strings + f_idx = structfun(@(s)isa(s, 'function_handle'), expParams); + fields = fieldnames(expParams); + paramCell = struct2cell(expParams); + paramCell(f_idx) = cellfun(@func2str, paramCell(f_idx),'UniformOutput', false); + expParams = cell2struct(paramCell, fields); + % Generate JSON path and save + jsonPath = fullfile(fileparts(dat.expFilePath(expRef, 'parameters', 'master')),... + [expRef, '_parameters.json']); + savejson('parameters', expParams, jsonPath); + % Register our JSON parameter set to Alyx + if ~strcmp(subject,'default') + alyx.registerFile(jsonPath, 'json', url, 'Parameters', [], AlyxInstance); + end +catch ex + warning(ex.identifier, 'Failed to save paramters as JSON: %s.\n Registering mat file instead', ex.message) + % Register our parameter set to Alyx + if ~strcmp(subject,'default') + alyx.registerFile(dat.expFilePath(expRef, 'parameters', 'master'), 'mat',... url, 'Parameters', [], AlyxInstance); + end end end \ No newline at end of file diff --git a/+dat/updateLogEntry.m b/+dat/updateLogEntry.m index 1d5a86a7..fefad909 100644 --- a/+dat/updateLogEntry.m +++ b/+dat/updateLogEntry.m @@ -12,7 +12,7 @@ function updateLogEntry(subject, id, newEntry) if isfield(newEntry, 'AlyxInstance')&&~isempty(newEntry.comments) data = struct('subject', dat.parseExpRef(newEntry.value.ref),... - 'narrative', mat2DStrTo1D(newEntry.comments)); + 'narrative', strrep(mat2DStrTo1D(newEntry.comments),newline,'\n')); alyx.putData(newEntry.AlyxInstance,... newEntry.AlyxInstance.subsessionURL, data); newEntry = rmfield(newEntry, 'AlyxInstance'); From 5b9d1f03c612bdea8b79152bd47313df98eadbdc Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 24 Jan 2018 18:30:53 +0000 Subject: [PATCH 033/393] Cleaned up repo * Removed legacy and unused code * Moved Nick's rig-specific code to cortexlab directory --- +eui/Log.m | 44 - +hw/DaqDataManager.m | 28 - +hw/DaqLaser.m | 89 - +hw/DaqRewardValve.m | 161 - +hw/RewardController.m | 35 - +hw/RewardValveControl.m | 1 - +hw/calibrate.m | 5 +- +hw/devices.m | 8 - addRigboxPaths.m | 5 +- cb-tools/SuperWebSocket/Config/log4net.config | 69 - .../SuperWebSocket/Config/log4net.unix.config | 69 - cb-tools/SuperWebSocket/InstallService.bat | 2 - cb-tools/SuperWebSocket/Newtonsoft.Json.dll | Bin 358400 -> 0 bytes .../SuperWebSocket/SuperSocket.Common.dll | Bin 29696 -> 0 bytes .../SuperWebSocket/SuperSocket.Common.pdb | Bin 89600 -> 0 bytes .../SuperWebSocket/SuperSocket.Common.xml | 1224 ---- .../SuperWebSocket/SuperSocket.Facility.XML | 467 -- .../SuperWebSocket/SuperSocket.Facility.dll | Bin 15872 -> 0 bytes .../SuperWebSocket/SuperSocket.Facility.pdb | Bin 42496 -> 0 bytes .../SuperWebSocket/SuperSocket.SocketBase.dll | Bin 94720 -> 0 bytes .../SuperWebSocket/SuperSocket.SocketBase.pdb | Bin 196096 -> 0 bytes .../SuperWebSocket/SuperSocket.SocketBase.xml | 4974 ----------------- .../SuperSocket.SocketEngine.XML | 1045 ---- .../SuperSocket.SocketEngine.dll | Bin 71680 -> 0 bytes .../SuperSocket.SocketEngine.pdb | Bin 165376 -> 0 bytes .../SuperSocket.SocketService.exe | Bin 11264 -> 0 bytes .../SuperSocket.SocketService.exe.config | 24 - .../SuperSocket.SocketService.pdb | Bin 24064 -> 0 bytes cb-tools/SuperWebSocket/SuperWebSocket.XML | 1762 ------ cb-tools/SuperWebSocket/SuperWebSocket.dll | Bin 62464 -> 0 bytes cb-tools/SuperWebSocket/SuperWebSocket.pdb | Bin 216576 -> 0 bytes cb-tools/SuperWebSocket/UninstallService.bat | 2 - cb-tools/SuperWebSocket/WebSocket4Net.dll | Bin 88576 -> 0 bytes cb-tools/SuperWebSocket/log4net.dll | Bin 286720 -> 0 bytes cb-tools/burgbox/+plt/binoErrorbar.m | 24 - cb-tools/burgbox/+plt/errorbar.m | 23 - cb-tools/burgbox/+plt/hshade.m | 37 - cb-tools/burgbox/+plt/vshade.m | 37 - cb-tools/jsonlab/AUTHORS.txt | 36 - cb-tools/jsonlab/ChangeLog.txt | 47 - cb-tools/jsonlab/LICENSE_BSD.txt | 25 - cb-tools/jsonlab/LICENSE_GPLv3.txt | 674 --- cb-tools/jsonlab/README.txt | 335 -- .../jsonlab/examples/demo_jsonlab_basic.m | 161 - cb-tools/jsonlab/examples/demo_ubjson_basic.m | 161 - cb-tools/jsonlab/examples/example1.json | 23 - cb-tools/jsonlab/examples/example2.json | 22 - cb-tools/jsonlab/examples/example3.json | 11 - cb-tools/jsonlab/examples/example4.json | 34 - .../jsonlab/examples/jsonlab_basictest.matlab | 361 -- cb-tools/jsonlab/examples/jsonlab_selftest.m | 12 - .../jsonlab/examples/jsonlab_selftest.matlab | 121 - cb-tools/jsonlab/examples/jsonlab_speedtest.m | 21 - cb-tools/jsonlab/jsonopt.m | 32 - cb-tools/jsonlab/loadjson.m | 520 -- cb-tools/jsonlab/loadubjson.m | 478 -- cb-tools/jsonlab/mergestruct.m | 33 - cb-tools/jsonlab/origsavejson.m | 386 -- cb-tools/jsonlab/savejson.m | 435 -- cb-tools/jsonlab/saveubjson.m | 490 -- cb-tools/jsonlab/varargin2struct.m | 40 - cb-tools/urlread2/http_createHeader.m | 11 - cb-tools/urlread2/http_paramsToString.m | 62 - cb-tools/urlread2/license.txt | 24 - cb-tools/urlread2/urlread2.m | 371 -- cb-tools/urlread2/urlread_notes.txt | 86 - cb-tools/urlread2/urlread_todos.txt | 13 - cb-tools/urlread2/urlread_versionInfo.txt | 13 - {+dat => cortexlab/+dat}/findNextSeqNum.m | 0 {+dat => cortexlab/+dat}/subjectSelector.m | 0 {+hw => cortexlab/+hw}/DaqLever.m | 0 {+hw => cortexlab/+hw}/DaqLick.m | 0 {+hw => cortexlab/+hw}/DaqPiezo.m | 0 cortexlab/+hw/daqControllerForValve.m | 28 - cortexlab/+srv/RemoteMPEPService.m | 21 +- 75 files changed, 16 insertions(+), 15206 deletions(-) delete mode 100644 +hw/DaqDataManager.m delete mode 100644 +hw/DaqLaser.m delete mode 100644 +hw/DaqRewardValve.m delete mode 100644 +hw/RewardController.m delete mode 100644 cb-tools/SuperWebSocket/Config/log4net.config delete mode 100644 cb-tools/SuperWebSocket/Config/log4net.unix.config delete mode 100644 cb-tools/SuperWebSocket/InstallService.bat delete mode 100644 cb-tools/SuperWebSocket/Newtonsoft.Json.dll delete mode 100644 cb-tools/SuperWebSocket/SuperSocket.Common.dll delete mode 100644 cb-tools/SuperWebSocket/SuperSocket.Common.pdb delete mode 100644 cb-tools/SuperWebSocket/SuperSocket.Common.xml delete mode 100644 cb-tools/SuperWebSocket/SuperSocket.Facility.XML delete mode 100644 cb-tools/SuperWebSocket/SuperSocket.Facility.dll delete mode 100644 cb-tools/SuperWebSocket/SuperSocket.Facility.pdb delete mode 100644 cb-tools/SuperWebSocket/SuperSocket.SocketBase.dll delete mode 100644 cb-tools/SuperWebSocket/SuperSocket.SocketBase.pdb delete mode 100644 cb-tools/SuperWebSocket/SuperSocket.SocketBase.xml delete mode 100644 cb-tools/SuperWebSocket/SuperSocket.SocketEngine.XML delete mode 100644 cb-tools/SuperWebSocket/SuperSocket.SocketEngine.dll delete mode 100644 cb-tools/SuperWebSocket/SuperSocket.SocketEngine.pdb delete mode 100644 cb-tools/SuperWebSocket/SuperSocket.SocketService.exe delete mode 100644 cb-tools/SuperWebSocket/SuperSocket.SocketService.exe.config delete mode 100644 cb-tools/SuperWebSocket/SuperSocket.SocketService.pdb delete mode 100644 cb-tools/SuperWebSocket/SuperWebSocket.XML delete mode 100644 cb-tools/SuperWebSocket/SuperWebSocket.dll delete mode 100644 cb-tools/SuperWebSocket/SuperWebSocket.pdb delete mode 100644 cb-tools/SuperWebSocket/UninstallService.bat delete mode 100644 cb-tools/SuperWebSocket/WebSocket4Net.dll delete mode 100644 cb-tools/SuperWebSocket/log4net.dll delete mode 100644 cb-tools/burgbox/+plt/binoErrorbar.m delete mode 100644 cb-tools/burgbox/+plt/errorbar.m delete mode 100644 cb-tools/burgbox/+plt/hshade.m delete mode 100644 cb-tools/burgbox/+plt/vshade.m delete mode 100644 cb-tools/jsonlab/AUTHORS.txt delete mode 100644 cb-tools/jsonlab/ChangeLog.txt delete mode 100644 cb-tools/jsonlab/LICENSE_BSD.txt delete mode 100644 cb-tools/jsonlab/LICENSE_GPLv3.txt delete mode 100644 cb-tools/jsonlab/README.txt delete mode 100644 cb-tools/jsonlab/examples/demo_jsonlab_basic.m delete mode 100644 cb-tools/jsonlab/examples/demo_ubjson_basic.m delete mode 100644 cb-tools/jsonlab/examples/example1.json delete mode 100644 cb-tools/jsonlab/examples/example2.json delete mode 100644 cb-tools/jsonlab/examples/example3.json delete mode 100644 cb-tools/jsonlab/examples/example4.json delete mode 100644 cb-tools/jsonlab/examples/jsonlab_basictest.matlab delete mode 100644 cb-tools/jsonlab/examples/jsonlab_selftest.m delete mode 100644 cb-tools/jsonlab/examples/jsonlab_selftest.matlab delete mode 100644 cb-tools/jsonlab/examples/jsonlab_speedtest.m delete mode 100644 cb-tools/jsonlab/jsonopt.m delete mode 100644 cb-tools/jsonlab/loadjson.m delete mode 100644 cb-tools/jsonlab/loadubjson.m delete mode 100644 cb-tools/jsonlab/mergestruct.m delete mode 100644 cb-tools/jsonlab/origsavejson.m delete mode 100644 cb-tools/jsonlab/savejson.m delete mode 100644 cb-tools/jsonlab/saveubjson.m delete mode 100644 cb-tools/jsonlab/varargin2struct.m delete mode 100644 cb-tools/urlread2/http_createHeader.m delete mode 100644 cb-tools/urlread2/http_paramsToString.m delete mode 100644 cb-tools/urlread2/license.txt delete mode 100644 cb-tools/urlread2/urlread2.m delete mode 100644 cb-tools/urlread2/urlread_notes.txt delete mode 100644 cb-tools/urlread2/urlread_todos.txt delete mode 100644 cb-tools/urlread2/urlread_versionInfo.txt rename {+dat => cortexlab/+dat}/findNextSeqNum.m (100%) rename {+dat => cortexlab/+dat}/subjectSelector.m (100%) rename {+hw => cortexlab/+hw}/DaqLever.m (100%) rename {+hw => cortexlab/+hw}/DaqLick.m (100%) rename {+hw => cortexlab/+hw}/DaqPiezo.m (100%) delete mode 100644 cortexlab/+hw/daqControllerForValve.m diff --git a/+eui/Log.m b/+eui/Log.m index 3172fa76..2821326f 100644 --- a/+eui/Log.m +++ b/+eui/Log.m @@ -110,50 +110,6 @@ function buildUI(obj, parent) 'RowName', [],... 'ColumnWidth', obj.columnWidths,... 'CellSelectionCallback', @obj.cellSelected); - -% obj.Table = uitable('Style', 'popupmenu', 'Enable', 'on',... -% 'String', {''},... -% 'Callback', @(src, evt) obj.showStack(get(src, 'Value')),... -% 'Parent', vbox); -% -% % set up the axes for displaying current frame image -% obj.Axes = bui.Axes(vbox); -% obj.Axes.ActivePositionProperty = 'Position'; -% obj.Image = imagesc(0, 'Parent', obj.Axes.Handle); -% obj.Axes.XTickLabel = []; -% obj.Axes.YTickLabel = []; -% obj.Axes.DataAspectRatio = [1 1 1]; -% -% % configure handling mouse events over axes to update selector cursor -% obj.Axes.addlistener('MouseLeft',... -% @(src, evt) handleMouseLeft(obj)); -% obj.Axes.addlistener('MouseMoved', @(src, evt) handleMouseMovement(obj, evt)); -% obj.Axes.addlistener('MouseButtonDown', @(src, evt) handleMouseDown(obj, evt)); -% obj.Axes.addlistener('MouseDragged', @(src, evt) handleMouseDragged(obj, evt)); -% -% bottombox = uiextras.HBox('Parent', vbox, 'Padding', 1); -% -% obj.PlayButton = uicontrol('String', '|>',... -% 'Callback', @(src, evt) obj.playStack(),... -% 'Parent', topbox); -% obj.StopButton = uicontrol('String', '||',... -% 'Callback', @(src, evt) obj.stopStack(),... -% 'Enable', 'off',... -% 'Parent', topbox); -% obj.SpeedMenu = uicontrol('Style', 'popupmenu', 'Enable', 'on',... -% 'String', {'', '', '', '', ''},... -% 'Value', find(obj.PlaySpeed == 1, 1),... -% 'Parent', topbox,... -% 'Callback', @(s,e) obj.updatePlayStep()); -% -% obj.FrameSlider = uicontrol('Style', 'slider', 'Enable', 'off',... -% 'Parent', bottombox,... -% 'Callback', @(src, ~) obj.showFrame(get(src, 'Value'))); -% obj.StatusText = uicontrol('Style', 'edit', 'String', '', ..., -% 'Enable', 'inactive', 'Parent', bottombox); -% set(vbox, 'Sizes', [24 -1 24]); -% set(topbox, 'Sizes', [-1 24 24 58]); -% set(bottombox, 'Sizes', [-1 160]); end end diff --git a/+hw/DaqDataManager.m b/+hw/DaqDataManager.m deleted file mode 100644 index 3a5e9d6c..00000000 --- a/+hw/DaqDataManager.m +++ /dev/null @@ -1,28 +0,0 @@ -classdef DaqDataManager - %HW.DAQDATAMANAGER [Unused] Interface for adding and configuring DAQ channels - % This class was started, presumably by Chris, at an unknown time and - % appears to have been created to manage the channels in the - % HW.DAQCONTROLLER object in a more user-friendly way. - % - % Perhaps this would be useful for creating and configuering a - % hardware.mat file (that used by SRV.EXPSERVER and MC to load and - % configure task-related hardware) in a more automated fashion. - % - % TODO: Finish this class, perhaps ask Chris what his aim was with this - % Part of Rigbox - - % xxxx-xx CB created - - properties - end - - methods - function id = manageAnalogOutputChannel(chan, defaultValue) - end - - function submit - end - end - -end - diff --git a/+hw/DaqLaser.m b/+hw/DaqLaser.m deleted file mode 100644 index fb51b1ba..00000000 --- a/+hw/DaqLaser.m +++ /dev/null @@ -1,89 +0,0 @@ -classdef DaqLaser < hw.RewardController - %DAQLASER Controls a laser via a DAQ to deliver reward - % Must (currently) be sole outputer on DAQ session - - properties - DaqSession % should be a DAQ session containing just one output channel - DaqId = 'Dev1' % the DAQ's device ID, e.g. 'Dev1' - DaqChannelId = 'ao1' % the DAQ's ID for the counter channel. e.g. 'ao0' - DaqOutputChannelIdx = 2 - % for controlling the reward valve - OpenValue = 5 - ClosedValue = 0 - MeasuredDeliveries % - PulseLength = 10e-3 % seconds - StimDuration = 0.5 % seconds - PulseFrequency = 25 %Hz - end - - properties (Access = protected) - CurrValue - end - - methods - function createDaqChannel(obj) - obj.DaqSession.addAnalogOutputChannel(obj.DaqId, obj.DaqChannelId, 'Voltage'); -% obj.DaqSession.outputSingleScan(obj.ClosedValue); - obj.CurrValue = 0; - end - function open(obj) - daqSession = obj.DaqSession; - if daqSession.IsRunning - daqSession.wait(); - end - daqSession.outputSingleScan(obj.OpenValue); - obj.CurrValue = obj.OpenValue; - end - function close(obj) - daqSession = obj.DaqSession; - if daqSession.IsRunning - daqSession.wait(); - end - daqSession.outputSingleScan(obj.ClosedValue); - obj.CurrValue = obj.ClosedValue; - end - function closed = toggle(obj) - if obj.CurrValue == obj.ClosedValue; - open(obj); - closed = false; - else - close(obj); - closed = true; - end - end - function samples = waveformFor(obj, size) - % Returns the waveform that should be sent to the DAQ to control - % reward output given a certain reward size - sampleRate = obj.DaqSession.Rate; - - nCycles = ceil(obj.PulseFrequency*obj.StimDuration); - nSamples = nCycles/obj.PulseFrequency*sampleRate; - samples = zeros(nSamples/nCycles, nCycles); - nPulseSamples = obj.PulseLength*sampleRate; - samples(1:nPulseSamples,:) = obj.OpenValue; - samples = samples(:); - end - - function deliverBackground(obj, size) - % size not implemeneted yet - lasersamples = waveformFor(obj, size); - samples = zeros(numel(lasersamples), numel(obj.DaqSession.Channels)); - samples(:,obj.DaqOutputChannelIdx) = lasersamples; - daqSession = obj.DaqSession; - if daqSession.IsRunning - daqSession.wait(); - end - daqSession.queueOutputData(samples); - daqSession.startBackground(); - time = obj.Clock.now; - obj.CurrValue = obj.ClosedValue; - logSample(obj, size, time); - end - - function deliverMultiple(obj, size, interval, n, sizeIsOpenDuration) - error('not implemented') - end - end - -end - diff --git a/+hw/DaqRewardValve.m b/+hw/DaqRewardValve.m deleted file mode 100644 index e99b9bd7..00000000 --- a/+hw/DaqRewardValve.m +++ /dev/null @@ -1,161 +0,0 @@ -classdef DaqRewardValve < hw.RewardController - %HW.DAQREWARDVALVE Controls a valve via a DAQ to deliver reward - % Must (currently) be sole outputer on DAQ session - % TODO - % - % Part of Rigbox - - % 2013-01 CB created - - properties - DaqSession; % should be a DAQ session containing just one output channel - DaqId = 'Dev1'; % the DAQ's device ID, e.g. 'Dev1' - DaqChannelId = 'ao0'; % the DAQ's ID for the counter channel. e.g. 'ao0' - DaqOutputChannelIdx = 1 - % for controlling the reward valve - OpenValue = 6; - ClosedValue = 0; - MeasuredDeliveries; % deliveries with measured volumes for calibration. - % This should be a struct array with fields 'durationSecs' & - % 'volumeMicroLitres' indicating the duration the valve was open, and the - % measured volume (in ul) for that delivery. These points are interpolated - % to work out how long to open the valve for arbitrary volumes. - end - - properties (Access = protected) - CurrValue; - end - - methods - function createDaqChannel(obj) - obj.DaqSession.addAnalogOutputChannel(obj.DaqId, obj.DaqChannelId, 'Voltage'); - obj.DaqSession.outputSingleScan(obj.ClosedValue); - obj.CurrValue = obj.ClosedValue; - end - function open(obj) - daqSession = obj.DaqSession; - if daqSession.IsRunning - daqSession.wait(); - end - daqSession.outputSingleScan(obj.OpenValue); - obj.CurrValue = obj.OpenValue; - end - function close(obj) - daqSession = obj.DaqSession; - if daqSession.IsRunning - daqSession.wait(); - end - daqSession.outputSingleScan(obj.ClosedValue); - obj.CurrValue = obj.ClosedValue; - end - function closed = toggle(obj) - if obj.CurrValue == obj.ClosedValue; - open(obj); - closed = false; - else - close(obj); - closed = true; - end - end - function duration = openDurationFor(obj, microLitres) - % Returns the duration the valve should be opened for to deliver - % microLitres of reward. Is calibrated using interpolation of the - % measured delivery data. - volumes = [obj.MeasuredDeliveries.volumeMicroLitres]; - durations = [obj.MeasuredDeliveries.durationSecs]; - if microLitres > max(volumes) || microLitres < min(volumes) - fprintf('Warning requested delivery of %.1f is outside calibration range\n',... - microLitres); - end - duration = interp1(volumes, durations, microLitres, 'pchip'); - end - function ul = microLitresFromDuration(obj, duration) - % Returns the amount of reward the valve would delivery by being open - % for the duration specified. Is calibrated using interpolation of the - % measured delivery data. - volumes = [obj.MeasuredDeliveries.volumeMicroLitres]; - durations = [obj.MeasuredDeliveries.durationSecs]; - ul = interp1(durations, volumes, duration, 'pchip'); - end - - function sz = deliverBackground(obj, sz) - % size is the volume to deliver in microlitres (ul). This is turned - % into an open duration for the valve using interpolation of the - % calibration measurements. - if nargin < 2 - sz = obj.DefaultRewardSize; - end - duration = openDurationFor(obj, sz); - daqSession = obj.DaqSession; - sampleRate = daqSession.Rate; - nOpenSamples = round(duration*sampleRate); - samples = zeros(nOpenSamples + 3, numel(obj.DaqSession.Channels)); - samples(:,obj.DaqOutputChannelIdx) = [obj.OpenValue*ones(nOpenSamples, 1) ; ... - obj.ClosedValue*ones(3,1)]; - if daqSession.IsRunning - daqSession.wait(); - end -% fprintf('Delivering %gul by opening valve for %gms\n', size, 1000*duration); - daqSession.queueOutputData(samples); - daqSession.startBackground(); - time = obj.Clock.now; - obj.CurrValue = obj.ClosedValue; - logSample(obj, sz, time); - end - - function deliverMultiple(obj, size, interval, n, sizeIsOpenDuration) - % Delivers n rewards in shots spaced in time by at least interval. - % Useful for example, for obtaining calibration data. - % If sizeIsOpenDuration is true, then specified size is the open - % duration of the valve, if false (default), then specified size is the - % usual micro litres size converted to open duration using the measurement - % data for calibration. - if nargin < 5 || isempty(sizeIsOpenDuration) - sizeIsOpenDuration = false; % defaults to size is in microlitres - end - if isempty(interval) - interval = 0.1; % seconds - good interval given open/close delays - end - daqSession = obj.DaqSession; - if daqSession.IsRunning - daqSession.wait(); - end - if sizeIsOpenDuration - duration = size; - size = microLitresFromDuration(obj, size); - else - duration = openDurationFor(obj, size); - end - sampleRate = daqSession.Rate; - nsamplesOpen = round(sampleRate*duration); - nsamplesClosed = round(sampleRate*interval); - period = 1/sampleRate * (nsamplesOpen + nsamplesClosed); - signal = [obj.OpenValue*ones(nsamplesOpen, 1) ; ... - obj.ClosedValue*ones(nsamplesClosed, 1)]; - blockReps = 20; - blockSignal = repmat(signal, [blockReps 1]); - nBlocks = floor(n/blockReps); - - for i = 1:nBlocks - % use the reward timer controller to open and close the reward valve - daqSession.queueOutputData(blockSignal); - time = obj.Clock.now; - daqSession.startForeground(); - fprintf('rewards %i-%i delivered.\n', blockReps*(i - 1) + 1, blockReps*i); - logSamples(obj, repmat(size, [1 blockReps]), ... - time + cumsum(period*ones(1, blockReps)) - period); - end - remaining = n - blockReps*nBlocks; - for i = 1:remaining - % use the reward timer controller to open and close the reward valve - daqSession.queueOutputData(signal); - time = obj.Clock.now; - daqSession.startForeground(); - logSample(obj, size, time); - end - fprintf('rewards %i-%i delivered.\n', blockReps*nBlocks + 1, blockReps*nBlocks + remaining); - end - end - -end - diff --git a/+hw/RewardController.m b/+hw/RewardController.m deleted file mode 100644 index cefbf257..00000000 --- a/+hw/RewardController.m +++ /dev/null @@ -1,35 +0,0 @@ -classdef RewardController < hw.DataLogging - %HW.REWARDCONTROLLER Abstract interface for controlling reward devices - % Detailed explanation goes here - % - % Part of Rigbox - - % 2012-10 CB created - - properties - DefaultRewardSize = 2.5 % reward size if no size was specified - end - - properties (Dependent = true) - DeliveredSizes - DeliveryTimes - end - - methods (Abstract) - %deliverBackground(size) call deliver to deliver a reward of the - %specified size and return before completion of delivery. - sz = deliverBackground(obj, sz) - deliverMultiple(obj, size, interval, n) %for calibration - end - - methods - function value = get.DeliveredSizes(obj) - value = obj.DataBuffer(1:obj.SampleCount); - end - function value = get.DeliveryTimes(obj) - value = obj.TimesBuffer(1:obj.SampleCount); - end - end - -end - diff --git a/+hw/RewardValveControl.m b/+hw/RewardValveControl.m index c79cebe8..aa94b999 100644 --- a/+hw/RewardValveControl.m +++ b/+hw/RewardValveControl.m @@ -9,7 +9,6 @@ properties Calibrations - % deliveries with measured volumes for calibration. % This should be a struct array with fields 'durationSecs' & % 'volumeMicroLitres' indicating the duration the valve was open, and the diff --git a/+hw/calibrate.m b/+hw/calibrate.m index 6bdbc0e5..50cc778f 100644 --- a/+hw/calibrate.m +++ b/+hw/calibrate.m @@ -1,6 +1,9 @@ function calibration = calibrate(channel, rewardController, scales, tMin, tMax) %HW.CALIBRATE Performs measured reward deliveries for calibration -% TODO. This needs sanitising and incoporating into HW.REWARDCONTROLLER +% This function is used by srv.expServer to return a water calibration. It still requires some scales to be attached to +% the computer. TODO: Sanitize and integrate into HW.REWARDVALVECONTROL +% +% See also HW.REWARDVALVECONTROL % % Part of Rigbox diff --git a/+hw/devices.m b/+hw/devices.m index 0912ce62..82598ab6 100644 --- a/+hw/devices.m +++ b/+hw/devices.m @@ -65,14 +65,6 @@ % end %% Set up controllers -if isfield(rig, 'rewardCalibrations') && isfield(rig, 'rewardController')... - && ~isfield(rig, 'daqController') &&... - ~isa(rig.rewardController, 'hw.DummyFeedback') - % create a daq controller based on legacy rig.rewardController - rig.daqController = hw.daqControllerForValve(... - rig.rewardController, rig.rewardCalibrations); -end - if init if isfield(rig, 'daqController') rig.daqController.createDaqChannels(); diff --git a/addRigboxPaths.m b/addRigboxPaths.m index 301feee0..2fa54a8b 100644 --- a/addRigboxPaths.m +++ b/addRigboxPaths.m @@ -38,10 +38,7 @@ function addRigboxPaths(savePaths) cortexLabAddonsPath,... % add the Rigging cortexlab add-ons rigboxPath,... % add Rigbox itself cbToolsPath,... % add cb-tools root dir - fullfile(cbToolsPath, 'burgbox'),... % Burgbox - fullfile(cbToolsPath, 'jsonlab'),... % jsonlab for JSON encoding - fullfile(cbToolsPath, 'urlread2')... % urlread2 for http requests - ); + fullfile(cbToolsPath, 'burgbox')); % Burgbox % guiLayoutPath,... % add GUI Layout toolbox % fullfile(guiLayoutPath, 'layout'),... % fullfile(guiLayoutPath, 'Patch'),... diff --git a/cb-tools/SuperWebSocket/Config/log4net.config b/cb-tools/SuperWebSocket/Config/log4net.config deleted file mode 100644 index efa786b7..00000000 --- a/cb-tools/SuperWebSocket/Config/log4net.config +++ /dev/null @@ -1,69 +0,0 @@ -<?xml version="1.0" encoding="utf-8" ?> -<log4net> - <appender name="errorAppender" type="log4net.Appender.RollingFileAppender"> - <filter type="log4net.Filter.LevelMatchFilter"> - <levelToMatch value="ERROR" /> - </filter> - <filter type="log4net.Filter.DenyAllFilter" /> - <File value="Logs\err.log" /> - <PreserveLogFileNameExtension value="true" /> - <appendToFile value="true" /> - <rollingStyle value="Date" /> - <datePattern value="yyyyMMdd" /> - <layout type="log4net.Layout.PatternLayout"> - <conversionPattern value="%date [%thread] %-5level %logger - %message%newline" /> - </layout> - </appender> - <appender name="infoAppender" type="log4net.Appender.RollingFileAppender"> - <filter type="log4net.Filter.LevelMatchFilter"> - <levelToMatch value="INFO" /> - </filter> - <filter type="log4net.Filter.DenyAllFilter" /> - <File value="Logs\info.log" /> - <PreserveLogFileNameExtension value="true" /> - <appendToFile value="true" /> - <rollingStyle value="Date" /> - <datePattern value="yyyyMMdd" /> - <layout type="log4net.Layout.PatternLayout"> - <conversionPattern value="%date [%thread] %-5level %logger - %message%newline" /> - </layout> - </appender> - <appender name="debugAppender" type="log4net.Appender.RollingFileAppender"> - <filter type="log4net.Filter.LevelMatchFilter"> - <levelToMatch value="DEBUG" /> - </filter> - <filter type="log4net.Filter.DenyAllFilter" /> - <File value="Logs\debug.log" /> - <PreserveLogFileNameExtension value="true" /> - <appendToFile value="true" /> - <rollingStyle value="Date" /> - <datePattern value="yyyyMMdd" /> - <layout type="log4net.Layout.PatternLayout"> - <conversionPattern value="%date [%thread] %-5level %logger - %message%newline" /> - </layout> - </appender> - <appender name="perfAppender" type="log4net.Appender.RollingFileAppender"> - <filter type="log4net.Filter.LevelMatchFilter"> - <levelToMatch value="INFO" /> - </filter> - <filter type="log4net.Filter.DenyAllFilter" /> - <File value="Logs\perf.log" /> - <PreserveLogFileNameExtension value="true" /> - <appendToFile value="true" /> - <rollingStyle value="Date" /> - <datePattern value="yyyyMMdd" /> - <layout type="log4net.Layout.PatternLayout"> - <conversionPattern value="%date %logger - %message%newline" /> - </layout> - </appender> - <root> - <level value="ALL" /> - <appender-ref ref="errorAppender" /> - <appender-ref ref="infoAppender" /> - <appender-ref ref="debugAppender" /> - </root> - <logger name="Performance" additivity="false"> - <level value="ALL" /> - <appender-ref ref="perfAppender" /> - </logger> -</log4net> \ No newline at end of file diff --git a/cb-tools/SuperWebSocket/Config/log4net.unix.config b/cb-tools/SuperWebSocket/Config/log4net.unix.config deleted file mode 100644 index d6f35702..00000000 --- a/cb-tools/SuperWebSocket/Config/log4net.unix.config +++ /dev/null @@ -1,69 +0,0 @@ -<?xml version="1.0" encoding="utf-8" ?> -<log4net> - <appender name="errorAppender" type="log4net.Appender.RollingFileAppender"> - <filter type="log4net.Filter.LevelMatchFilter"> - <levelToMatch value="ERROR" /> - </filter> - <filter type="log4net.Filter.DenyAllFilter" /> - <File value="Logs/err.log" /> - <PreserveLogFileNameExtension value="true" /> - <appendToFile value="true" /> - <rollingStyle value="Date" /> - <datePattern value="yyyyMMdd" /> - <layout type="log4net.Layout.PatternLayout"> - <conversionPattern value="%date [%thread] %-5level %logger - %message%newline" /> - </layout> - </appender> - <appender name="infoAppender" type="log4net.Appender.RollingFileAppender"> - <filter type="log4net.Filter.LevelMatchFilter"> - <levelToMatch value="INFO" /> - </filter> - <filter type="log4net.Filter.DenyAllFilter" /> - <File value="Logs/info.log" /> - <PreserveLogFileNameExtension value="true" /> - <appendToFile value="true" /> - <rollingStyle value="Date" /> - <datePattern value="yyyyMMdd" /> - <layout type="log4net.Layout.PatternLayout"> - <conversionPattern value="%date [%thread] %-5level %logger - %message%newline" /> - </layout> - </appender> - <appender name="debugAppender" type="log4net.Appender.RollingFileAppender"> - <filter type="log4net.Filter.LevelMatchFilter"> - <levelToMatch value="DEBUG" /> - </filter> - <filter type="log4net.Filter.DenyAllFilter" /> - <File value="Logs/debug.log" /> - <PreserveLogFileNameExtension value="true" /> - <appendToFile value="true" /> - <rollingStyle value="Date" /> - <datePattern value="yyyyMMdd" /> - <layout type="log4net.Layout.PatternLayout"> - <conversionPattern value="%date [%thread] %-5level %logger - %message%newline" /> - </layout> - </appender> - <appender name="perfAppender" type="log4net.Appender.RollingFileAppender"> - <filter type="log4net.Filter.LevelMatchFilter"> - <levelToMatch value="INFO" /> - </filter> - <filter type="log4net.Filter.DenyAllFilter" /> - <File value="Logs/perf.log" /> - <PreserveLogFileNameExtension value="true" /> - <appendToFile value="true" /> - <rollingStyle value="Date" /> - <datePattern value="yyyyMMdd" /> - <layout type="log4net.Layout.PatternLayout"> - <conversionPattern value="%date %logger - %message%newline" /> - </layout> - </appender> - <root> - <level value="ALL" /> - <appender-ref ref="errorAppender" /> - <appender-ref ref="infoAppender" /> - <appender-ref ref="debugAppender" /> - </root> - <logger name="Performance" additivity="false"> - <level value="ALL" /> - <appender-ref ref="perfAppender" /> - </logger> -</log4net> \ No newline at end of file diff --git a/cb-tools/SuperWebSocket/InstallService.bat b/cb-tools/SuperWebSocket/InstallService.bat deleted file mode 100644 index 50530656..00000000 --- a/cb-tools/SuperWebSocket/InstallService.bat +++ /dev/null @@ -1,2 +0,0 @@ -SuperSocket.SocketService.exe -i -pause \ No newline at end of file diff --git a/cb-tools/SuperWebSocket/Newtonsoft.Json.dll b/cb-tools/SuperWebSocket/Newtonsoft.Json.dll deleted file mode 100644 index 67b9d3511ddbd23596535e262c468dc3b3df7385..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 358400 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%0Dc#`I4go9XL)5-<O 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™~ zh1;P2&@NfTGRx^~1kk#3nrb&JWlq+@Mr|y%>C)!R=Rr`Gws;JdK18%|vk@24oVv_? zj53E+Y@kw~rEwumm$yPyzppND;Ud6s=rHK$cIE+no^T?Bm+brrIP2_8s|tc>cJ^WA z3u^;K*z)X;&#&WhFAFR}t}yMdg#n<CJFu}(hPDxXoDXor1KUzVVZB*RM|c(%I(-4G zE+SQ$AljvP|Fbc^Ij2kG4SzX}21cj;(0`x?<c6}Quqn3yWI=lmY@tJw??kG-9M6jH zcz3rr1i;@n_;Ir&ziaSoeFDHp*PJu-$0#F1SqdyKV2J+mLVQ}UA}Ov(q*WPhmX+tx zY?Sd+-iYQ*VUgkcU>S)c3`t;p1my)@V`eBKX)PhmZwCLw1FzF1pDvia#YZ#DvKb!h zV|%cvr7GQvYxNOjc%V2LgGEk_XgTMN>eL%sYw)@f$o#%z7-YW;)BBu2IKMV__d!bj zr9fZ&xizYAlOv-O8F=uXxn6Lq7160&{ZH@kRqcoT&t!^s*NR?XjjfX^-^<V`?)zFC zroGOCZIsbY2OSP~N~DsOB#&l`a^?zbV$dmu10S}EMTIy9p;ZZ)IHoXoZbkA{&gpQD zDQAvhW|0DwhZm*U-x>hA8O-mJNEWg!ik1<014-hlm=w<apECEq3A;*KiG9V*VaLzt z_!b>$CH5@S`NE&`{xiYfoOnAT%^FqqHemUSM?Ug#0}f_^WbyZHa47KLK6EtK*!BJg z>advc4%P-Ew++cy2Yf02zwp2BANb>R6!^EF@$dY@T|Ws2?<L}U2jUSI*vA%uZA4t) z9f+I}dKWMsomgjFM(8NayrM{CyAFQtWZb1Ts6t-Q!;8qPsZpzkJsDM~C3H*wFj`w# z-Txk%MMd`>nDPLM^&@as6`Foc7kD2I6Nqx0>Es!5b(e(4#u#%kAJC(-;ut>CSU4+= zM~xrCh`K1i7RFie5uiKpv6w#rtimOPTH1<Ffm*Y{G4Po<e-5bGxdSI)HUnP(7N(hj zFZoSCTPM;RSRcm&!Y!%^z)(GQm|wxH8M!>mq!(g2;YU9)j9PNP=8W6=DkzdU0rLP3 z5h5Qw`mY%nG&Kg2G0+JprVc9(3~YfCwUzWOxG{}FC3>r(a~`r-=yiH66Q1&+sBa<y zSuR-VI@&4o4zr+F8TOjG)z3#w9qpTmj!a9ZS4QAl@QF#bpC#EarjAWEqIN!+gnTkv z(ya*_id-o0J8)?1toS}6P{j{0nt>ky^ShflEz%^&w7^KFG)mQV_~KoZit4!<vjmar zyrk4<O}cbSh9k*xbd8-?gL}b2H_uk}I7S}Jjm@?HkWqh?7}u*FW1><g3`baV7~hP* zPl!)UH|yZp;_EKCeHd5b;5tV>Lw}Dc56umNA|yeb;pj<O{Qow7zx`wUF!KBvajISl zoH3m=i1-T#Vnw9>!*xY4Yi2e5+jA_a{-U{!l|`g7IcH^oy5f|Gh*{m(Oh+{D55pGG zv^Np4pqkNI0@flS%c5EFD}*(qdx^7%x={T8BJZF7C@<2_Q0F7hNZ&joa8mM&oeBOq z4w4Zh4mxkx;unSfLP(Xjps8Yi^gAGV&&G79&43ibM5E$2griGbo>B386qywzi5AL# zBsmrJSdGh*XVhQ_kfR4vj~<MQKcYyru}W6OWLxx6Z0oT@_D@hc<(ddgp_ctce=;%a zlqoi;V@#PNn2RzO`Wx<C6FQBrtBf#MA6ZUjQ(f9(BJ0`m{|W)2ea%NAmZZ>?^o1M% zc|eQ^5^gN7GRjO?`sYga%t4oDH4-kHQ@>8w>#Ihz71EPq9?o6Nqg-)tr(7<4U1fyP zhdMNFCkz61!!_gq$b-Zr&9F`(YnFRqgtIT=9F_}uiHD`u3wC_KZ0m(}{CtL=2s?(K zgs-cNFxWhzm(#8qum~jsL<ce0`~h6^4Ai8P9VdPlkY_3^$ukXKR~ccjcMQ*TSXAc; zIsLE<r(XulF>)>>&rH~nXDxhPWrWpuX2Bwq4G@tNI}mCEw|S-o>d;YiPdV=<&$_T8 z&m4SRWrWq@UJn+UXHh5#daexTKq)*c44G{x&jppe=L&nOu0lM+hn36g!zm^nnEA!{ zqya4D9?yn!)wqcVj2cz5XCusU&2-4RSp1Qh8E6dX^JCD;VDaP~4qtSX!IQAfGD1yY zUrxm4><U;w@|%YPa!5c0ac)d_<!j3j9**HY3{7fy#5*prI~MQdtc{+N!i<>iyVp=7 zCUH28d%_GW4?eAYf&zfNif}JJR0y=Z2*7ZLn$e89p1Lp*r!Mr2KlI&UX$Fb`v3IwO zBsj?jSP&l^M@61QQJ-RyIRm9|v8l{J3n17ss987z=h-NE@F*D3a4BHDjd6_3FB@g8 zFdVR-w8wHQrjunFnR$YAmH1*REhY3G3UNH{<S9nN2Oowok$wOyvEj(|FgDrRoe-HU zX&5GcE<%hswlJ}`Ix)7`x@9ihB~#($6v}<t^*#a(wW92-`!7Pj_Fkra@O7}%>psD+ zBE~ZxHZbg4JTvm5FTNOZ186~7)JES4DlaQkVX{s4b+yklvkIrSV{NdF+Nf$--LA2D zhD~8jGdlerk->;AD@3@jug;0w!$)cHTvc5twXhL7tT(bxSKC>baqeTaJUSuXu+KZy zt-WD3Fwh!$)A4$>dN_CkLY{+=P2t#f8;>r2+G&$}>d+m-)4m)SU1gZjo`#hRS@fKu zoiDIAU*THJ6a!^k-4QP1_AT02c|C=kio1gKYC3vldds$f&=~20oTxUOI!O`@xdR*T z^`fQ8bIS0lFxeRxhsYP=K>dCyp>Z$^VZ{swk)xdUO{<a<^t7*`kFxZNrm`JKE2q3- zQ_$EJBMH+=oT16E+9Bf5ei7nX;K4jq8)iCOD`lawfng%EMnMDTnHeI*SWTA@#*)4{ z%q-yuj&h!nD=<`)2!`5%^Bm?O+~(36ADPtE`bn@!+A(lv71n@aUH#fsVsEu47ZT** zwmb-pv`@ove#t7>LT%#UQ3v57uDBvT73(jOBYYmX20GHwkXy-1+d6fA@TR$(Rz8^J z1s8|aY%keyx*H2=&J3=;=o>kt4{l7vT&@FG5KQ3LDuCw*k6+H<!5e;LQvrC`*Zc?% zJfFgBXIhvsitenGeCAXQ@kO5)?L;R?EQD3;N{H5Q3aKGNY;5-~!gHrfSYLGpId{37 zk1zTHUGNEY1#nvT#|Cq)QPu`^jZxNCAf9S;N@myfpQ(xv{!;Qgj;e_KNR1|v<k(6> zdT{@c_0$F69>*&`^CKncfwM~zZx6Oh636Nt?D#pOJ^5%^DTK7Tq~ffD>9nIO2Mchd z1dQzDifC{}Y}8PH$LbF=falO(-Z}KQjPdOba>lm@zKCH@eEvay9iRj30u<N@P%cQ0 z)!+T(_ld1PQbT_vhyF+p?w_8aKejzCH5Iy94!;KP$iQS2Rt)guM}dW@YD{mUZZq;K zW}p-7a)wF}jX*ERh;o3th6Bg~C_xeB1zX-}R~tG%a~8=yJL8rcm^m-NiU30@XZUQ2 zWKOn$k3$Zw#TxQ}#Y9vUxT@J$&F>rn$@VDt?QI8s=MaP)I@k{U&LIdppsOOR_?<%# zcIad~@H>Y<I|S#1<DpmodF899C4Aef$c%RWHV$XlW}<dd8!5ZhTM{&TNH-6xP<#LD zFJIaQo=m6>&4w;y>h}Stq`uLSD4S`2anjY9*AXkXw7^=D;3!l?1DGs92ZdH*9EQ_= z6SpE@eJ}EA8m;rdAKS#lLk)*~bOFGE34OwjAF;!(?dR;SW$++1AzMEtqA!!pBjpol zD8C3|qO_D>%<m0nk;6qO9>!iwP;kA2Uo+5G{FTw0-Iq^ChG@)JNQ4k;Rs04niPG`* z9$(&yGdcB(QrMg_NQWbA!U#P(3DFt2G=|U_s7InRL+c+Uk-J^bA{Y=ufJ@MH|Jsmx zMeZ8YVMby(FiIXHn-4E#8L?5v%s8em@3kckM|}scB-eD-N>nugu{cxY7%D7V!^b$& z8iZgCQ$~D&%V-V^2Fw_O9AOQCiOHv3hXO-7zitN|A3TDD)(->Qq#3rPLN6oSKqiAp zkLV^o*!r%pC!07>ha~$WT$e`(oLV>?duI$&<fgOJBUas6Bp7;T1TIGeqPZqM_*W#P zuqbt_ixMUHM^SR<1229~Kpl~d>yoG=+{`x9D7z9y?6{iBuZ+LatP@D3#@dDByEs`} zlKM+jU052tYF4${khRFe*tHXBSMAzFo(|VNDrjyZoeP^7h4rI^Ruk64sWBFOT#Us# zgC|ENyW`|2zjnR}k1H-f1@r*xcI#63;EhA~Kx`bp54vaD!ddsjCfKA<jfd+Jm~j+8 zm{HADFKd=qc+3Y`>S_i9zU&8}FQ!(IzkQdE6SxaMR($y`e_kf<$!P#)#+(B5T!Mw< z`#D6J5~(t7q!nuzR2kQh^*LRJ(d9X87RHwcPx-}wi(@lHnf~k%_Z;ln%$B<%Sz`z} zN~Ms?h_4!Tpf^+eNt+Jkk92`+qf7%Afj|nO^&lJllV`HLE=p?`bh1QGEk>rm^&rx3 z#&H~a33S6hn^iP2GMKF9k-Q4M&eQ3Pls^yce>A3nzHnSkhiL>EXGLRpRJ6dwjK)KV zf%Pz>vGL4k+z_R;qY>eL4y}(kliI1Zj+pr{yLX1xM@Fe9poVssb2_7V>H9@lmq;3g zbw4Uf92iaWS)$$;rHExGbwB1VoC?!vY9;fe<{Z`Un5a*=(jY#+tvh;~LHPXGd`gF+ zz_a>NO~L#Y&D3&eEO<;Kg?)F=j$<kAi6&x&zCSMGbU*{|qj2asi~6ejNLd<GXGU{e zj3{@3`B{3vuElW14veRn<;_qxBW}8%UOi{u6eY(+0YPB8#+J<3LuZ?64oqI33sxc0 zGHTWYnD7FDWU*?r;q04{HwE(w5xu}%T`!Q)g#LRlbK~!fl${tA(OQK}STP9}ve<t! z453>9DrqJ5+~$!V5HILvO@?(O8)j<0x*c;$s#L26a6>q=VYu+uj3uvD15_{keoZG~ zhNd9EIoO|*ZPyl(U0BtM)_K*MXerQ#A))yeYdgb9^`UDEn~HTv-orK(Y6eo<%`mHv z#^EYS>WQ=1u^8)8YqMinjuZ>D(lP8<j3qH6ESb*#s=C;+j4Ss@j&7nvC@kuPuF!-t z5L0|m^VTEf*&4~EH5$w^NWun_-|7t}Z6Y4gYB4BV+`Nl%vth?*b1;=om@X;DcXcI7 z75dr^c`NflY11lT&o+%&l5Oz0+?Yl6{44J@dr0a9@(tW-d#=QHP7lP;D2v$hjrD1? z$G!2{q1$MZ;7e?p@r-2{!I^#^b_)2d)vQ|AMW=Iu)7Z52MAH_v!zMh8Wqf*Q8lEZa zZIs=PFpRS4fYzgA16kR=Qs##tb7%%!I8L02FUECu;1gvU4G4q0JK+>Nge)=wcfra( zn){=9J~#=L)GF|d`uDl2K7w{-u<sA_gT`>1{|pKrLPYf#NVXkwlW)wlL=AP?-yc<n zjE$^W2qoT-L-~601ht_r!FmkQNes<~V=A`b&*9gd-wQjE=hDa>$s*s?_P4aiHuy&u zHQ>p8ah>x5<a6tJGfowW{sQyBA$*D2I_x}(xkL>+kmkd+u#p#oD~v4jLkoafPry`= zf)~Of$L2K??laYOc9|OVn%<|>C8{9nNv27x1@`nXk$CsdTS%ivn6K>3<MF}6P@O#+ zFJ{RDhBbp-FkEwlPkd-0#KHFN)^p+8<Msyb29BHe7vU2Vu1pk0A{<%_*T6jh#|EZ4 zMajXB5SG3NLLZ`#WZ;>SC>LOvHtT4@h(<X>bQ6+0#Th~_^G%Um;jX~F2%*ym?B|ia z;nWLWy;Cj5se!*kO{1fnB}&#>=)!u63c+rd``}l<2Qmk~R#PuB8CaMd{B#cbfqKi_ zPcZ8Ju`<^#SFxgNc|L@wSb%Y;v<tJKsL&GdtE83KTk8-^ygRTIwm4Cpfn;6=BhIJD zl29U|c`<l|x1)VneLI@VUP~CN8djX0#!Q!J44Ae~SqFLae~d#K6d8`g!f+gBN8&)M z3WC@;B<81jbqvsXK!f88+>hwkw{U$Hdka@s_8}_gmhisdIAx(MKlV>~PqIQxZqoBP zg-vzF@z3Sr0y^prU9|PeKgu^VgkA#ikH%J8=Gp|~%nG8Qt0%CWiQvw!k0SU0j8Y~O z-B=o8tzp*;Kf-uTe7KOlb?mCxLTa<{<QL<<_y`Mmvib}bSaEV7udYBa>7}>^ig(<z z5-!}26!u`b$nJW%C0oa2Q1X$<WnJU)o<u{Hsb){=^f%NF)$uQF#&pX{@aMG4gZxG} z@X{KSE@E33d)+ma^)SDzr=1GtSk&W42kbY-lrXgFGusS?oga6-r?Hqf24Z>x&5*?0 zFH4o@G--~zKk5$Y#sSk+QpE815WXl%Y9M~-UEoUTociq+X+x8usjZi4;}u00rMT+N z03rsf9<G+@#zEKl`1QswKNMVv1jhF0slnCS5DKo*hS9;NwP9Fri#7}nzMu_#gL|~0 zd+;@F=ny=v4K18GO^Pr>7#I9jTQqRy)Yle2Ym1tAVkf7VcMW6mx+9uB)<^|SJiKst z6?B#0_7}$+JmVaP#Rq@9fy#XjRE1i{m+$0mO{st6PAgxhS8@9^Jf!35A8dFh9iF8N zdyeWkFVD!SpJ6Rx;niif6QchF%Y@YdGGH`o-Nu~a%t^-ekA^h?Nno{y3z}1G&0PqO zt^hRbi$8|>8JLk$zW42WFASBdYL@EqhJ3LJ#g{i5m5ZUbz3f=A8P??+>2`5=B~o9E zA$H6k#gGZ31auMU(<r0EyivHaD8pI?0knnDNL(9X+pz@TG9LiLD0l1+m2km}eFH>S zJ;`{AFLQ9_Pze;>2yzEc5?hZj@4B>fZ4LBt#dbN>)mBrQ&JfyY$@d~JPKj+ptx(BW zLA$cio$FQ)8^Mn+3U|(|NyuYpTGunHtwtEu2AHHNKmiIp%A(QHz`I^DoyN0S88*&l zS>ss9eHxx*@yx>PDH>GjswUz!l)B<n{ONQoO=P>QP2w!^J^}YNj5pTAI6bLb+BmtW zorzoM#YOE*a=zD|96W{08Eh~QbaGZfdLj@@dIz|nCr&P%l^kMb$Q`rf5pVJ_J)Ln( zKdjx7ZxWn%&%>jw(d3YaTq#N~i5`E6>B+aPJ%baxz`bGII~eZPWAKpR)gCj3SdRlu z^q?I}!`*n!z!N|NPXeSk-MD=-1$Td(K&Iw;{0PK(E#l1kQwkZEYZ2m<&>9dUE^F}t zm*(K{6pZnvJFpJl1eXUd?5&5XFe$VFpG23(8+aN9FAk*O6K*D7I<uLy`$Dx<hatLd z<i1><6?)3nd$))TCT?^#1Dn8&mv#j<!+`uIy()W%{8j7;7i3!9&#I-e>~h6%EoR=- zI`R&Zvz`tLJwskDQ?i=1Suz0S=mOgHpbe+wvmi^z&qUGQLMc$aY{j?G4Fi=4`)$OJ z<0^aOv>!JU*jL%JzR<EHszxO$32Y_*p@~o%$p1Nb^M$qp;<EVX@$ushrDq_AzW4h^ z&^=9=&A<z!#9s7)cp%5E({Tr^umJcXJ{dzw3q@ES=S%UR7rYa;BAirs&cH6<C7<JT z@NO9LvH3J(Fm73AP<ju$<g}89^*E*}!rjPlkrxI=(m@xrx&2XEpnt!N&s@43+VSGv z-A(jYV48(KqcG0In;4h_*4Yt<K@m3h)LVOCU~Fo;usxWW68TB;=3baN>nK8LZzb<# z<TW<@ru8h-CuTsi541+ven4xBB%PGqCXit>b^8(=IG%YKUyNs7;pepLKlsFsT{x=3 z@`c{p%qH>~$}f^;lzI%mh?Qp+)YNu`7!2y`u&}mzBQsJ6)j;G%rD(U{VRwf1p^rz# zZg*rGV#plbvkWV`0HQsF4p4AIGJHaQmPaytyouMJ@r)6iBH7OFpB$6pWOnZ)+=r{H z8egE>lodwsg>Z(iYL5J<&mP%3y0Xzq$sCvL<7|!iC$e4)9u9jkgIRU#;!=g7qU}4f zXoX!Smhgbu9eJmX=oT+S-W+AkTg53k9jd+D$ITb1VM|Lv2GaTe$v+ka&i0QpxY{zt z|B;yRt0>V~p<-d>EMMU)rOPH%q_!Yka8j`uh_T_;D7yfQn-9AK+%k>B0@9<EtF5}x z-lKQwah>y1_}vP>X$he(5h)yhEhmY$j4+zc8kZq$RU{hTsSL$(P#KqbB}mH+#OEnY z^qzKo1!L$Cl0U%{U-cZim=ZPnbZmH0-);A{xj?T2;qa>tyiP!SYvgIWiA;}z6pcvY zhweno@`A_1`8lT39wTe@k$G5aY){Xnv+mT*Ids;8_Gjp9o7UOa5R^J2iMNa}R%gVu z&LUaIJC&hCbS6lQ&c2D&*(?8(&P2Mf&R*6!qg_O2M6}M>cKnmhBC`HJ(^*e2KbOvW z(LP3J4s;-&qs|gT-y)7|$c=Imr8vv@2}V{tYzwWoNajy*PGwM9ad@_}OfVl<p~2RO z7f8IfFe$}(+Vx#nkBQ!dDom5;yr&O6L~oeDf$=y`OwO@`rUoPjK2L4hmJ?Yx3(%Vv zaVHQ9&5=A<Jm0QUZB9JeZIekPaELo*dI;r>>tb<+Qrn@An5xbU&L^jfAi6)nu8R`& zBtS+&YfP^zBl-?9=HlW&*app^at9ir<KthJScR1)U02wpjAz1m<lD*utz7(Q1YSiZ zNcH+tefjNDyeYnA9CgV-GoDG;2mDI2F~{Wg2M&W&&P3=+FfIclCp@Z%s}}LRP>Ofn zsFD{_;tKPEPlPpK@7UoZia4MekC}+bQ;IL5(V614HldG=XP)yXoK?lPFsxtU$ft$t zB07>|effQiD4|}P_h^;mm!$dfi;QS(q$|~|>Rha?3@_gWS|bgobQ=b*Ur;@@@MaNZ zO^8V%6Vnb=h`OiX5<3}cxh9~4l^u8uwuaPk*7L%+YfO?@e44tT*s!w!pESbMV4}q> z$aZYfToYNV6(s1|O|G5KW0y<v1lJ9pN1nj;kiVfVY`iRp3-@Z#Yx)eMJ-GnEXZX4I zLhvU+$a`~5>7ZEPo{up)hH$8GF^E7%#*}U+5Bsstd@;g!0b$_58}WgCa_BLQ=@t$r z+=!>^MjY3sn}OF6>jGy+{4pHR9*^(}KZ$L>(esJ)8FN%_XWfQJB-&1ibl5Er_6~%d zfgKO*8{>*VyQ^T}+$2RuKME&vBn-^+L>G_3eFU${vBL8_hRg%M2!4LM>yenB>9xBa z?!X%&G#*RO;?Y%m^610Z$)oh}yXXd*^M-|I$c?femb*RVt{zS;I~*ql@mf1)1a$n& zfOMbQs~Q!B`#7fcCPXx&?S(lJ9scYByx}u}hVr-I%&Q8lBh~3J(iW##Mj5}{MZ&Kc zc$*#!d#MsV5ZU%2Bv|ef5@+BYO`~r&4Adggq%--vt7-JMg|k9AYP)hLfh+}bbIBQ@ zyjLw0#O55K^wl)hW&Dn_SzyJJtn7_0*86ljOTK=#&!PNQoE7+`+SzGcLBpAx`-`hJ z_*`xSwA%;#Ak8@j<5%xxktjxVJy1wZi!xw_#DiqI97a4LdS-xnONw=j9<VedY_P(^ zFE)K1=cmH(H*<yVhnl8fe-gTyW2_HhZ+!$XDr0co5hJI0iO<Ms(>xBQA(-45R+4Ds z+|b+u(~unBVAz~bo9?tZUu@EG?O%?!xeFc!zXo^Tkep#KFit%Yr=2J_IX+*5WlQ<D zt&?kv^)W(=HyXfE{z_4K;OJaR*rs9Up~;Hp;&6ow1smdS>GZftKJ?`6z@n$j6&NOO zWY<O`54N`>O0eNa#`PgAL;L<mdQJ;O>Cv&lC`<Tjqwq%$iTwGoJ|X@6Y>_F?|DFER z2>m$29AdqKHpdXyVD7iiJowxG4IPJ`mg702C^P4lHb5`Cpf`>ZC7BBNMHLHNs#<C8 z^`*4>{;P6(S8o$j@9xEeVzPK~FIZw&W=1jVGl+wEC5#vyg~`Um9sGKXiI^jE6tpmH zd(*ZzwS8@y-7R2e^P<r@j<$(oSTleleU#m;FfdTZFivt>^U#b0w^nZjW6tQw0t5!j zp=pe^jQg6xuap5fQK2`!8G+9s@HCFB@rHRqV^a??amrby9AyrHC;gMNRRX#?j^~U* zm+W@5Hz^llqvRrn5d0><1rVCWDB*KVAE#y_+}+f7a8Xw^z5=UDzQ75DigS&a<n?7< zE*FQyXJG~buO#u>=!<!6bYwmmMJ1=+mF({2c#ClZMx7z4ac=MYO%SKiO@!$E<>Xv_ z?d#XPK8>g+YZx{xI63_gy-!+#DtXK;i<w_jv5?JF*wQ8U7#P+Ul*lYdafy_{PoY^` zN?nJTa!=eI#$(<nJkWtd`qq~c3vQ;xj6yBLEi#$zh1OSafDDGmo=!j)>mxovC1EDu zyO(@wDs=z8hb)|PF=aBUx;^h?cdFK)H#s)WX&YFLRQI{ki<0<Uox~|)2p%Xl)A~7$ zet9N(6oEqI6<}yC14T4~N6eYG=xH1y@U@7aX59_40;C3yzsiJV;G2JN@nd0FcCbnj z!Q=?~IDB16<QxJy_aR=+f~HQq9fNs`Z^6dnE=UZns;S1i<q#;Q*r{4EV}8+)H@bXg zqPH8emu#)_;+vo3vVI|7?VhJARX8v%v+>=Ja6;cvN<Ur;$JEnUBW3}DS*4(;CE(>l zFaXwk*=<R|FTaFdaC=AKd-!jh->f=X&s~_95g&C<MlUzPQ|OJ@$h4N$eaS36yPk)= zA%@)f1^&tiqK|LS^U0ZmLPTR9bX!<o_d^S_41Fe{rV(?5So(^rhOCo_x!wTBIilaF zhk4bQHerIXfidN<nm$w~Ih$wKbUurdy!{gNl#`5n9p}oacMa-;tZUNzGQFVB`{o7D zW{evKxCIZB+Uu%0Vm;1RfDUU@e5;C3&}1S&`nag2FyXB??%{xj9Nmz7hn|Vv(<2qF z<QnTVm~$FcDmA#Xj<_4&msuuar*U|01Lrk9uU^UNt%lOi*Bxt_`o=>ENKI;1G<^=u zV*k57_J{a!WP(|=h1JFooN%%4K#O5K+6X>HI*#Xzz>f&T!ay`%Ze-Kt*$3HMvV7vm znG9@^0E`pb0EOl#Hcn>n#V{{ZpZ>?Gb2*;UF!*bAX*jgTQ7Y8*g$UVRao2w87y(I( zbe$HsI!F&R@LUle!PEEe$N5uT`T2YzYZ=y*g+9YAX@>`rG`-vD?w65xEEU(S4d(DM z1y>uE9Fkk?^cw*t(@y<|aq#75yX3gKnPn|zy2LqM2rPWXrM(mWeH}R8`pVDsrZ^ci zSAN5u+>|ik7>3^n5fS9r^BoBgtMRM@tLf4}#6_29*mJ~e>-ZALr{@hh#?ozCu<zYr zqht!+I3zmG4VKd-ghodX?(|``SOsIm<sx^=(lMN_|E}%pA)P(ai<77ra_wiBlg<nG zfY*WQdBGl&q6+*8nF{lgkx{>$jS5QeW-e8o8$4WH?wFjaw>7)$se`<#xNy0&kE!8Q z+?l>PfmrQ;0%T);U5xL+Gi0oa%-n>9Y8MeIeo-}Lp2b!Rg#{b`vVK?rUDhw}teH`1 zxYGLN1<&Bco3!jRSvrE@_VF5)Gif(HNG7(Ay4|d9&-9B^Par;rBtDUi{`SRJGE?W$ zwZVBVsgTel<mVV3nRMrBo7-OFXC=kDSl@cD*DBu0@9)w%s|i}3Otd^)L6FTs%+%~d z+KmL49ZHVpHw^Zut}DGOq52e^eUFc+hC%LEMeddKr7NBvkP&>YOx>JeB&PE5bK~dA zNiIstZ)i-Ii<Fns_Vyz&a(w~JD1VN?&&*=Mr@${rrh2&$zS*8)5mNn^dKD)f-bD2E zqgSk8X()-M!jB_;XV!-EM)^-0xGyM9?jS#0U2Zd2f+>lZC?t5YirgNETqD1m78y6* zOZK0@5KVocI{w(zzF^_vYT1ifLT}^H4{EzYzrf#=;N&n_%rP@5;yh|*`>-p~n5c*6 zIuuT~BzIF*Ybc3%26Gmw>i-f4p9^uI+LQC^*l}<MGGZcuJALbjzP}C2rmGtkTO9>G zlNcip(}8u_YYbc7{E28PE5E%C1+6&Rd{^LC@Ybsq`ZXZbu_s~0Tq+cp%-~%Zx?hJh zBkwEE&AUl{FPpb3@Eh62IU{V&8PM-%1jGvzA^~-eE<zbr8Z%6Eo5>&Wh;1f+;=@fS zMEMmQvQfr?_`Z(#>wcXpc-=kdg8v1=##ryChdJkCm=gF~Y=V<xUCBmo)3#j~MP2py zM8eQ}4&Oi+=0q%OXJf?QOwL<t{w@h6a2m`CGUN!h7SJVP2L20ss8iahw%~`3WlFvC zma5Bq^_mVGxY{)|hE)J31$=`e4*Bq^c?BHeAUAEvh!oq(*h1o70}F%2e8YMXFl2x# zZzit#@59;hbm+1GcmI$uxD-*-7oDNT;65(S$-jv>rKQG&eg!2f7qgrmLl;Y}VWhY; zp9XLZm|)H4>a<m(h*iJ|hfo|qysP3_?2nLrY8Ko>E?8F5O6;wjkg#G)n4&2uJfxtN z*ju|mfftj!u$AGQJK%#6ukFR-lTn(AJ%I@@S-U|O=no<D^Rx}-p=+dIvGgRFUIYgY z`>c^5OLg+*H3}czaP>s>U6Cx(_L}z+b@|VFZJ+G}*RtP=l&h>FbemY4q5#JSS3#Nf zk$Ab(2csZ%l~@Q|(plnLLgAh{nri9H1d<>{f@j_+X&C&=tOQ7l$kl)~tuoLRw_hA$ zzD`E?vZjs6T|@-yczWHJ=OvE7jekb)YbLAI_4*Ox+WHOSXpu7IovH~_kZ&>gu7L`x z05tr@&{)ZkAxwiwVxi$TmL?^J2Az4K;Ww71W(-Z4&4=Gun$##wjCCL_V!g52_@Op) zFGqq#;CARgkPf<lA0RSU;FkN*bbqeKy|zBgU^^Q3LUnrB6r@wIA1$kO<!}H=x^mV) zBnKu<cwsN|1jo==>{J=d1A0Y8cl?}IZBWXrdnLHb3_o7}F)Ox)^L8GyA=f@DX(jef zSyR${O}S%N`u-x6q+g7@&fsI=SYW8OfMIY22ea{Ufeb_(qc7|qlOv`WyUXY(vQF*C z_NbV_kZJviZmaZb`Xeqw*<M?@WQL9zr|JsYWAqO*no*z30R)PnZ%hQ(b{T{9nAJvy z;K^mq6lf}h-Vc3O)@=rPt0T}A_xI&@#x{v&piA&1dNHPM;Ef$pV{%$~@XPmeay~!V zl`#a@m$?evZaw2?@1@`@Psx^=IO>D02G*od@#~91nKo`U0UX{&cscDc`OT7vkdjOL zm=o9|K;y<SSH-q)lH@Z<<T6lNiGB3;!~{soBI-(WOZNwtBxh33$PK#WlD$hm)3Xn9 zOaBpTfa)FQjp#)Xpsxjgp)3GyrL!6$RF}1%ULbrnc}l+GoX5NH>&&Tlb-|_BhG#Wq zn&h{{%^&1Y@myH$0?NIFS&3F+FPmkhzK=KD)=OY+%Fv-vvcn!vZ42kK>k!V-vt+DI z8S8{)Y=RJwVlA*sr!MSJ|MHqLLr-C1HwQ6K%;SJA)WSI+w722ZgRx`}uQN5THj8jD z+AxP9vX-<`cw>_a#5p`PD`M155X;td$o5`X2jx*641*3Rp?<0ELV2k!KJ*);O!T_T z$;iO=g5Rl^T##|D7mY@GX_Dd9!PJLsexl*A(FQ=PJ!<#Vnd+K{$@1XA%fc0h;gtQp zT8Bn-pCVSuLa2uHnA-2lPs3yc^SCu0vuo1FLT<)jU-IoI_Pm{5r!raYs`=MstoG03 zB#lTcC$!hxtAWXLJ?98M>>g`5xcjYXVi?n0^={FUp11^qvS@UO>DPvAt06Q}$dpP8 zG=kBG4W9o_XUe=A)-Nn%cs+$=HA4dE?xalTl>*Jf)sZA9#O9xxsF)gqc^<Al#Z?z# zMApcuXPrmNa0aPBU!^aTv<!U?XQ&B$1m-}UI8DQkPJ=kv>-RqLO6U|k<9?iSQsbQ> z@mfu<9%1Bfo*dlA>i$mv`(}<1HYEFeQul|3!H8XPxYP9yFbs78uv!A(PIxFx_tiMQ z`2b;N#OJ5GdCi{%;qncCJT|lix`G(fD6nbdEBq)Dos*_9^%96Cp;)M)d|83KNSuPH z_n~VV6Fuy1B{lZ8@`K27EYSnu9%EepL>LKY<eIZ0;mHuc5|_W=;jFln*lB|FPLn=; z8}L64{)Xly-R5}AR%T%G8>3kd#sSiMMlsjDXdTVO!sE7fu(%tng_v0F5T4f6wcv?O zI#^eZ59Y$j&YMhzW<Z0t(@rMbkg&{aL(r5qj}NY-{Yl_cjDiMsZY|CVPe-E?XJf=` zb@Sz$$A?kbH%mu&A4(h^hi6<y%@#eh7C9zf`g3fLIEN_ho!$b+HiQ$y3|%)fa3`;; z@%@OrauTk~sd2USuFNymjhN$&tcW?m2va21mL0(|-IjC30~j>sOa#ny0n~B>WO)Fx zy#S%BBqvj*5eVyJh82vw324R!YsLg?#sq6df;Dok2yidGkuy<$UHXHwL$vkik5@jC zY#GF@w#bEMpa2ke)B<L6I#M<rpTjXL;wU<Ds>%3%0=cnItO%YHy=Hkce7(+cR7}`6 z8M}wTTSg~X)mjqH2&}M+VHqj`;CdZed%mEVpV1t11P5TqGxV;p(l8aCkRV;WTRV#! z`u&YhsXx0SLLKJUb#M!Y$vVx(nIWw6V99f9g0uwU)9Ig4oa8bxWSv8=D1_$)&qFxU zk3|0}Ja@_rQ&>NE9%3Vm&YA->b<PggRourRJWQ)QYYAOb)(>9yKQLhVN53$<r;*3a z^gK>)<bMdU`Yayd-fL6{_Kg~Zd6>bs&+@`8kenR04pQ;s*q02)l@m|6@+iXWf9KVQ zbwM>=Ji{Bqt0)={ri1*=(Ll&f(_r>e#8S?`qwDH#o@CoQZM&fcZnd&W2S!PI8--_5 zBF-$&1^9l3JW>O@=Ix!Y_M}`Z<baGKa_f)EAb3ZV8v-sqlU)~V)Y09SP-}Rc!ZoZI z)gTn524MBcjUOk&F71DQ2`_cVh)5lH=dE@Rlz&nqr{*muTYl5M;MDE`OKUFteBAc* zDR&NPy*lI0Nu^tt9DM$VxAy(F?2DnJcdVFnrSGLfrTgcu9dY9GZq2`_pX*3oncn;9 z-ivGZ^6eUUs7~Jl5B{;em*d8Zf10sqL;8Q7Sya9%CHO<95mhTbc*MDN)-^^aSKf&_ z@kJN3-qL4RomXl#ynfN{;cvINvCEXXyB_-0ov`|e0gW$e+hN`FH@~yu<ka^***353 z7U$9?N83Mf-Jc)dbLf?KYy95tqQ1kQ{IKMc2L7R6-hNHThFy||Pny=?@#{W}?|HQC z;-9z8>)!98(Eh94yS_Nx<Mmr=j30ebpWfXDynFsdC*lTGI<HR~H+|gUcV=vD^2zpx z-ygQ)&fC8K?gdrnsfPFXe{kG#eDa~GWhDpQU)*@bs)xF-KUJ{z=wqj+ET41Vn>X)w ztPZ^M#?;<hs-9@}uJygQ(ZwIO+8Y}EL0aXHuU);i_ki{4#x5%kr|v5H^sVh}Tb&x) z-8Jg_eLb#iwfIk8?G8KpRlU~b%WL}o+4toSlWKmDH{qGtkJLR_wx(H!(ep~p?&+VN zJaYNcQ^!5K2Mn5$ob=Qiy{CTJbK2044)&UVL+Q%LE*<>DxHsR~lfM1vs;{1$-0+$O z#`6c<jq}UrtvNsQKezWd__HUc>XCs<p1Gq>V9qAr6Y0&3$*<Jj_<6^*uU~oBwVnHJ zIXUZrdD}<M^!NC>Y;Wo6#bf@vcYUbGmq|@$Zx}xFlGC5RHS_PoT|eEn@TP0(_uV=& z>({wuFTXsdY47#}Zflfr=#xjg{&?Ty<ueA}{_tgu7d;o+65RRDwChf;OsKu){5S9H z(!V$Q&6uFUAJ=^+wJ2&{RNB0>n2ooJlXt$`k8!956-qsh?=SBt)u8``;HYsUP}kw^ zA}otOzZ7-@`>Vmn^;|-O9s_$_0Jo9Azk$C7UB(31Zu1XWIQg=iXXCw`BL1UMsq!-) z<6c1kH-49*1N&dEQrPgS=D{8XLf*;3Y!S;EB+?_gvG`(-gxU{=kHUN#%=niXXMX$O z_auI(dE_lKRIPR_UCLcUVJ0L&V5-2jHF98cd5xZcqr@fz=u=B;%!18kvDqc?Re?tZ z#wC9N*ILP{Mv`ii+yFBheUi!N8rZnhUCE<ibFbK}08CPAL6fAm2<6^nhV>u7B=u|Z z5m4gUW!Pk<kV9j@Bz0a2>H7%077#u26!M%Slq*ucfP@=UW&!RM_@=<WQa;7}NpVf` zzfxdG;0raM#+*r2&HaFQ4jrzYQfZzf=J+()T$RSqe-(3lI&HT3Nz*-x_Vw!0rm4Up zz$Dd5U`D;)bCOhg?cXt2>r^iX@|d}lsz6{jfnx;D6}Um*I|9EKShGIq+X@`p_|p`h zI@pA<=xi~>o=sb#|9he-&DZBq=NW}GzbWuY5nX>RqRq{vebKJhXi3=~YE7AcYxi+G z=(GLD?P{ve+8=FSQ*G)%xS}KBQGwk$9c}MdH+34;$*-mhTqtm}z&8bcA@E0mtvi#l zA7D)tI{#>Um%5=VwRClN!VW#YK<Ep5Gwt3Hm~r6|nAcxOn^y(?2pgAb<ENa9FQWbG z0fdhYB|I%Ka1G5@4<p<p@S$s&2iA^e9&nFgeA5KJH-;KGG?uVn9N~8X!lvU%^YP7u z*)yoOi8FfE$Wrb*z5r|qh+KMS%cef%pGBQFoyAmmNZ{$&$7cIfpE*p0&2t`@<5Hz_ z3Hu7Xb}soZ7I@*j2j<jOB@5~-sHqw)q&Bh^GsJK1CCpq#IQV|TJ@->TU#~d00yZmY z(|>K7wJtSgEx9dPdppeA)*hRUQ{7_olfaax=$a?6tH7%SP8N9gQ{?ujz|CTFNZ?mb zk$>tswQiNsWF7CtX}OM^2Lbxj1To*Sjx<Zf{9QK9hsEZg<SVl_ZECG29JHQgXUclY zf3Mg)A#j)2{|xgUWo%$5`5Q>zLEr@fFBEvGz`+8q2CS=wZ>Y1tr2=AezrfXE|H_6G zycPbMz#jz0KTVqk0y_%4MBq(;xIyk|hWHSmPkr(<Q*g`1H5;?ks~efV%{JBD1bH@* z=6->j1RfOl*(U1tuT5$lV!D|$%buZm<1_S?@hoi$pJlAuKFb>Ek!P6$pAq}L0uMb) z%3HUn8a36rtt?Z|34C9ma~o}H2+R=JMBsS>F9!6h5!+arZWlO9;HQ8!)%QYkN?_;b zj<)~a)^L~YEYZUSZrM%=UlI6`zzr|3Om*DBQgiVRma<tpm_r@}Oj4&_q<zhu4U&Co z^UgIJ;ddu%i7$5@*oE@4n;PpZaFD=&z`I*hOE2wi*%Wp4ZgTMKp&q*JVOYDwJa#W# zYwn}{$bIBlwvYMk)&0!X-|Q#n#Fq%03T*!p=`VYU^b1~^@RCcd7P#Xj(jOJ`w*s9n zPk70vJO@}puM#*%;C6w>1^y{8^&n}+9Apl^8PKKfI7t0$J4pG<Zz9|&=3`?2$3d28 z-yy<ThbA9#sWpd4Q|DFM<iARDFEP&%xLDvLuaf8bD$;yZMVcF4dteUQf;So@*Hz9p z>maWt3iJ!i6_^j`Qmx*k-}l}k^dF($7mhH$y#t8)=WV9Qi|@!KBX(;oeG@e3byXqS ze%(_^#wo-??4|YqpN7?o{y4zx#YiSeIYBuNEq)xj6?ppwZWBP6j7~FlK*8-+aVy2{ zbQ8Aofq8L@+{LOA*n`5ev+4`mWb_>h;j4?R`6a7o1nVj@&&ldV!7h;Te6o54Saa1~ zv4@ka-W0c<GX74+PDskxTcNjt(7y&2hn`7Lr2a*u9tMn(X{krxJsV2>Ur=74LN?}e zkju^J2R(=|G8{RezeW8ZT-rJq_gmDD;;$#%1_S$Du)%^&R;L9UWn+eeI-6=^Zo%%g zu|x+$SPKj<)2SL*xJ$+^e)62GQUyCA*c4Suuy1WFS1{8c<rLLOuv#|OR4~iNN*q8{ zd*do-qC&L;maHxTRtvGg-7E-ilyM98)l5^kToTyb#uCALJ05~$PXWVQFX|G(o`*kT z{Ui?m5z6UmfQ0uNut(G^>~CU9zXM<O)g1M`xP1+`$ASGMviu;j%uy$W%PGTUdes78 zjC(`VWo}i=RT}&!tJc80YK5w0(5<hytyBeqjS}oZ)l9G{HrCvr)C+(qq+PMNtpw%; zRw~$LU~SYY)l#rM<`ScWTCLiNzqiHTqquXMp?@X*9#y>!YS462gO5RXOwGE$YN@rr zsL%6&eS=-ZLxr-ZP(H1$7Pl*f@>v_3Ae39wwG#TBz`Sa!8lmL`maJY=cNo;*W{fwz z>Rq)-+;%#dDj%zz62jY{j03huu&)LCj2QeGaXF4L>Ps8T5bRrZP$)}*#UWQ8HXekQ z`T<kwCt!^2P+(qQ?~2<far;?)DQ=U1S&pC8ui|!>xc#CaCj8waZol9WGV**(+<sMV zaoZwpzp4bm4gvG3ld7g*?*qHQ@tdk6*bjpJj`Sx_!<FOc<2Z%Y4`QjnE&zsWae>vd zvCe`uw=wSNOjZ}$SZ~3u0Y)zUP~4N%cwmFoAL??!rn#tVmt(TYx&Rd=^qK5<*ko!h zg<CC^Ze!~OtLu1H{OtjDfvW4+Vmj(2tJi^bQw<%T3HG^-eJPZG0LxR29Y2}Vz_X^C zDc#udvryIr)=d>TS~w|L8*wXgw04rRr<*C=!7<#)eAXY>BdVigvXgn@DmU{)SI2AO zuL5q%p_#XwUih00Y!pUypNZR2aqH>$M*KYlf8A9dN0T^m*#@jPu>3fNen==Uax@q0 zO<>TY<3hnc0(JpP`xSzHCzJymx5cqM{0=Nm4RYKUr=>Qr{c032>Ltr#db$By7ROX6 z0_Me=;kXn7SVzGs95_A<Y#^|U@%9Ez%m5oFZqpn`B)oFL?s8xsHry5nHp}sig#Hk) zI5pSti%@O{)<!KP2FVTsn}N3nev5kuIr4K4>w>!-e+uRAz`Vfz5-i#4GXHX{bmY4{ z&|oWIqi~mAp^JLC5ZGPnAxE(*5pFkNbUX_8NVOEVAl$sT?We6^w*uRWJ2<)uwotHj zj$VR2E|kwWE*0z<U_-Hcvcg3<4|-XO4>)cY>|?=RwXt6WdyN=m!8UYg@GW4}M7m(V zI{xFTgk((wJLUL5DBB5^Z5%_SQDgd;U+NjZNC-pW7N_bPr-X784x(>GU3$t@3CgL! zyr?g6?g`j^;;#jse`I)D;MT`zVI;YsH1#Ij;!t0<7PrsDt&@!z@h)?*<9uTPCKi&F zAJ|rPfpN2&HA7Qi3fL`fO5GWl7uaOME*0z&V~+3~Em%Keo^ZKKu>QsZH_PTjz-nP` zZjn%KjAzR=z*y|&Hjkaa=AsT>;%1qCHy&?~q1N3DELnXFY$33%65h|^?`q>Y_YAmk z8paE^7u=`=R1&a-XcKn})(F@=YP7Leu=c=~svC{{68gXdhCar4MR<-BY^;H2K*8l^ z!NwbSz!TUU!6q2*3FQjGCK|`x)cjL|-C}$$lrI6>iuU+>#8#;<5?tmCHQ6{V{{9lm z$p&83fxnuGq?~NH*(p=!0h^(w7|G(+1(+9DnulBlBr>0s8yVs@T-<I4#&oz<uo*^e z4|Dk(Om2@-GmYjRrr7;(!y98pOYyf(DDN~{iND=~%{KZ8WtC9QHU<dgC$`%_Pds@3 zCOqdGlf++A5+z$;lnZ61V0RmH1ZxOvl3Hxc7s_IBTWl<pIGmrvI4m|63wEJkkJ;ED z!PWz#UWSRkjldYcae_T#tQ0vbfZ<jJW4}<&1~vuQOM)#G%4dzYMW3sH%}~!89|@PI zY`4z@+i7Dz2v#N7cB9bCT=#wwbKOp(i<hCF5VxI1ceo|1(#_0gdyPSY9e;+{E5=oV z{U+F}#tnj9_#EBdFvbbCSg<#Z@q)c5*jq+Wu>LsB=v7CI34+~%g&*`1j0(X%7VJyo z4#5s$FB#f*W1(Pyoy2}Kmc(Gc8_NW{V>jJS8OsHGdk?Wcj1_{_-An9G<3ZGixR*w- zzpycv@zXs2u7;g99*J@Lukom07rsm`run#FLBZn8Ck0z7n8#czn2xQ_TqoEc;+9}; z5bT;)$fbt4NwC#|)ij?G?0{hD<`%(f{)d#A<~G4D6D-TzF4zLW>X<JG_KIM+=8J;q z7&SC^3HHaU<kHC8BiIM85o=~160F|q#EQ)-!9s$Snr{fUM6ed-TY|kH*m>sLf_*Gl zOY>d9ba<`I_XNBC4f1SlejwN^!P=O|1zRUrd-Ef~jtbVn{6w(fZ_;09^E1J;oL$Tl zf@xh}V16l>PU-IE*MiM>i~f3=-+Eb__B%qXxA}dHzdq)#g6TYQk@=foTI!3<Q-bNd zdx`m{U|JLX%)h-Z*R3^k9D^$Fs97Iyp1}JAE*JQaz}0}o>V=vmj$HLY%~q(sKC9Ut z@MO)-fV)$t1OA;lAJCb$7%(~QKEUj><$#UTRsyz2TLm~Itur_bPkRdHXVV)R481eL z_4!u<CJU@3u)e^A8niDGa~px31a=pAiNIk3#|jJq_N$!g?+j_@`r8985x7d=I>21D z-#^cw42#e&e-$<kb=1Ec=Fj~P0{-ED1Ta41alq`1hJeKa`({jcgr!ZY^@uU3GF@Op zfyDwl2)sz(6#~ZyybZ9iicfpcaH)H0b;ich<$w<LRjr@E-^ij)T4!Oh7_c6yE3L!% zS(N83xH{AiS(N0mZ1$PQ3w%c4f$X0|heORll_#<vgjC-NOsGL~8^nkyOZcnU|5u<{ zn>O(RlLe*=%of;4V4lEYf$areAn;;={RIvMELOv6mqV)9lx>`LJ8Z_(z5{S@-PlyV z9dg$46V?})P=n?oF}D$TiNIk3XVj%@Cozu&WZwG({pi_sskem!m)1Q2n*-VHk?vh{ zPB{BjE(6R}ed@h~Zqa@98X75T{KjGNDeAY4-V4GhxVPR(ETJ6)%vHnclf$(7)bslK zPoWpKu<jASx((g~v;b37QG*X*?%d!rz&-+#B|kHzqCB7W4XV7^BAf<@w98{D_*G!d zd}q8vHOo%`>@9F?elpB+1+Ei#P~g}3>9CJ4V2ZaWAk8I(R{)MF92TFeHWp5S`L3ex zVE(Acm*7y}7S#YeU6cvfrx{D%jm<hF<f{9cG4y%Ok>Bi6_GI(2gh7?-1#S_z0}%UQ ziYZ%4G4;?$V0(e%ilb?_skjn;w+q}Y@BkoEq?n;tC36${SB@=N4LGahNx;WS)&uS= zsRVq#WHaC&C1rrMOHao2uWVJi4d!{JI}%FO+R{Ccym!mhfH$>#5^zb&&44>wt_S?2 zWhJ1u)%}T(tyLM!{aS4U47J(;cz>&tNadZaR>Ae-R*wV5wSEe)PV0?;En4qMV0o{b z#PVK0$)TQTT>$uW>tevIty=;f_O!!-Q>`sky;IcSEnoD`Ra4roLV29gb{l-nYr7jX z#qFq>LG3y_Qq;X$n3rDJGPF;MYPj`Nz!6*3MPUtZZa0@&0u0;iYPT6S`vsoYaC1Vx z%7enq)qZphhw`^4=aTl=k!+{Yv+bAF7*zSZz?}jQ0_Lji9o_`}lO1a(qwek43~)fF zcFDPFa;F~24Pfq{%zXaf#e@y}_NyG%nPuT-fwv2sD{#5MX9eyDbf|l}5bo;2{PsB@ zIXu@lMV)@`Iq<olYiE?KO983LxixduO<nJ<=}?oq-UoO)?2%pq*K{R^4P95lJ~l?5 zrmd_wsPapJe+$e?Ux~?v{@soO-q7t+z^UE716<VYcfd8>j8vT`P4z-ImbkR;qNVOn z`&a|yq&n2-?hOGacW3)DqdR5LZOz@?DMO$KZDs?uR8RCc;cTha2~4g{^RL;2o5g0I zz$1XA>S&Lvz;jJE=B}Z=n7e|#nBS&$W6qq{i#c;qH|EU#-Iz0P=*FD6x)*ccbG>rY z9cq6s=EHY-F&}>2i}~>9Ud-EL{TmaRyN(Y)4(vU|?@-_M9uC;<!kYl+U&u1M^umTl zOBEb?H8ww&4P|_%4ZS|2rMheAU9g!y^lrfWhdu(hXXq1v*_S_)!Iau`S&Hhp^OM1v zJ{NT(YpsO>AG@CBzf)P8={n7sc44htl{NfYK!y^|bx$^5g?2Wf24NdN;V^-<1U48> zoA$K{^TqsKHqEBkBn$jiY~saSEHpFflBTJ^RsxFvV^atDMAEBlM1w3yID%=tLEzyL z7Hqx|7&nsUx+9sgodsSyl9ZJKSBz{9*R3PBp`2b)*b3&uBME;R$ugcfioUvzx*i%E zGK!&e9vuYiG5Th}?H#5A>U!^u(S5VYVF=*ik%U`EUJv-w$mv<EAD$h`6dZ<nGWo`h z+02Lg06X0HN;X~ZzC5bQ-?Il*K64}CF2G!M^v1uli&f7ts}hUV)no2QPPl1I=i0?; z<`|~?12E^R&0{F%<nb%&IMgHKSq2AhX?g|QEW&%YJO$WuC*kZp8CT`1rZ?S(^4JQn z6nSQ6om|!JrhRo9U~S`svw@lg$U4x2)3YaTVkqt)VNS52k*lT)d^E^<`z3+z1yexT zAw-^4A<~@f%GL)ZaRPm{5~xe%iHS^$(-RqD-AR0>y2B*W=$7vCNo?<?PGaot5x8F9 zE0ZWs2}-m>>6US4%cH0_K56+WVDFaS0WN9zJ7B+721@0LNpFF(=Vn6d=1kP;^Rw=Y zv}@aopF|xy*8gOr*4<RRURta55u368)nY?<LGRVzu<sVi9NX%ZbZ0yG;Vp!J2&_4o zX5AW=jo5&R*~?IS{NM8`eRUp57@JqOj@&4D>^boHX(U^s!y{?dxwLRHIro^%{H$~A z`0TxK9WsjH4x7xfJY_O{EfUIbyT;teeCSX=!gWyP&r?n~u|24aFk4`uz%Bx>5jaiY zy#m(=d{N*V0)G&gIF%e43+yDYzre8q?+~~|;97yZ1im5gYk{W))+{F<OJHY#{RNH` zI91?mfvW^=2W+WcE5E3IxJK2n8=iJOY#2w>5*5EAzGWkr)84BnNQ#!e23hO_6Xp*m zY+swu6qrzh<~Dx9WU=|Vf_1{8Tj!#d*1fE)x3aHw{MOP2=!aJl1}Zzib$Vq-nE$9` z?`z+*=`g=L?XCtan{Qsn{=gT2J$L>WaKzS*!&B6}{R4(arP|$)ArhWW-`z0G!J7V* zD_1=@V|OF;>t?(}h~C|xO0AP`W>9ZBHUAQG(o9Jaf$ahNRbB*GtZtk+r%AE8ZRXM@ zDQe}+`_M}{|4wIov6^&eD+?0dd7+i7p1yN2N;i9q{VJcolWqC|z+Cm-oy(1ItNQt! z91Z;{_StuhhOhc}H8hIVpLhA2vaLxPnWCB;STkx+<=e9u`d0!^2~3+!n-+j6>f+gp zA;V~aGiRsNO;Ps<d}KE3mfSf9iU{8>Dpj55EXK-52(Va97dUIqpG7HZ_Z-zMS2dW+ z8mZIVqGqgPe;&hHQH{-2o95mOo8LClrnD+^T#6c7b>28XYQnzFS<}AS9JL&{p)}3^ z3FeFE|J^*A7TZt*tZz=ZMc}S>H1B{pS6x28c5$v6J-=~rvATEuP?#@XKp8F<cwoU% z*u1vj81_iB)kY`_6H9W{f`zqh8*1spn{w5xMR%2?sLgK<2r+LA2!-2pXIkUZs3h~z zK8{>WI6>g80x#`On+C%Piv_l?O>@4OzssiC6q{s$zlzNQ@w-gmDuMChI-~CVNIf~$ ze^DvNQ-ldM2-}Exn7~>BeSlF3@4)zL<6@5EZtBW0^7h3XS?^oSJhSlL`_E&Fe10?2 z!gouGy5sG#$;|&um)Uy;ga%c<wv0LUF7z}8RerH-IVAs0pt_&tR?BJMOW<V!hXJDY z+swLd-)8pWssLRo`I+}{v@r|tpIyKIS69!o<j{8IEtg=Y%L?>-1d>~ufz`vRvFdId z`Ko%Ju;ogO@6`Lx6MptQIs7Q_l)$(bIt+BFx-WDGhkxgD=L__E59rS={}H(UA~b)! zK(liP=@WKz2meNZ^i_yH9(6)(bQ$bYBVOzQsA+E7iIJL`{2=4CY<CJy6@9gbuw-wI zgjDsz-e%WePtOAla7ya%186tFVK>g9B&?{Lgq=6yYvCh<ucLjZkuLSziUUPa^V=}z zJ#a1T!>;8q=EX7QH8B6aVndA0S7+HcMx9BM37cIjV)>WCCLC68nE$TEb6CWtx>U0x z_Fq1ra}isI*j&{5LB@T-VZ!ACR}0*A*ciujq;3x$_Q3qcL!~&xl>Nx;fJ;qylxD5B zYahSRg2o>I2KE|i8}A#`w)q6}iG~9o<tfuEA7xnoWZ&!wrdP)&n07C(z8`z%-xhQJ zTlDqvYI3_O#=Jz#hhU$p?l{u86l44&ZIDB<)~vwUvXgI9whe0-i|5xY2VAz2=0Ddw zIK`#BYfm_<>S1zN&GaOuc7<l%v9ZRfy?}kBvCQtNmpBadm&R0|)XT7Mz?-Y^H&b0I znBT?*r(O;>G!pomrmh3#RP`EZY+C9t)awHTo2llej&*p{N4DGY)R4oczOb<;Q*Uu# z5vZ~Dw>-7Nk*Kb>h%L<QyM9YilDbK->7Jfxk2{joWWi>7#-wclHqFM$(zXMeCD?xN zthAku8tQJr_Ia12?FDwf?e=KeE0{%HWn-Ju4g*_bV>{B`0Jho24yL^e?0vzeLob&B zb2im-9!dKEm}O({rG1L_ZmNwP7mRe+l^gdd<m6>W{M#S*W!hIU*xza2$6#6Me*l{< zTxP0f>6Za}R<P-+MS2{@1Um)W>u8alfWx96=8@+#)d6m4s(C)K>8cm}rK`sUTcoZ@ zPczch4>ooaFuxjEp#4>(XB!!6n_wR~7QtVp+AY{#$GY_Gz>WyE-@$aRrG61?re}D@ z7GQtb*vyRWz~TzYbEfApxMitC8`}xDEakT`=DKWE*T$IZvK1cuqd&=YwN*(3TWi$8 zd>7rOsdqEB81>Ww!S*X^u%1dQ(r%w2yn1S@VEYyISr2nyblb0}&wA<|8)G`uQxlqN zH^!)*S|Zpq^`~%|P)xUJxaUkLOEi`#*vq9FYmiBQ^;&4GD04d`JI}`2X6{0(*vG~e zxb^|NhDS@lvuoy8j`|qUXzb$5Ek=E{wUx$(2=<MQjm|s(H!PcrzX_QkM+4Qz#_r6l zG8(GKZEOi-X{3I%u}3rCG8(D4w%Xs*neW1_mW^%Cd>3woHnza^0kHFJY+vTbzy{ga z>zOAoQ$0p7UH+P=j|F2I?K7H?J^uD5uCDcgVW}Urn`d3EZ-Je%v2C?3cUa13N6ML= zy|vzTG*vzud%M=hMpKn4m@ZX$s<w7hU9!Ap9_HC31P?<fP(uX!+%X_4*(^{u3igrX z+N>;KA?;7fW`UZiF>txuQK0S@Y`<_RR4W7{m!@W++8~(bQmD3Sf5N3u9f)A1p!`T< z9@Z1h)R%&3p3T&cw%a{f@51erjjhW1*l4B<USW#3Kb^J3(Omg#Y){s9V5u5Icx}yM z)zEfplHJ)XRRuOyn%xc9c{bJwZY@*?8@mv0EmU`nfy;&Fd1|C!TC!GZj7_-|{#vOC zHuf<5wNm9awmI!vqqUl0V{5W6ceGaXG$xv8qwclcZmGS+(MGMXu{&#T2lkj?GdxRb zzw2nLHrQ?t)}CXwRrNaPxUa8$H!#b_cGO-1tXMFe?(Nh_!KNwJrtQ>L8)I$SUcF~y ztWAYInC_2bd(>WiCvLi4YOhY(*rD2Vb9H3s`yIywOSZ9Z1*>Oce+!mpWASxXn(b9v z8_TV8nWKZcz{bvlTL*QKjrD+AM|Fi@Gu7bKN6e1ualvi}_N3WKE$PhAr>RToJYt@& znxC(+;dP!iyQnK{?56am&8}*?jrFMWf#U-8hK-F!2;I~dHpUvdn=0s{xm4EKYIaxS zZS0;paYheySy#I4b@Tw%Qw_1P$H1kRy4=P#33jE8y(HK*g59p(fxlkr+6%~KzvDBw z^;TnS?0dl`+1P1lqPM!m#@uyRnis0OY%CpEAGIigeQaE$mPfE%=EZ7t1lw=+RqG<y z0rL{IC4yC%{nX9~_Lh06dL@Ewcl1|pM6jdg0QG(ZJ8lkCpGB~}jzQ}C2==LYnfg6~ zeQ6F>P_V={z3zAB5ao+tzhVziS_J#cyj<0dV5aj5WkoQb^Gb#D;5L^O?DOjv!LprK ztML)6k@FfgKY}%L4pZwQSR3cH>QDsh>by>U6TvQWUau0mheNo`Ib2;4!G<|+P-`RD zjm{D3uLw5LIa2lM5%zbhbCg;d!R~U7R$C+3J<c1|+Y#&`=NNS|f<5INtI~UhxomTe zQ!OIc|Do!><FYpXIDp?}UKon96+z)az#>Bd(W0WFqT(J<R~?88oLKij5ci-U?omgz z)>TDtR|U~3ZdBYGN8O`!Rev|WMt}Up`}MipUGgM(GA?-zD}zzAspHBJ6mRORG89c~ zE~fjQt`o!P(;q_7RKw}DZzgf9k&L!+r66{!k&Fts#COgJR1(8ViX+t#=pKk2GmJp5 zgy>$X4gLw0Z^=p)@*~xe$b+i@vF%|bY6=<a%P7<y#J2a*=tmG+ug9Q)AhuqQMI%9M zuQ3kI0I~hcIJ6AJ_6+0DCJ;NG7>`bKrJ!~0U8rt?{&0`Q6VNNre)pR41f;ZLy-r0( zXvs-v6;~l*+g}RW4r1G13d#quZ7c<qnz}7bM*o>QD5oMHHej1B2}k!rISth_b)1e> zN;Gv^PDhhXotLMgg{Jo4X=t;l%XFOGVN*Bc8R)910z4DFHFZ~>h03=1()vSrHmYjs zsXPZon0h16MeR*}l;@!VrVc9^Xsjt&`57%UWu;`IKTRE1ve0F&badMNurePRZCUH- z=(>9gIU9W^M8CBk;f1IsR|@&P;$dYWiVzuHdk!m$*hKV?9+$g6!ixo{UbsI~zUqVf zJ7t-fDIS{o3+l&fDa2(xY}Hk0ppfC}k%w2IK_G9gp<Ln{ZxtFRGQ?U~h1PJTpmrV& z)wRg39q$Wb?fr^;+cTx0A3dVg4QLNnA!4onj$U!GBeGb$5q*S=)wvO^?C`agE$AJH zO}8CccVwBEE(bYq74p91pzuy#r^`bwu}m2#$)mNp2hHaat?xzaxI``c&=$y8>-*4N z5SxbstP1*9s7~~VQwxw1$HnL2Fme(iY$+8YS1z%X3X$cTl7(o(H`x(hBdyam-6K!> zsyQAj<ga2!Ye&tJzgIk~97WThPS(%AP{a=`&Oo^yOVlDX3smH>N<E3za24{;;S>t& z%u0%RKZhc?#Jm@yD9Biy#i$90&HF{vhARbK_E@i8L3crSsji}+F09TJ^n~gfTF)g~ zxQ=!U@#S&@?c);d-9Sg7Bx~;m3h63(#@f4$Dt8lNpL8io;1bi_M?)ZE)7?kwL2SB5 z=;JrjJw<fL9{m&3Jwq;BVhwqQnsTKeOXbb#OVkGBUwNnc8a)8La6hR2gF@H~9_TV- zExbitxl&MA<zp)Sk;5J=qvu@ir`7*Z4=yq9pU^Na(fVgJMu@~$&cmP4WYABQ_fRc_ z>DU}0+_fj7f1*7ZNA_Y$LDMQfRCS!zTV!mx5d0gLXu-fUd$246_1N~<-oW8}x(sxC zn-pb*_kL5-8W)IK@RQ0PRBK!Wa;>6jHuxN<PL*<+9liwWT*XbZ$2UO}s`zQn_zq}g zl@P50E(QHrrLN|NpMZ{6X|7equR!;zbkw}?dywSWOY^}vp3f1k;yGCR4$}h#`d5gn zdydrtu?}kOIYq01ZMnqUhGIt{lH@s03&oYVQpiZpMO0PIk}Kpe+(Be$u4gQ+g}3%$ zEu^4Do&{7(6PQxbub!*5aLoRvt=krP?$_#kk$TxP4}bqfwxynzzR?>l|9)(`Y|`25 ziS|8?;!3Ayk*}y?xKhb3*LPHLkPY@iI(rg^{pIVU{!gi1nwhaP)4RB?+4>x>JgOAY z9!mAH)$8JgQ0G!wOMSc&+FR@8O!XUNn`st_|A1@{%_8w`A->;jfD1%Pbkyst(g36W ztY_)yJXJ#+K0xS>m%H8w#}5>G!Zj;V=wGglT!luFr!Ose6C_yz^(gF+#7Y+8PmZ<p zCOAmQu(lw*DUJuxH+4~ELrHf_J*s6~>B!yEj4B5*A4><SQpf@<vA8KFgG66KEzUG! z|I_x=x|Z&GG_J<8Le$ujCpE_rP_nV5uihNb<H|s-EJO7ed=%8#GDdHS--CKtQuJ21 z{9slm{U)(Y(_7=3ps`eKaSPB)s`fY@w2-PJ9uHbiYl+3HL3vbhcqdmjIZX8fwj07) zV4A0Q#_^^W>0R(mQ)~6EIDF`rk{k7IILFj(y*qXo_9Z*2|A^<Ax}f*K&rRLdd*bNf zUrIjId*PL)p6k8wTT}n)@i;m8OG%mZ!Ou;VB?&lj#Fwlh>5B_YSx7$|`O}vyi1f#M zO+}Ca*kR<CtT7pg=b36t60yywFIhK|gyT)clR<c|sWJLs>^AyK$$?}DPBt}+48=vJ zMw4OKZHy>+#*#vYV~eS2BpC;r${-_fJyXlbPq>MxHDn}iZ)zJEg?pGfKt|&vQzyw7 zJjT>zG8U(rx<$s}S*9M4@p!(eXJi6iW$FW&h&Py0=!ow;Q+7rQE->Y0OvYzSc^j$t zx~X6z4gYPbt}z9_HPy^W$LiQG%cY|+72BHXYD~jUrg|IGvAd}O#tiIZYN#<22bvmX z%)+&}#C56JI1+TtGRc^2?)d_}FX7p^i^z=n-ut!LIG!sVP4%8{%)yCVnJCM9F;xLq zHd^Ps#+ZlCm|ARP;L9-GHt*k!O#HyqA5{M1`23+g-n)$~d>2&U-P@RttBe=fF`8v# zKdw}C%KMP905>!Bm$4A{=DLNic<158c#@EzR=q^E9#n(tBv(4>S@o2$7?+s3U@XC} zxeD;8s@IKWn4Uh-zgsw+YB{z6?XP-@J~8CLm5#1fMfwWt#+6F<*P7J|Ttj43WvqU| zO-vQat8hnC$K};H-qd+{4IXaljl348n%aYZ#hIp7$m{SjQ?jxiZ!~pW`3>iqvQjqS zqo&R(zvIVT`4amc+lX!0!?~!Cuy3wS*p4d$<@q>TZNlqQSeAkQ^l`V^if@9h_*ksA z;g@MF%a_=9V=jKrm5uKC1X|@{dJ0JYve7H5JZ!_2fsk*Q)lTe?&aw<->)Y6BH*U_w zt|Z>YdvH6hbbkGF5AF(LN7Q?8jwp%Px5ZvOZW^m4Ut;U$e!Pt<16B5IZ?zxa<toM1 zeY;s5z^}OqQFY&VtAp5MI-9N#h4@~=hj18(edip)ZMo9vH&B18L-<EigRJs#B3CJH zY#CuyfH!azAhw1S;=NqAa2wz8R!8u^P%_DPvei-SGDEair^<Azzi=|hwaR>}BJ44X zW#V}0H1;vI%IY)@GIdrtgCn@I(L|5mtj^*!Tw=+e!y83OGR^lKK8JI-QpghDT&r_< zA7q<pR*VmW_S38wp9G!s-Dh<kUjSYAZK<5cH$YE)3#~5T`=F1$$Elukr4lQ@LaU4T zEvT~JaVk8U&k<?wcgCs&8=wTgi&W)7qiJ>tR{+hX*(K}+`h{kf@pqtYG`oyLK>0Mg zf+IlZX?6uS20it=Zgmy61mSA;sXBois^#HpxH~AQ+8(ODpqOfBtghq1ptx!msYY?7 zq7l_zSlz%mpy^b%@N>{osyo<!4x8Ilw2kT>?gu(j?VZ(qyjF;A{kc|;a3NPZdFNlp z`fq%aD}|8ikyIBUbFbdi`Z1<IcuD`#Nv-N_sU8UNTApBOF3+eYT0g~ZTq$UO^;-Hf z+?A^o7gbNUevVg~nq&PEp9Ed29&hy;Uj*H+o^AaG-vm9azQp<;{1Eh}`bz76@k<a2 zSZDnfe*jqpY_@)fm3gc$rPw}TyY+i)3#u3}-s%H(0(k}OwEl=aK;H%Iv;GgWm))}I zY6cv#{)B@;jV(v4KjS)}h=3w%BsKby1)Q<QQVgh7zy)hb>d2K&Vgs&N%ThPckbt{X z2||3C(f3k*BUKvpjWmh%gdKkt1bnvU@A%?65unmvt4=XXR<W^?mU5M1R?=E>qq{OT z53HoEw2~{GXo0728EHLN3ULWMtCW%cfUH`euT5EL7i8ZD22dRm8SQ7Njh$3tDw_Uy z`)95Uv^KDfjf15B%-SnN+XCaQ93>kNYu!om2OXg8IZHtx)=w8H0d$;BS3&A8L~aJ2 z!xg0wpqGJpRHHyRh-H(w3P|~&OH{0Fwv;*r#oByT-=J<bmCP(TsJD%Wsg$7pHdUmB zyp|NSJZOlGMY_pVidik*63S$CmSR?mj}#4J)A>npAXcZpl*}dekpa>;A#x}vPYRII zKz{|Tpzo!f4LVJ;KxsbcGR*>|<)FJX3zB{Xy{1`^v=wyHHxCC(7eLp2_fXyC64!}p zNRLgekZVXEpyY($JX}+fv)CM^lZ@a!R5n8NvyH_el7p!|R9>c5$f1%yO!qkW91fF0 z%q-U`Op4>mM4y7E*wm8NL&>@|ezpmhwt_m=SV*<YoGupEmJXU)Xj5A{ZEA&FN4f;l z?WuXx=6mTD=zPtSRF6Q<YM!@=kY0j}kSkOlKwcrYZR$$$d@;B6LLN}rfPM&hW>Zgc z01Xa#LsbbhCFG+`eaRcNJVdgsF9m?|LJZqTDGXE?Ql6?l=zNHqZ3C$(=yixERU43f zsGn^^DGuZt8c5X>R5vutwvp5y6c-vnH4HR7w2^I;GzK&?v>8<@Xmw~C+s4vN(9Y0K zR9T=Cq1|koNJ~L?LVHuK1-%O$VB1vM1X99=Q00Q$!$#UhOZ!2-VUwwjfNF%zv27-u z0@Vx4qAC&MYfp2jb~anq;!Z_#DVi&t6oyBZX)a9y-J)tC?cgfpM^7=*ArKo^5+j`g zv9T&K(p9d*gpE~+k*-6=#;UZG{sytpDJ`Y9T<o4{(=sh3y9KN-Dd>trt1_*m7DD86 zcn7LDQ2E+Sc8f*H?`n6angj}~olvHY^eY$Jrq{@ArQ<>*y0)#_PC5r_UpuKxJLw-# z_uBJ~_7Ykm>g-=TxlDVh7&NW+*fJfY9!ps!#%pwtCV|)pjSkXE5F4S<K{^0pBQ!cl z54cj$FSTQ-daM*J<kA*8NaPoxqf{NGcc4pDouu-sM7Ge9TqaiX2d$@yleVrF*;uL{ zq}OYON@)w7rJ`Sjl4#ahviXfE1HG!9UZ#r_&Xt1R(-yi)M?q!l%q-JQirOHi^Q@Cu z=0~XmD2S?u6c4IjXK|UH(q1lc4%Az^Da6l#dP~ahtd?5_I|u45MXzMaK%a68oA;JF zfFkp*H;<QmdC7FLs!pENM+yS%uCqe!Bh?0-t#b({NDV;u>fDwRq~@Tvb&i+mE42eD z-=C-I4EmMcWA7{V6QXOu4Vr!AN<kIBe^REu)Qc+vP0M?1KS1gO+Lu>8e4um>M0S?M zBucM9Eq1<+Ns{6>vN>W${x)R?NlBm(U#GG|q%mCLn`EdoS%`cekz97DGy~KsVhmL# zsAoi;G)!6pN{*m+7^Q8XsSzn<hfDdO%!sK}MO@<gb+WX66YGn(N<303-OMCfA1i(2 z5+%n<$y>gboFJXw`nBZbFW;%QFCtcy{i;v2XJ2JgckMS>#s5>ax?8@<avZ*$?)Qo- zWV05vcHJFiQ_c1o*UgjCOm(Vz3a3fe%${8;n<hQxDm7R(MS2Zl{hT7<KlnV5MHTa; zDN-4(QiJt+iev$?UQdx?xKhx;x;LqQhK$Wax>N*W^N=ptZDVzo8f+fYrAi<+59yM> z5N7i*Rcg<bPL9!em?rgrl5CEqNfn{JMLTbnohD5O9ohMy<@7IetKRc@Sayb~t92ii zooVWCD*AqJ{#U4eqM~nQ29>S%uq^%cP0)Aso|m0#Dze_YvOk;ZS`XW0eIsHw-_!tF za=|xxUUtzJ*^a4aXSY;J67xs5oGMh~VcuD<mr3(Ltk=t>)gadE6;dvU^?IfB7l`%x z7wIa9^?H@`0>pZ~R-(7^=wB&jy<R7|m?C!TC4Ug>^#-Yfspn-kO5=nK*6S@_=0VM= z=lhKg*XwV!Ra(YNvaKV;?hk1z)WS+`ldgbR$?aciv3*)EPs%a%{{N)cUm<_Z9O~yu zJIs<^G}|FPG~0``%az`V77SKro}}gQxiwgwd6GSd)tM(%0kJxFN&z5N=T0eti_Jrf z-7cvGWNho$Ew$$=G}zX$Tj~K~+rw_DH;8QyyQKjjwgv2#l0a+=*ewkKu`OV?G#tdX zfZfs<A-)Cdky0RI>-=6Rjf?dx&Tg-iB{IVLvQJvbRm$gmpR^Xl=6#>E4aDYspL77k z=6%0(0>tKhzjTEwg)FWAqurm<11{EHAF6j;g@n!D0jc^9wsZ>#o1+6#8?H>c^(WdL zke-PQv90)^^ny>9ffnst(CUyR=d$Ti$o~3c?eZl%F4jT{l^d66p+M@$Rf^g6P$2aJ zvF+ioG#JFTheBx_h;0u?r0F2GJsg#?L2P^YOIizJ+e4AG9mKYWlhRRB#O{>z1jM$7 zGZM;Uy)MNAXf5ZYszQ8wxFAJy74o^gAdTiqA(!jVvAZaxbFul$qM9p8(&M^3sYF@| zVxN17v;oAH=OyVl7pvtLyGv3D7pr9*)h#H=YPl>u1+iK#OCLe3mMc=Fonl#&$Q-*X zQZ+7C%WkTgT!n<KyH}-15Uca5)Dpz%yef6!N+FFRkJw$4`f;&ZPEZZ~X1eRrgm0$1 zF3ke57H&w(L2OOFA#DY*e%_GIaHWvWk(cdmNY}Yo3pc4AaFt@V54kBh?qYLPNZ3|& zOKJsTy}l)N0kK}+lKO*K>$jz`AlCYAiTxe``!v~}=(c3bm4Qy}s@VFDw2~{GT#U?< z?n>)H|3t2k?@E7w92?}}d(tjY^#*&W4uKletW-J%ilbSnRLmug67EaYcC*$~(7*;q z%RQ3fK~o!?D)+Y(w?|}48(b>)L@EGnq<SV@-7B)a4epeCAqDLdDroSi+)HWFexb`$ z|43~PF^MCV_tLC<kv(YeZ@Ev>v;v_^&U$%SE<P;uu7PuTA{Q45*)^<G-jK8Sz=#y& z*|2)~GV)_kc*9!d?d0OWM9EeSo0KmvUp^+(wO;%3PO{5!p&ku;maibMED{>ha7cM~ z8J`fE(r{$?%5qgMaTmXe+?I=d?y=ZY&gFBPfokmD-r7@s#%swy52(E4A4EyQj@>Ns z01!KNv&h5ETGo{Jme+HopxuoMa8<eMNzr;Cm5&^8M(9eT-^=^TiJ&))a>`eeOF$@U zh5Vg-;H)TV6?Fjn%Pr3d*+=D3^#E0jx<r-4RfyO*iN8D!GB(=WUydwh(}^=1e_6R8 z#LjH|<&PkCX5%lvy(n6+@D|7=q2Q=J<^AQyph&9fa{OhH#ZU#vaaV*oQw7RHK!c(V zln;{quZk><X2J5->q2v*4wtVX-vup;ip8Pw${Qlv992LSbW3PA&BEm8pd(Qy%7@7z zw?%d&>RkD7c@*d@ZQ*;_=8nj&)9E7QexL_j2S6{mT<(gJxG^i446@-W0r_%8+!H0E zxR!Gj@~2B8<YKN;%*GHz$QOkSHijTVeh6Y?2qNUyT<L^e<%p0gl(IV03A@S>A^U>Z zRgSuHD-gTNQCIE?Vpm-1$&*3sic39t9*A94sW0b(*j1JKash~4<%pCYf!I}!Nck;@ zU9)N+yWbamVb`o0$o`;$#+PtIxgF?a<J(e0xd*7Eah}vj?gx6<c!k_Z9twKj*v39e z9u3l)l&6|3#J91=@(fce<i_$MQ?a;-{0mnKS=PkQzKMKDWMoT|OEfzM`m@Pxsfm0W z#I8s*l^=oF6^W)Yc_8NKRFeZZTDAjSZ&E<z3VPBc4>yymfIc+YLsd<P_A}7Fx!i|K zjMZ!*4+F8WlP%==AU4Xfg}e#GMqsv(4};jq%@*=UE-}V4Mjr5x^{f!F@t7^;g<J)Q zjeu??KLoK6(5>XRAT|QJm8?Hv)1@GzX_$R0*^x_(z-%K|1F;d9ZRGkOHd3^W+yTVK zW44u3Ox>2+$qP&!l-tXjOclx<<a|@d<&N?tQ>W!l@-tKC<ycw$Tg;K8`yL!8yP3Ky z{~*^ebwlnfN1H0ZUF7bj?#f-|WK$32Zt@IMPv!3NN>gv>FNp0lby(>k+dTf#`bW8^ z>}5(;ddYQ6St-5cwx*6N@p3;98*AG~UIt?0eiP(vT;dy{ue{sT3c0VGZz{^ZpM1hp z3;O}`MIpkr{zUl(R|;v}^tO~JKY)zw3zFpLkaee7lKf6&eCrq_<0ov6SnDg~L9&&p zDEq;(gQ*twLuGd^cE7lz{cyRKnZ@FfawJzay%*Wjexy9lR6qMs@=dNx^rq=x`_b}S zQ={$2%FCXz)-#Y1J>7nye8AK^`$@9hGnQqd3eofIC(GZNnr@#ahnQMyKSl1!m4UvG zUS&U39tT<1=ymqf<ds~kXPfP(%Uextx1S;JGquxxrhLrQe*0PSB~$tKv*ib-j@r+W z|21{Oey*%NXZ>U?oVA}PJDR#^pCMaJUA6yNu4n3&eWo1Cm4U`bm)d8^vrIj<&z65R zOTMsQAn!2skNrZqz|;r(MRJKL$ziel$&}%+M6UeeOV8{amdX*P9@{UIo0xKTST46Q zRmowc9Q}$-mx1O*dpi6gujk4{>!bY~R>>i+S(b_RMh7{pmgjP1qoU|a4r}B^ra~Ro z%BxM)byz2FHPzVRH~FTiHVzx*s5h+6Y;-lci^CSVm8l*MTje-Y{T#N*y-W>p$dLzv zo<@&!*dY%$vq=tl@=Q}R9CpcTOl3Ljk&8{OaM&-0|07!0n{9A7C`Xyv>X0w52RSv{ z<xnW+m^$EaM80b3n8RPP^}nJPpJwMBiev{<R~=5sl}+7sI3@Q2)oJ#`;jBE+)N6-x z@+ecE9L~$@K+T#NjwSL|Q+AG*<h`a`9IwdqRo?VZTy?)L_v8|Ll<RVTQ!C`_@-S0T z_BZ5lrdrtFlBaPM8XucYw!I@C0BOx9+uoJmfY@Dyd$Qd-)?T5(?ke1q{Xy)mLa7`A zVs{lv<u)L8TyS5G1F_?R`*Jdf-TinVj{~v09}nb(Aa+OPp}Z2r?x;MJ_k!3l$|Jep zOG$c+@<_f3Vt0l9mLGuFU7^2a<-O=NJ0^N8+kn_H(PR4Sr6Oa;L{H=(5IZJ%BFBK( zanDn^1Be~>Je7xl*j<`u@+c6yOY=<50_BBNb$l*w0kL~F&*ftvcF*Ryd=A9!ue^}2 zfY|+&7xG;$@oB!4qd%~IvauEZj#M9+($S&dP{&tttf{(=Z{&EceA&KvUB`drY{)Fl z*Q@X3m8PN`-^*J}wQ~F@A2HR*@jtnkD_;(3-pTQ^`~kA4=07@O#p*xNdQ|hij<Vvw zl`prVQkCzyQc;iQgB*1whKoLN+x#a-qRfKqg@2l(m9m>F8{P3*>}ah#0gY(B*wIFD z{lr?xMn6~I=xD370{K|>JC;$pnmXfXr}Qy($FZC;$kZE0du1f(lcVP3piDAl@8qa# zrB8RW{h5!&!^uf0=E|mHLl--`C~r&!Ik_rF>3ben$)f5(P8E~~rXrmxDjyV<Wuw&Q zXB^!WnZEN%DB7u#@;jfS4CKCda|d_jIw*+BLn+7ml7SlTJ=>wOa*MY|-{ZUQw~kd5 z3$LZr*wMVZlc&PoX3efAAExqBvbfU8+vWqDEJ_hbYcasdTT$p+AXv$kEq-#UsziW# zQ~4+~pnnCn<64Y&@>PcNjD4fWVqayPsdt*MQX+&_w1~xiii6JTEWn3aOm*^8LO^#~ z%yFuwB!kMw%y;@u$>9>?v;36;u2fVtW~r0E;!RjBg@ldKt*+Dnu`#;UmHI+-$=9Qb z0kKiJ)s+(Z9wfGwk7({r^@b}Ig~Y^LRaYw0H_ot8EsbJUI|V5HK^<e9$_6Uqxbo$m zF&mwNln(SgHFOM?oEURks-Xng3XP7@^qNY0P+ClmQ-~4|nnkAzRff<v>agjuV(55p z<t=DM%pRv&iYI;R9Ls*EwbWKZKs#d&JJnIHa%G}|7+XC;v9S{+&(rDZDP6e=al;B{ zl}Kd>S30^AL&r)h<G51M<Cqgp4V90ew=uqYlwx0w)sl{^TAp)itW*R!w>+yfR;qzK zThcQdC4?&#`L|pnH&J$i>QFURUek9*v1Qi0<s}@gL_09u!d+UHI7KUMK!3En;nYk? z2L030scds)JSeKwMyD3aRM5Ou^od4go{({~RgP0DWf91y^+u=G%4#S%sI{}+R@n&3 zZ5@l-DLEjmji$F#_JO*#(Vg2XM}&;cZSrtO<rrkRt)_QW&T*yCG1k6%C*>7nF>S|N z#VS^gVvhQ^jm2?FdC;`B@m6sPdlMa-+YPkj4@wZ|c-s?Bos~!-<L|b4xU13#WNF9L z1WLxVJF9e6I)M_~-Ils3bYIBoH0HE(aqh1418r@0!s$n46qG#M?yyr&WinT(@wOek zqp8e=l2ZFTs-Hnk+n=J!f$1i-ujJfY*$LV~vv{QieJ>tsuar&~ue1VrcVKD@>c!PT z2<LV1a*kKJfSz{ncTP}xLCK08^Kd_fy>X8%m$;6Vock$#L75$6aerkHlq~94)49LG z-pa>Xx9?P&Dj5{lDUX&M3EIUq21H_6Hl9n|?d-3lgV^29fyxRl@g0|_WO9k4nM7qB zR{>%BtVCstD9P{DB`SHKRrG3TqH=&sjLu9_@<qn?Zb`}oQ%TNA%0H%(od+q%navSv zeXR2k#m*GHgQ|pciP5LSm4P5OiZoeC<|^c4Jd>3YA;O+j_(}11VRaS~_N>B4Wg?dt z|2bO8;>t$Hyi=S<D~_&TT3_ruM)Bif&*INC#wuZ4V$AbcrJl&>(-8Z$u}UW{F;aA# zG77{-ijG&NaET>2UdiSXOK`lhlB?7>*Lkz^c%=k%pK5|)N8k6wzB~N8>~@~0M1vYo zO;W~liO(oSnQ!W#bBeOr)KTZjN}j2+N~%)AC6-{C@)pGQ$x{?N`kpen|CHEXajJ5K zD;53F<)rg;rJNhfQqfSV8Hxv&7)3f$@!=Am^h~81S2{gPIPW}D2?x14-Jlu^n$e}6 zK1<mM)3MKJmhutAKBL)641K#GoA)BmQ^s6n6iBj^I?q#bxY#H5)cI#6oW5s|eZLoY z|Kpsgi~z0kZex?Bq?!89dA?G@CDxDyN;rK}9c_W`^-G->DNVU<;YD3^mnBMnAw%wJ z<FZs42PFe6I@L;0->#KhmMMi$a(LIOE-RE0A$+B4ZK_wG&fQ}1O6B91tXpH3m5P65 z*7_~{WB0Z$zbFrcj9cCF@M`66$Oit{*k!e%S79Z^+PX$z@0DY{?oi=Jmo>^Ht^)kz z#})D#Whv-0)mmjI*DYMW$6%LVl>)8|)Vaq9D&i$tFW+N=%Q|HUD7D8_m-Wg>(D5F5 z(t2eKs9aB`@t~xhvuR28W;r$w`8_kKrh>}$s_nd9nE~qAYZ1+6gZA`V>GGSBW$HJV z4aySGhhAH#mUD^G(Z4Ih`8q*Il<(gVyHVN1Gq%p3blIfjLdl8o7hN_h1t7L2Z&4Hr z>sdOP9e>^B55<P75FM}bnyNCWx+nelG^GRRNo8x-JY|BIj_;**DluFc$p66AxSdKm zm)LH1DRabheC+uyCF7gvb}4I2Il1mqcA7fsyjwYFijGTHu5yX3aj)_b#P(<V6vdm( z16#{0x$aZOfeO66UH2=yK<m8yUH?>$nyTq~Kyj(crW0%VAthOe?*|K%qae189abv( zu##*W%W*oQSU{3xqtj6(KnO>)aw>aFsRf!&k2Q`f4L}#^vBn9dIjCU;XZ@to9)#M& z;!{c&P<whjaY~8jx`o;C#A#)a5I>%vPvb+$0E?!dRmO0Mb^e?(nTvhy&iXlJ4VSp5 zQLJ?JW%H1N*!_-T1^J12XLmS?l`Igu!%?hMt|l^epQKnB!j*!y#%p@9atX96K9Bwu z&Q&3j&-ECx;`q9*=asncM0+>mmpWZg=7AnkT~vxeA&$;^i88OcDEU18i1j69C6^dk zby+DE;v=grD<puG6eFuHD-m4j#HtU{FDvbZ=)AwvE-T5VV(}Ga9GAF8eMOl7V%Mmz zDCr<}jrxkRh>Jb#|4zH2{0U-D`(IVwK}mM~`l@0RD3%htetlJ`!X>U>UsL=bW7n^* zEA_bO7;^VEt~ZpHT&YxXuD6sKP?EhR;Eu8c#I8}_Q8t3uHR?OcArQM3dsis|u`Aa1 zlvk#1OQnhtBzorPeo(%zc$g}bA1I-wj>`{~7N$<ikCdLK&dYx*BTen0zic$e)MfdJ zvc}X6`KhwoQ~`dboHcb<ey%()^-zAHd@}V^eyKPFf9dBN`IYjWsgLq&rJ<?A${VGF zDJ$h4WuPfp`B#~2>bUY&$>I|Go_ESpu5^CX^-fs>%5gyYJEZ{hdqrm|T!Zz6?=hX< zD^<AiC3ar*K?wn|^Qw<Z7KojJeNu`*?Ck8bqSR!y<V$SdiqsY$c9tcnaUga+Cad#6 z?CBs`%?GjlnW7%&5=RM&dXkHs?XHj&^%WQEXR@=ZqL44W9_y^Dt+>Sg%uxGti9Lh0 zI**I(BNJV%RZA$FE*pjRndWMv#)9hh`PsFM8gFW;tDSn3E1h4hw^vVdiL3SY>Ltk7 z)p`f@7G&&dy@UElWVE03&1LFKQ!8Xg^@FJ>dnZ*5`!WwL>|NBdT;ghd1=Uw%<bC5j z?5+lZbb1%cU9AmT>-w9khuQ$tq|X+r=AhPna$PH{?La;Ilv66JT|mQWRz>X%nnJTG zY9eSspFdqa)nw4RK8+Mlbv!7KW?t$PP(IDP)H$G2G_$DLper=9s4GDC`{ZG7bsgwq zpFLDtLFE$ia8-3DsA|F<s)L~K6LMXB)W1M63FQ<Y^(?3x&3x6%pg}bARquc%CLDG3 zQ~w6dN;pmR8nh_kc$sSIN6_koORm*awHE&z=+iTIUB6RpL8SUKDkspUguAZ(syiq@ z;Te?==u*N5*Xn8@=w(83nd)jd$hvPH4p1XOetq{)H3K#6JKiZ!Z3}AC7gq>WJA;1c z`<rW!+6$D>cMH`(A--k>tFyS+bw!&B!Rl463^cs2YlRwWacw?FsQrNH6+%>pIzm4T z__;!;YT=Kvr=#8jmRG2yP67=bu&qLEbv<bOfI}4`R3$=8H)X(y3iVYtt`xLvz{Ls; z)P;3<#{1Gx&E+aU6XS1IXsGW0CTpY?3h`$u8mT9_QV4sdqLF$LvbS~aSBO$?Kz4Az zOR5Kuz4QN6p|ScLGE&{DVq^6kWSbH`RcNB(daN%gBtOBbViVPhi|+XnoGUg}%R{z0 z!IP>YWS0^GDn_ebpqB|DRQ^JAj*`kWQ>TgPs7_R9u9mMamc;PB-&c%LeYvvHi2+d+ zTdEbgGSJckTROK=y+G>^JhpGG&Joj*#{;q}wo$XW(#WR)E2&n9j4#2q>S-?a2A1C| zwpHcGFH84i#dfNND+M(jm{g{NI!k1zJ5@(Dx-p-3G+^Mxik;NGpy@RGK}~ETvaErn z6+5fZO@)>Zd|t7e+Jk2q=*fYZU3#cpc*gcU31xb!Be>GZ^?{OGFLgZV@xVN(mr55j z{f$+&7cN6Hp+fp&9u98traTkf+!9O$Bvx}9z(tQZ)$qh%w}I*mUS}q0msrOwQO$rB z4kqT|M0FwPL1IgqtpsUF?WukPS(5T_lKKazdD0%L-9mhM4pQ???ZJc8%UmgFNK$9F z!Rm9+=%jvBB$_RkLd<$LL@nSdz*CY&x(!uPbFs{pCQWf0rgr5jz&n#>xeZr4v=G^~ zr1@^i>Q2xbsuAi<t^#Z~Xt~=@YFrE}S%B*d+Uzz;4QeISbI>5iF=}nD6f|bgZnv>& zG-%eKLbq{h<<_Ew6@yN=jaQ36xq~jcO;oqG5w)Bi^wMpLdK2_;&<D44^&{xNK}w~m zs!Ll@(s8hDrD<vm$ak<)rRnNXP}E?LN;6cOcA{kG!M>Gds=lDXgM-~>sdnvImWd_| z_H&r6&f>~I8H0l>%~5kYitLxcO)6!o{+&cEIfGkP%2H2*4iD~DDO-I2x-mGR(gL+d zEGwCU^dZA4Em9Z$AQU=eYNeH`Z)c&_LozG<qF&)TjQb4fZ?#6<$UmbDq#wKxwpPsp zRi*k>ZN@*N4Aki0uU*%v_4((Jfd)~nR~6oR21?Gi>;9X1lh-M(xNJ}py0G@xdL4^5 zsDrr<Bi7yqb)=bXsr0)#l}kL~yGdOMVo(2WQg?E(D`vYYZC3AcWuP`g&sN%^PVCBB zC`5gSUa7QI%>)e_dZ*GhHJ3}YzC+!>C3?0)J#S_$<Q?ign2udF%2OS>i8@*9d8$7b zYyD}ZooXj8woSjOv`Zb%l}_Bl{;RZC9m|zMe8VL7y=od{nL|(Eed;X8ejR#N*{9Bj zY!}V;tIHrOpxJ))SIABc&BK4Hn?YBG?xD)#N+q3!ox=yzKViD0VYyZZ)T5##?OCqX zL3LwyHV>)j<**9whtzRDGNm9iJQnAxF+GIbhHGlRx{`~|1AUUUKwS@FPqG%Me{iLe z<l*P=VRe_7j?5UIYjs#X1ldZO6{^P|+ex!RwU~=7`CO|b>du~D`clRHs5+{bX#Ev! z{V(-bu5`l2HXTzpgV@-nV`?6#pmDD2arIBo$;RcB<LXh+<m5c5NIeajpS(gYQZIql zC;PgeP;Y^DCkIeH0<mW%PpU6LMaj8VC)E!i_WblIRqoCDnNHaA)2CD$(6!`3tJA6j z=tc5zs!AYrL>@k)dV|W1*h3WnszkH1Y8a>*&CaUzLA7XhPHhT`qS-mM4XEviQ}p*$ z<3PPeoK=d|o**{n>%7_@#KwG`SBHVv^WhiNF(CGQ_ysi;^g~Ff`$csoXmCg*RTgMU zNQ`@lx)iiLB$jF|Xiv@F?w8a}pz}2cQRRZXLdLjXR`-MIg-oG30<sUKzcizs0{Mn6 zr78gpAF<y3s(KSNb;KW34?#<4c1?W&+Dx-+>U+?^5qsRPt5Ur9jEYAbrn2S|@1nV( z+KY_udv2%|P32nMP(!)ckyUk%n`)%U(9i)P9=Ft;pkpJy_qeTI1-%;C(BqC8(nqw% zo@BkJMu6B+*FCi{h&{<#s<s5NCs|9?PN4UV_gURnyMy#5EtUIfU(n}~`>Y<QgF!Z< zS}G6JQ6Se*`>Y<SlRy@lJyfTI0%`V0%>dP<*&}r^h&?&`x4H_%o}B$#{T;N{b!?f( z>UL0*J~1AT)x97e`UKDuwE$F;-c@*_o&d3Ppr`725IYBYs$K`NbD(ExDTti|JyV~7 z*g4R1^<NM>2YRle1hG8XInWE0zVL=`aqJxEg<1~8&VgR4t{`>}^iuT%-S4x{>Xljz z^s!G%<&|0!R4!qk)ob;8P}PK%%4;<W#Lj`<s4*aR4)jLt2x8|z|ES$S>>TJHH376J zA-m$g>LAeSgq2hyLGS$IJl?7kL8N+bs%fB03AXAxbsp$tLQ<J`>LSplggB4)>Mx-D zgx*veK<qr{gSrjG&T~Gfdq6El<w+mad{Eq|74k>52-IiPQP=;}V$iTrr>U-i*!j^X z^&W_wAAM4vg6dZs?D1Ls2h^hC7^=^pANr0hgEYOb_>2<z#&{sj4m5ET(y`_OnmNju zstRbqC|gz1{6MQmC6$r18lWw79%QW!Xg8e)S!=|VO3u(s(PBi#w_-(`z?Dr$T&H^| z+ALFZJXCETsK9%XhprWwTIoTwf4S1>9jXl;RvLYI5?jxxPFA$mBDlm}!&V#2#h#o^ zDr2iH1YL2kRm*4>ga{hLbe)TR$362XqrK%yW%oin%4%-?SqmwsJS}Ob?HwTGK4yh% zr)?iB6f%asyIng9YQ)uJh{#%Vg%1<z&eaB##MN=Q$i{PJffjK^C5vnu*GJHwT*V_q zc4o}jGUc@AKQU#ZYhylnl+&U|evw+ayf%qT952~x|8b?0`{QFh?KO22E16Ez3Eim5 zfY^v02h9RvBYGUP>Rc&&nK^2qLTu@JI%@quR~(W(owVa(I^;EB4Ao^*sh-Z-JyUZ% zUA5Py7I{|GEThF7`A_)O(_L!=3Z?SU=7FLnZ1SwEDPu&*Rugu3dTKEfg?^lH$kSVk z16eAc^z_ksfCf$|@$}P%OcEt0O}Op(oi>RplfL2bv1fIS{`@)rq?b?l;2Ef$=MqPp z!P)~MdZ*$X{U!3}q9ke3C)X-idj}bNe!7N+C-Yhedw#lxX2r#7vGuB{g$WV%27yqm z9*Dg`AXIA#Vs9u3(^_+hH<W~FaghBxp^kMetp{Y~CPq^AhwQtFRlUNsp`b<+{i()) zy3nk)HW@UMX0^4MLbPYOR&}&ou2eE-Qd6(*wf$Ul4Edyq*57MKxY#=wCR#^mx1i+4 zNiDtVYFa9r+cfmoq*$uDpi7ftairE2^!KEmUXj{5u3NZHN^hGeZ7XPe%6O|L+D<O9 ztea{FOii?Isuc;*@nCfwqqSO7#Aoy*CD^UG*3r})uNK-Uu5@If<GNaE8zK8IWuaGV z?I_58@^Y%jAiv3LsN&K^one!=c(v6IfTAYnd9~BRr;03Ya=urrmJAv^`IJ{@?fEp3 z{o3<}*N<A%bfHw5_0%l0h2~9u=#`+QfYwZ2Blpu5g0@b6<JDh#0NOKIQwM7ybHsGV zCTo@<+IrCW$w#b*YsH|4lgn9>wNCRyNw$`c)Ovv4Pp)VgtrdaFrFvP$YEc=YWcAeQ zmI>N{OrglsI+n?rO_orn)F{goEgCd1wWVdM7BgRDqf$FtW@>A)g=VG3Tjppv3xyV^ z4zgru#fyZtrH-;>X}-&ZN>Zm-7HV0`g+8V(v@FrutPs+ht+p)JhO81IX`3ypv@DQY z+6H;8_9w411Km7)y!Sfo5Xh$RR`2!NVNiJCtKPq9&3K&|D84YX&ju|9G`g@wpWn4y zQ6~vco2PBm&Tz5yY@+o>?XnPJWBoR1AE0%%zHHH)SBsuKNjYNKqJ@B3q@A>E)s}*~ zr(L(~(3G{JWK!Bw%WiEAXmZ*+%RVjbSCK7F)4UIAHP#8`r1f<y(Bio=QGS|}ccE4c z*@d*q-hXLs>qW_;=xW|Y+WOywO4I6ipV2OY*ynyuyJ4!4_j&EUsg~XswdbI>X|djy zw5}UOEw)p7dtcLrY!vdFGQ#_|)?t%S<dmu2_ci~`LT#t~?EOe1TZDQ}S>^pyv-}~n zu|baaD{U;uEp3nYYb^`3bYw-#KiWpnlqnT0Z?yuhLc+%3zSquz*f`t|+D#A}hx<`` z0%GHEKWZO9Y`p1z8rjBLFC=V4>wlUfh>d9dPxAt?Z>~>T5Qu$qebVZI*a+IsS_=>x zLHk+z0mMeoB0T}bM$ppnFd#Nc7V9Y>HcA%jb3tsxuB0yov8Re8eZv>gk-oB?2Vx_A zW&JRSjUHC?vmiElSkZ5S*yv$Ze*$8ohgJOph>gnCbh2G62{tNM)15$Uw6d;yf!Jte zT@MDaakxaU2V&!Ji5|n1ivCL}@;3C|Tx{z&OEulpC2uQz3s=6x#>`sl#UM5g*H(WA zVxwfs>h3wLXRMZ7t8zO1m;M#n7EC#Z%j-To%SH#%fAuM^2ZAoAZ}G9$BS0_GcltQ! zF`!CQ5BfOj<3P=)9`kY1UxRv1J?G=Bd+cECWuOUDull&?{-8`MS3QO+8||2S&!>W( z11g;Q+{aDd3ned3edkk2F9N-wa@XB*MV-#mBwr7`D=1_dp}J|RoNr}4E02}TMj_KG z_*T(Ngvh}}FJDi+$4;J+xM@CA$z0+X%u`<_GCEx&nr-<e^U{xS@y|i=(l2n88f*z# zbcbE6PL@@%vFJfu;;mTTdKQSim&!+fYnG%VVRf|o%XESEzPhgvGNyO-_0tpg@QlAv z-(UZOD+Sq2@8#>SALL@!$cFm{>UTv(pMy^Gt*KYt%ce^~q0@Djn))QJLey~jbl(si z?PFOXYBT+3-%x!asQdKgzO{6R{URGYeVuQ(z7aHb`c~iC`lLTaHf#Dm-|uz$U>dJw z#q?vob@lb24bw0B*4HBsifsS%JH8F{1E4e0ANw}cyB-qR_37_?qjV)-=<n%<UsHW1 zR|b-1xcN2HcY>^_n(Gft`S`Wag9_Mm8K}XGP`{RXSFRM)d`3OLR(clb#~Gdd+Uo}n zvyv%j^o(JCar)jup*d7t^#MnOcFdUS*F#^yRfwKco=x?FFV75A^{C#jhn@_ryBuHD zzo)*9mlW$uFTNd_cNIXinZ(6@@uoZzSNrub6_B{qub=)WuZ4YnyZrj==b-h&GY<F- z(64f3(-A#Ieu;X-QP$6FRNeEu-(Wop^rZ4NzhQa~R|eWJGcRDc9{v|AnSr*<+(T6a zDxz7ke&Cp>#dg*~D&@G4Gu2OeQ!cjdo(>qL$8!}T)|b)xS~I&EFjhYRS-`A20poP~ zqDI<Y0qQmDWxzzeITx!X7EjXaafzigNpJUmS!a=vGqcpdNqRpaJ`a=h*PuJItXXzK z^z->FJF2^&53}3?C+iL;S(bsy&bA9o)sJ#z(`&I+0;lN4DW37)ElbxuxLD6>2Bz!L zLi8NyqSZ9r|Fo#3%j~*=)Ab0h6qGbODsYBA8#H!yyTIA{UMM+b_K$&c^#`Ekvy%cd z^ia_rI(_0%Vx~@CY{7eW<3y86Svo!=ba3|M!1=lxS32r!nH9J|Zv#3pdtu-reU1=c zf=l(iT-oUS?6rYQ^~kfVmTXiydwbvtotzhXJ$rxPFM1G2nsYpGl^)JZW}woOj}lkw z-aN}dRZbrdT%%V9bvf-fXstevD;?R+DG6MsKL<I_xgWTmudQ@Hp;npmPvCE+YRplC zHkh)VV;}VUH}VYHXv%p`aL}f26dANxZ+U_BTD&uEiyp_7fqt0NA!v&}i>nZ^_O|OE zx#;IGr%zCh-l2p|SBO~axq1#4eXIPO!9jU?_$86CzU<Q1aHXK%=Zp^8t-D<Ql6eO0 z)zi3A(2+T*LHqQbkg<OLsry`EB{R{@IkSTf=>50~k@K9$phJ2IWbfuI2+G%kud<S2 zEkCS#bBVS5FrV8m`+_fG`+{GBjzCt3Jrmaj9W@n@xIO5&UYpmFjU4Ch4l2_7f;_2C z>N7wARHyY#poqBzL1*<65L<VP^?O|G&ZLj;dEMojn0K~3&+A)3Yzbb_gRXyBF7`ne z^)yqSK_z+#h%J{(y8jJPk}ZkL`XDYb?^pC*Tw>m@{NKEP5u5jOLD$T=^-R1Pbi-6Y z;-jED`cGz^B>1krQV7M)bqv0zmvFH$SdPJ^`ly><m;8NwhN+<7`}#&9`io<c!4LKK zpwV-qga6izTYNe+b#B|>Cwep&eShTKfx*x8Vy<koYwoDv=X&)!tYii{IyW`=l^)JT zpEs;tCGfT0*wl>RH+m0KS;7D6Tkf&xvQeEXD}vwaZ%wTa{-{Tne#vrzKkJi0*XC9U zl*wFEGlCVe-c(kwMqYsap1UI0AmR5#Eq~8lA8bvgnaT++LkhSu(8sw4gUgec2cl%e zyrN)7f*vwuqF;MH_i-YGD;q`43vzHK&A7zp=R$gNrIWt%&S4kQA2gC{s1V&RZK>o! zQn<uj#0q3Fh~3AlK(=wQ`*`PqD-zl?{VTLxHt!U6BUjB@N`u{q^&{3Zwk|ymu0*`K z3h8L&4^Hl+6^M<p^dN&l?b~2y53&HXu8o~@WwKX@KjYv@u7TK_J3R^do3&7g11#9t zlQaXdvF=`EFqatX?nUN-*b^OIWFv?@!(kx>AodK0H+ctQ&tg<16&|w|ZejLBhYyJn z;!kw=ktE32GaP;-4KnuBMm4e)#Gcytj$DM2?5U0Fq!cptoJJsd2x8A^1QMGkti23$ zciun2fusZIHT^V$NIF-k!IpI}S;>`+^o*YN!Q?r}CF4VI4T7GsTC!2Y45da*^E2v{ zaSDf!dOTyFO8FWgB+is;jZiX-i;XCc3=Si|fd1`*ox{mrLPqzl4lcFHYslD=uT4Up zvG(X2O}j=1*CF$SjMH6%Tq4LGP?4u&a0Gb(k}Q$Ib&2zHF<rw7-Zkoz#vry%M3Mwh z`!>NfB1sl#U7NZ!8W8^%d^)~PG$JiQY@KLCh6vGhBDh8);_y<m(4j($8d0P#mslsF z$Y9XwjL+6lWI3oXBi^bBDFi*u=u)F8c_Cy}{5cOdCFB*W^On*4=bkhR;3}o<omHBW zSjdLaESii5E&W-wi6$FB1wVhbZbmK%VYW`RAo6QbCtD|4k_sTUPP8NuT=ZRY^z&;; zUh(;(zXnl!c4$k|o@eaSTv($O=?f)iXZ}{BHAx1g(}SbdWFCn9UQcVX5wswa>6H++ zqVG;=P0$;$%vNW{QrU8`rxS)av?flbcGPG?dVumW_tj`eazF<%kJRW$ZvMk+$wsF# z&(-KeUYNQ@rTqIPdqDLT^l#=rHDZa|Tao>niE74@0uau!t{F#ensT6;^iGtt$|Al$ zkafKE3^cuX_OQ;xo|nu(%ZpnM??TRl9#VBBr{A;b#OTv*qy)sytGbb!LUegntJ#hG z!^Q4z2G{(Nd@{3G+=Gx0qV?dch?+gf)Q>`~vL4&VlPptBYW5+|xzfqmtV_5bc?-Ic zbzAC3@PE87v_1M{6mc+hTk21Ih0tSq!ZU#M0kN%PAQ{J%f?m;D29j-D;@(RlIS3^i zTiVr3B&R{2vf^qEA|+hn8(|Q6&XtYq=69o+>nAY}TPOFgIha%dRhU1r<`5EWYJAP1 zqyflx{<NCINC!~;`I$9`lVnp{YW_s}eHL{#o`0t181fj@cK*wn<481;_!`o7z8o@v zB!fE7w+@*^ib1{SR|-iXAGtEn!1*B|lj(0^i0QUYjtfaA8$rY8_X(Lw3`t}Y=KmBj zodlUm3z<oJgXYf944Fd`O|1x-OG-e?=5GwiAW^cYW%c|+A(<o*v}69UkSwwlbYi}r zLpFI1Dxq3Hf)p{`L#l;jBk2A73n7chO;u#&v#*6LCJy|S+u6vHeK%wYu@I3(rv4qW zl!RMBE$;6_mXn`A5!qVkN|I*EK6DjX3X0CI8oHJcYcXB-?AoF0iHoV`p&Lj)P(-Vq zp_|A7Q^}!Q$Z1p4LjNH5#2SK>^DfCb=6A>7>{ECL@wH(su)R%b@D9?()auY&GLWm3 zK5y~BDUZwnO>gzoX(!nZV$WObCTE1`QyXub_K?>g$@0``FLAYHEzm0&^eACJsRd%| z{GX&fh#e*TNrr;hQNjU|Aw<}dkcY@75c@_rM2es!`>r}fq%xufc9f7$+__2(Hh=jf zlq(x8&)y!IPdb9uXYULxATvRKWbX?-Ojd$+XCDeJB(FiovyX%xA(pb@b2vry7wG^x zo_#g+I7u_JtDz@I4(MF=i_lZ#G#6XCvG@#M@?Xw0zNnBMZKwI3gN&VN{2TgJ0f{*5 zJSl-XZ)O`|7f6<!=nGj;Hmro4231?&5_W~SloMHFOC^VEBpMX5z$ffFxeThmpnBL1 za@SPNu$#oLyqK=}f;wThNgSxlg2(oE$q-N<s(WM_XxM^=VWp%9G;KkPu!qFeUexmQ zf_7n#NLA3v1wVxSO@d7I4tqita%G^6w9e-w$U#hZXn~)@3o;dSk?JLxXKF~;D|)3& zlzg&aRM;DGz*I`uKja~(m&MQFEzulBNqM24!#h#~RDR(*pZ6pR<hwA);R8toeZO#K z*hgaJB&KV(utnH^#1|C5@aM2kB+}H<Fl5lz)AN1Zq=g&8WFy&BZkT4w0?nt>5o3+1 zrD0YEa$(ca>pcsXhS?f1T-oRh?U|kN9CUx-{;+b!JXg_zwCJ6WgRvguO66!A00k_X zR@2ECRY8=DT67}J*?0izy68ffi{Zjwa-WU5E@~0xY6O7>ExHy~!FUZCzvy0=n=!;q z)G}+)(=ZRC2()6+KVemjQ2uiKZ1l&X&tVp0y}Ky+$D%T|yp5y$Rr&M`Y>`JTAESO{ zk+EyIenzw@zgpFdc+kEcHEa1BO{$2Jw-z<26ksHS-Y=?GE6^wcSuJi+E7*8%Dz;XL zVeu5xSuO5ctCrCh<gs{4t?!LAQ1If}wdxuRL6M6ahSfLTa%G^di?eGrH2QgoT7Fu* zvQ{JG0ch6ZjkOvZ77NQVQ1;@zwVE2UxUx}9j}x_;8y`R$7hkItWBAgS|Fe>Liyzi% zWeniUB?H-9yfLz^F$@%X@x{n?#$!GYbj19{!K2$7-FbT%X!FHKi5-oK(B83&Er)k9 zcJey2k+Ot@#~P<WWtWr>?`+sr6}|SP>SDx!0+%?4cQvkq>d~yb@dng$Nu}@~jn7;e zsOJ*j@ScXdkEmtXlG@?%Mh8>%!xN07Tp4Kek|yE(jYwZna?X;+_K8L&Xfaiiu@ba_ zwm!(X2ii^R9AdmS)joWf5$`8zIlZKF_y}Vf=+2VtT0a?EK~I)!w;yS^RTCxuThcRp zlu-<_TiQQ-jM4Qwk$t!HvHf_1E_QQd_yiE!n@$LyWXdx!J$$k$HZnZj*bB7`TG}pR zs)77Pol~f$8<C*(OXDJD8fl<GOM67jHny8ejF@YjHZ?lpXXCS}NfB8_#p<HYJxixW zEHHdP$Cu8DSY*^Ul@+nXXb-wUv*pGBQ&|x!jY*(qOP55fHg=lIidbtDfuv=tBi0+& zKn}}(kJw;D^k+-v7QRM*_iB@o24cVXzR8#cVrK=L3^ah3G}`1KeUm|i@XDN6nw14* zIx>}mlJ{~>QMq!lZ?)UfCL<0?vi3F`D?zNi&Bl5VYj3mRH}GqFn~h*0-ri;-9K_n& zY}A30ti8=fLoU(YW+M$svi7zZMIhGR7NZ!%+S_8ZPyE{67Nd(0Z*Pmy3&h&nV#Gs9 z*4`E)kxR6<#pto0)gr#bw;KI~Q1{(;%561v6@JbBFb)c#b-OFH{loZh>}$5okdJer zvozag<nXt;(6iRPfgQIS<TJ~}IqY`B4#ehRyHSAH(e^E4@!nC*wi`u4d>*zN#UM5h z+l?43O0s#_Zv4O{=3%=rii_Tb+82v+=<kuSk}1fK%T40b@xJ63@n1xjXO58wVtvUm zg5<A#$uYu(cwcgiND%8wjxh;JvcBXPv$;fHa*T~!^cM<f>pP6DM6}L6%^k)X5c@QD z7#l&X^&Q4*L$uD;)*Z%2A>R59L$MOIu-11Nql66B`VM0fmuP*5v4SfFIq#3fxyBw) z5SLvQQ44D?*SIQV+}vL)F4wpV8QVH?>Aw7Hd%1?cr|21LFW0EeCECk1qPawSc}BTV zR+440IL~kuLema7xaAq!>VM648M}qhwF4hJ?=nK#e$93p5khF~p<jY_8$bQ<HQQrM z5JI2w<E{1>Hl4p_dkqI6)U+VpYOfJ6@@uxw2ople;Tt{n84pH(&GsA5g;0aTgM02b zj{f{L`_nirgk~J>*85MR_ujABL1Um0>V9;w-$CR0H`yVh6tXEaJ7kRA_jS5_BUK1( zq*=c4=9{d*_z$uYniUws_J5u3urWpmmHBJ2-(e#Q<V{s*bpP{f$s<OB5K5-m5#z-- z*-_&iWV>i~)R=PM>vVq^vxU$Nn*C*14t~v!8U8}3>aoRs$BgCQWXFwPAsa}u<3{kI zuhSJ7wS~|cniUy0zR6A)_aVDRvlGTIXTDB%()djXojSQA>7=psEX!`;GN=AcI%T{C zvF+%TfzFAHt=FfFsA4Xo&8c%#F+zMhI%RYKvF+%Tu@_3R_4<@iz$Mn}Q^p-G`fKZ_ zV)1F?G3cVu)vx_LZM+sjFVD;yeA<w%@eCPf=M6q<j1l77L$NU#ve2_>zQx93PzSE^ z*T2?TY*Y|J)6NzRE;bUr$<7;tAlpQ<^TzIPvJ1u`$j+X%9&*7bbK`5BC5Dp_dP}ns z!{yf3?6Of=2$eaf+FUmF@p}bgE52eJ6+)xWrTJblEc_0@m&{)XEjxD#Uok%aUq<*H zfOM44r;8WjOX7-=2-y=pT|<5y>`R@^h0vH{)}Fn@jy1ky6@`$)d6p$8-)2LEP~`cQ zBd!=r`Lzo%-8Ey45K5xiHRH7Qb-Eiyi4fXEvl~WJejdQ4iwwSHbQ7ZI=xM%pj6R^> zE{tk+#~8vTu3+6Uib3p-!yUto9~q0g5qFJVT%s>`jVVItf9$;pSXR~gKD^f6`yFvW z!I=`197;*jObg*faK-^e5k&>T0mXqF2(yBufutO=L9>L^%CekDQqcp6rAeAo4p~9k z;K@2!G<dZ7?&n$Sjn~Zno&WE5uJ5|O>$|w#`+oMlp7pG?*Is+=wFmb8*9Q*<opm{R z_N&r9bNQ<!;XL7(_nE6b&s0^~=dLidBs!hAuN&T<<dgQLOYm&;Au$YTU%D3YtWxFs zr|W68B&MB^P5$XB)=Q6L=_||}rMT%i!cL60`(St2hPV@Gie9YODpDk|LTHE#wpCsg z%@^<HR6;|SmCMSbSK(q-eDf+>)s4^)D}bihz@8P12N{nte#ltOc*X~nk8k92NHv6S z9VpchS9>b`8;<51;WGPM&)2r0Z>q0NWl5p14*L@tBH(vhUQrWOhetV=vV+-EDPe3! z`=D=G|6jHH^7;;B^zA3AZ>W}0KB}F4+eaxwI4`x2cPp!0|L?ZgwR+Jg)K={;rbywM zt3Hzf+Z0(qOY5Xaakal%uWRKZ9Ve65$a}3^5B`V#i`f4^&82GPi@w|F>NQohUCw2Z zErn7j!ryDFYx&nkQ!)SZ7Na`++19yMiP!R{amElY`b2y6D*IY7Nx!oGsy(mOl2nH) z+f9w_s&0)}Q?z622sd$LLPKolT;<Oj=$U)2&<#YpQla=i|A{B*zg&^mTZ%XSFMpEj zKa2lY<y!g(<-10zt-MNYSxUF9q8j=$1_KRoBimgWmCqe)M{;UUA6=#PVnw0KMd&?N z`o>Y)Yash)u#d7;oJaY1k;SQmhRFVpY$;S;7484W|Fi|vmQpSM!0#=uTGbFrQK<Gu zb%av(682xgTWu}dn}0`dk1AnBA=S-$6f0-_Kdr5=zv`bfN*m%GL^DMt$0Xa(_93>t zskQsKZ=4U=M=4}`=a#<x_6+-c&Z(;A=h*(5vF3N%5Xx5Tt9%q*y(fAVs=4p@qvD%| zR1?+CzP0_I*lH{LapeEZHbnsE3TE`JgHqJ7zE<~cWl{`r%T>8*J-w~{KUY*ue6btK z^^QnnOA*O<weDMbf6k@Wm*!xG7|FS4dr2{Y?LVTo6s1!O8)61~DqF?B+9n@UkKSs# z{B_<x`g_aut-r6G&2>=9e72RckZpx3b-7QfZ-oE0{Ti3M1!#yJY`?|m+w+zF_fgfk z*543nmQ|}rQOPBq;<6f-(3Vx`yS_9JG{ooZqw+3e&#Unpj`l5ks{P$Jj<Wy$yyfqE zs>t7QxoSLA=&N7<3ay=*`KWUZg_k&&uOf<QeVss4GyqDW_7vqA#7aB%>B_d+J5*V< zy0<?^vyX2nzP2j&f49U~E>YEO1lzufs;9!Soa(Dg@ljNJUhSjK#Z(I`AJw*MO?_MP zzrCiabXB5S3txM-&$_4<P(HqGq7>Cus&DwVhO)2jhf1dwz*Pt?<*#h-83g&5>~D#s zoJ&Q%TBp4pS7v}$&g7L&?d*+1d$dig;b^|8q*%Pa+jI>n)q8HGB2Zq7^Ll-#WelOV zql%{1?$0AjLHmO8H^oLS@oIlkR4G#IDl*_-2}Oz{jOy%CwY+cptJ)r8AB8l>FmT>M zc|T-bZR^k3R`?y;YU?Ol>6f8nkA8vX{|#TD2r8A(d+eySO%cRCs)kqhhrgq!k@i}i zJvhF)vhgU6t={WP<nO(%rDmAdityjoRokj7Hgr^yLLHm>a?KNfhEPXp-<tfjXKe&i ztiaZ^gqkh;MyTD_yn79$nj#4$S|XK8pZq%-tV=<PukBSb+4DZe#~FRg`oFggq1wlH zwE4Ty4Dq~AO>W_~z1DS0I!{zHLqpV_En;Ngk>qOSC7;MtA4AkCzH?D;%#FYMUm5?8 zxy&m{r`noVMDE6Gp-Lp%n@S^_6ojhIzP2eUelOiypCi93h9KJ%->~w(73Z(}=il9e z|M^^9wWk_IRa^a)?H#2vejl-~R@Bi!3K~hgBbb3}JAY=Y-bKB|o2xEg1*-q2Xb5#K zT$_5O;@e8(b7g96F7H)wUnR7Z`h&ubd=2XFkFn}}P}STQ)oesvfl*ht6t>{H)!LZZ z^7@i4g>Pyr_PmL8<<pgwa8~-T9nZEpTO@xeREY{zG+#y8W7t#SIJOnm*2K7~R^Ga) zc2=HO*GehA+rrmY_4%WsB3~K5D{5tMYe+GTYe;B_xjwe)4=VLqGkkqD03(B;TSm=_ zRXeC7o!X1l2(QL26`9c6Z`E->hf7zf*E)|-Yp2TfZFyCS@AmcGY705CLRG7)>!6O^ zgjd!|9g!*8mDh!Q*T^@QitLR>GaprFwe1wD_E$YkDPDgn<x#Y!6wA5JYZ#Tih3yiq zi8>40%8H8MyYAkrE45cXy$X%4sy^zBUd6dqd#dg7cUs`;_N4k;+3T*Zr}9y{!mh|= z2;Y`bt)Z@Ft8v2@RjOJeH6|$h>sc20UpeFYzd5G==SKup&ugLD3cl^;+cMYERnJ%J zOZaDdqHkYS+f<F=JNO9x=e8;S!CTO`wtsyj{W~)~-)R3m`vb1|C)}6RIx76kM^S6t zjaz_FT_-X1b(rtiliK`BMa5BVuC~tKS&OUNSt+WXbdO#N->X&1R$J{C)IsW6sa#5@ z5yKSrHMOZ~6rxm9-xK%sRP~(A*Fzd`u0SA;0&L&FwmQPr+NNj^-4f&}_5Gl$XRWt# zY7a)0s^&22dZV&^F&dtR7|j03Y-h2pd>&+b$?s?gweKH%j>ggzYiLY+8EA<&fHtuQ zXctF)BKVf|*S4xHwSXZ`15NP-&=NlYZNj#e)>`=xqK9yAX~H_8S5ZgL>!FzZ%z!02 z!M4Gl>K3uq5Dqcy_gw0jYY2*9B9%%}<9|pw*$RC}IW<ZvpULny#2n70j*@EJcz~71 zIrUk#*PvWOJPtI4QhZZ=^KRi>sy=UV-oNhE!Q2je*;BR8)&9!oy}$IiI@f<sQT0^& zteVxR_EdY#39jdVuk`<R<T}{vz2^&R#85|VLPIEpMg#BpwNg~Al&vDDHmU9NS03ZO z@Y$Mbk5GA){oU`Wszg)NDp&4Dwil@;Dpy0;rU?Fn;ypK1ch8ll8WVhNs=xORdu_BU z>#X!0+++WGD+K%asCzo<Ziw%Gp%khK#zxfH5^5~HdWNhbP^%hzCHcx0P&HAh8N6MT zt=5rj@7AQgYH|ci+=>!yg3e96sg&0gwYk)}o>K6MIBXFcKY1eA2Xg~%XH`~h-YY#x zH+*7VU6!xEUEk}WcJ^+y;V8uvW4J7}rfP(wa@D$cJ=OMAslIKZT38(iX$&#=ZlHI) zl&;o7-4*g}0d>5XjMBZi$j9<oBeiy>KIW=8z9s&(G8>UCotJWXOUdxrYE%bP<Z%l; z<x>;#_vTflsO?+Go=Q>AnEBpkQFmv2j}EGL`JVf$)>B7(vb}Ak`oI>B@0+?Gig!Dz zRLW)W`JlH)DgTr3G+Fnyq4NBUb=C5<k-b}%j@PD8bDP@KD<l7PJ$-wW>L;q)t8Eqk z64!@(y!BMN?WO;7YpCsVt+rC@SO;;uJ%>Ck;oGX$Dy0>2c}HEfWmRwQ#-oj|O*OxA z?E2qQ+HxJbF{(c1+gDXheEX6b303=FjlT0bn)66ei-zdOrCgm$DJrk>SBknvbFF*R zv<0OYgP7`ig(=iCqG?wtw%@NOm|_Z2Eisd8qUN{TIS!%se7shX{2Y|`E`#z>cO0&^ z%YTpK&841wQl4ac&#!A0sqbH?n1ol>;Q?M3@~0=PsOFEo<nM12PjUUPPF)NA+NqCS zBbU<Y8otC67lgj^|6<NXXz-lhykgEzb@Sd2Rh|{>d5-sS)l0ofZ3~bh1??B=PKWp2 zfjUc7_YsbAD)m4^sK~wwMK-mr&bg|%+;2G7FN{i2J=XRzl|`wB@Mrrv#<suPhUg62 z6iVp{TPc#~|Ay8n71zc&m%84fTFkffKetUWf^$*dHuzk?<PvcugpU+dx@t);s`$10 zi}#F7?GXx9A67o&Il>GdRElcVZ0M>)OXRVS%BxC$loj8yl%gUK;;IPy)W*NIa&<1> z9_8EH=`8Zf9z~^i?@j!*y^QOlYV~*8!M6^oU#XhZ+JAOdyNXMxy^1BpOI*X9tRH3j zl(y-tO4+AgCYw^FK1;1;Pn!9d;%nCFs3e8S*S!f1L9-*JDBC+zGS^Ww|0_J{7<#2b zk;lA9=fAbOZ!}-~e-@ecK10wNdE>O?x~ch<+UslA7yCG0nN(+^>fBJFI_FZ@6?4}s zukWcizUX_U>#ud+Xuh86E`vgK_fVaODx|IK9f5tfc-K|b{whwb?p6MJEv_Atw)GXM z+yZJH#~-ECx;&r1h1-_U5D{0{)CYXmt{<mfjq2LY>l{;|8WU7rRsT^K$<-Q}VkqKR zB8j8b#y42Uy;mQ7nvZ=q+w@->==ne?)OC9`&OgYh3Tyj-A;{kpi#e5aU&Rv3SXWo% zmb1MUXb2U@*H+4F?6ZY^=&40hY=@0!m|0go^jraYFV}D{UkOp`s7gPMea+jdzV$rL z5tPq)w!dS%#L?7q3$^2?_nfXaf)uKcQ7)9gSKCzmRW23Tw<ctpVk`Fn)dIeX@>gwJ zYkU1^O)+9{DSsVV-M=6oDHN*yK>hH4b~maymmb8Zo(NKF7Yqf@!Lt5$?3>uXBm4V~ z;dJaUu9%JZKI7VnbKL<n`B?=iR`HnJo8zdO3}id*cQix-+i5^UOk$g!;F7|NG!9ha zsTIs5IaM9$)c!@IAMTW~UTdQV@*L^G-*xX1LHVrwgKmgFDqeq8XVqKi?7(}S<VB7^ zI<9!ULUk+mQ4X{6$rVa%-p_pW&wTQp;(3>^qRPDtA46Q?eMI&7%N#+W>OXV_B>4)1 z_Z}iC-fJDTws$X6idySh+Z2AUQk>?DYVDLwGap0izULQh*pn1PC|ezmLZKTXhEtV2 z7`B>G<L>kCseh&<BGuBfbZUR^%tX~k%^x1*HhGq>rqXp(Db%*6W4)oDtyQ+Fe>#__ zW&%p_O(n&9<tpncPj#k0^f;BG`j2`_N?jB9ob|8yD3*`B-ZfI^+6udI>#4q^6t!MU z;qN^|U(R{gutGkD*!TzA6s4@Z12p)l15+IS-AC%PdzDw+Nmuqqzxx=1Y*T#2%6E*a zR_gk;Ivy!ojeeKd-?g6VscP;!8mK290ywqS{<9gKTHmJZ>1)$*(-2Bm<L4i3?@033 zslGA)-1f%#pQN@y{k_j`+`=~1)*E?GF}0E}su9>1eN%mHb(cr=ZPm`oR!0hj>bld& zJygnzKtsIDwwkg0Z=ssitGZFShJIf6kGA)}U6igy<3HM0?k~Rkj;}p>B$e{U2i|?b zd(TDfUp-Dxs_tVxwrZvB&sn0baO1v?TQm}WKtFH;Xl+G3{2Gb&q9J~b@gutszw7X8 zC2kVU@oSG?NBpiAoy2YUdtY=HRU!(0(V~s$FGA!1{9;81{6geF{08Ae&x62&@f#w- z@e7f0_zi_U6dVuVcyJ>862YSpYmx}X?^gU8<2M|?MEu6!mx5m-{3eLWC~u1BieEka z8i}dm4*VkU>xmyN&%rMnzk9`W#G8(I(-Chv(q^EH`^C>PR}95>3rC1$Vyt*hOg5g! z`n)N|i)|=t8_L=S-|b=|e)aG>Yix(U9r||YrN~hVyA<|L)L<tf?-Y08_pPxLX*-d& z6KT8fp!6=WR_sOIz0meT+b8bBZz6v6@C!EgA#ESh_95*M>UfAbR31XUL)bQlp&f>H zScJ;M&<;a80__O2BhZdOI|A*vxXCyU?YIa9uLai?C!n2xb^_W7XeXeZMBbCoP9pC~ zXeXha661|iuus9hAjVr4kbXf-1g{0x70tx!c!xoI5sx<@bO0`Ng^0=<cG;uE-EBU0 zbr&DBIf(5yt=+xub>fkMCmOF4X9s@LI7!gE?vez(<1R_iyX}&Y>j&8MF1s{-hh3VW zch{u}dS_jlpm)`!33@ACnxMDQZG+Ff?rrR|4L)hG!v~R)Kj^*2+eF~d61k22x559g zwT(;JhEkd$uMzTI<8s8@1MGtLhL($2cwguyv3}TfO*V;7*lrSE0%bVklZ<EMTO+mc z@D9KM!zuOQ;pAUFJOQ>G(HVB15#50+Mo{W^jFAZ?a<-s1!Oj-+2H4qx-tIb|*Kt1A ze?G6{d|t=-T<7_`ju-GItQk!S?*r~kei&$_lo%JpAjTI{9)t2*$`imr<GVMxAf6t- z9QK9rg^cTf-BLF+EfpQx&2L>Qy0rVUad*6-_BX%ol3J|0jBh^r*6xzps=FMUlmh#G z#>yLPb-GI<<Tt-f7QI7d8)G?emAQ}c5GyCyu4X*Pc!BXEqqLE~i?IP?GvE%{itYA{ zVT|2bj{<HEj{|;cBmxs;3S&BOm6^$Q13`MC>>V%!7|Zrh#^TpMHG<^RPvo^rl*O-~ zGyCy7S;;<Yc3{28*#3G&k6c@?km>GxTZ_<ld*s_%haT=hK5v`(Hh1VLwvU-1h<~Xu z={w{)dF=I1d#sb60hh=NjB>-5?77EUf;Alg|2i8=WQe3Up~lJUH_VdRw$KeHWt{B3 zAtr+2jDp^M!=Q*b*>l5~h$5`Z4jGP85`gr^#&AjRUksPDFN90l7aHI#_($Y&Ueg%% zY#@4tlo;8z)D0&ivTZXqIGX0$MCjKMv0U3Fz#q(bwwFt46RKMRYO-j<O_4DiKZfJS zaQql9ce%WyX}OVa`>11|$aV6$4Y85rKQb~-ZrE@?@XZYmLg^hazFj%+Q={B=e%a#4 zbo1bb70~lXePJz^M>lMbOqXW^%OcmoXO$Tz&!7dC%NLs-GnUIuO;5t!(exzjpEsO} zTrLkc{lZA%(v#2we~e7xwaZ{7gOv<csC}q4XiUhqm9)LiL-lOq$wwbpWzOd|pU-Wc z$95ju1#B0vy^QT;Y_DW{CEIJ*Uc>f!w%5zL8}r(&XQh~xVpfV-DP^UUl~PtJSgBy8 zf|W{EDp{#yC6<*~wvV!Xl<j!7<JmsV_Gz}yvVE5Aa+$gzEGpk>x$#aQ_gL$jj#02b z>ev@}bLe1TK<Efyc<2~li_kRS7agYpdxhQwY#o{n92A-Z>>T<q&>i|X@Q0310!8Ta zQ73tO?ZX~XVl=Q92mVtwv){e(V%vP%qZ`|HZDpso`(?|Ljc22>QJ<w<8`ygX?6GQi z>*U+M=xFO%!&|LJp1Zzo&oKLE8*l0vWv637KkRV_Wj}kPO(jMcawW*__MXk7d-k*6 zwrP0JM0@W|qhU|jlmff*hDkj$?Y}mf-ZS040RH{pzse*`K!l~63VNp4hYfKwO^4FA zP72~IMJ^-co+gR*<(m>@9F*lfvpJV#+~0Df4RzjR2kzapv}?Bf05P*|dpDhopfy?u zWmL$!Jp&DjA7s$pm&;{^81!!KZ25hbGdf#d*wiArgF$;@wzO?7k=e2lW82L~@X?CM z79*o+PifLE+veFk2K!^|=AoSl_oJ*4n^U6?p_L}X=jqKQh6{Ucx_h5(<>m#^T?~5j zeztAx=Ec$Bs6)M=eEVCQS4HRB&t1P0{`)o`fDgTo--S0ve-XU|+oi;aG3X89%k8&s z35hAP$8NbfrVH|RLTw9HgvDeU%kjO`Ok@0(ZZY}xxm(6`j@6Ox*)kHI^j7j1Uc2S? zg<F0^uEkqQWDK{!I;{KBm@pn!1GzQ!*($ayh^7&8XH1|$duY5t<LWkU;dq1IZ&c3q zK5n-|Y$vf#KS}StKFNBuecK!LdR5!^zj0F1*1^%-$RCy8B;W48bxh|Bl)j?LI*d{M zd!6G5YtUks8t2=7=y-3hquLG#J=QDV79RRhul1}HGv?{k&Y`#UUZ(9{p>e(EYkN@W zwB7|?9q(vO!&)qKUEoqecn_V;>pmYit6jdF*6!2Z+icU?o$H;Cec_wl*|5h%ECK%5 zdmU0Q^<Dxr`fLKa`V<)V-`xaQ(zaEfZBW|x*#yt=eez|mtr@^L#$?6}V4guohdhIh z4tXf?62^A=&UXVGm($!u#@$;hdc>iJ%yBQ{`ka&Wy~b*}aBFd&7~U@ZjJ=!Q>yu)M zjN^UsZOgY-_n|#^0FGJ9x1R5Vhs82HVe5g7!ZO)E8#tn`bWpF5j)t91%3|(y#oVuo zxj(oZv_;l%YtV=q6Hv-+(!eom(kFeJIZkf<s&4~FXvimhD~t~UFEy?(K1BU13_4C# z7@xqdg#U^rM_I|Y=a0InA1Mj)G`IFy?hog=-yY?DRm1va)-Cg)mU-<C$#YvzMp$NX z;NAV|n6$4|aD+gUS}f3{whBaQUb`UJw5MFy8rnbK{?pdp{X;lc2*>Hbsa+VuO+J#E zG$O^AwB2J(>b)y@O<OtgO2T5}O`4Czo3y{5H^@G3(A+FZm-1~%W^BHF)RRxfrfKC; z$!oD0Y|my~Cuw^v!Q7zNz$I*F+nPVwu-g*(_?rn(DsG%Ga2@QsfE7394BTW~2wzoy z6Yr;+j9<e`j7>(J9y<ok=V+UZ7CkNu%+vNwJsJ)QbA111(?OfiXUOwaeE)cp(Y;3q zly`fO(hIoF_^fT`LEDVeJ?;R0)gub{YmdIb`VoVHH%E*BdLqUFlOiTDP6tkjn9F!C za9%{yLFGnq#Dj>uCjw)X@l8aDvCsH7qazaYJtHuZl-95h@mdt1o_NnP@6jjO|0MgL zG|2xX`=4b0YGc~A*9I-)^*Us{-t3frHG0Bf>zpxbTjijYCe8mZVvDabFED1KPf~>5 z0cQtE<Qib+Iu3<=HK+mX?*`>M5*A$?<bo{+pEG8(KIMPTSiEhd{{?QP3*1T<*}llO zGz+((|C_He7H`8`6nzGxu}SUeGMBfgKRDMx+alMo6Fv>hydGhL*KlncuxA7IY`~uR zsL7DQ4a_|e34@pLzS9hzV+R+QbOb3d?b{P%0s7mIk?Xm?H8bl$Ddrwns+FeO2bk+k z8b6zvH*QaN(-CC-;0i7=-+s@QgM*74A9ZZltrALtT*-SxrAZ@yB}(yx9p%1ViI`%@ zX~b+X<Sg$61*{ja|5@H6PID>w_FbLs9g=S!J#z7obG&D5W6Z>UogmM1oEnaEnR8v{ zXwA&KP`4Ug&j+@b$RbCtki!9l)r_wPtdk43N3-sXvoMC!zBypy!NKRb=I6P#G)H|n zF5h<Jjzw|Vw(dI~2KEkkK5mm^*p4-ExweWMUqR|O9e3WgP2RQRow#jsK4Y(~2Vg(4 z<I5h|w(vn`U@zKnE^ZtAzlmFi`Nl=yvK=Q4n(-P#TbXAAPsC%}AJ@<i0oCBX4R z_t|cEc0u$$+w(gJUk8@zBSP!YgF|WNd~9ew_RZ5nsg!e2+@TkMtwS#&Ce5GgSTqXN zv1n#k2kVHjh?PKA0$B-SC5V+ERzg?_VI_o>4y<%wr2{MNO*(UF&!x0Ci(g-55?084 zj$=Ev4m-rXi^k*xNjM-No^A4Z+uX+MzD@qL18a@ZY|Cw=e`;)#vh*a*R(b~<7)CR? zEdg|<Gsk_AQQFC_W_ugsImQc&n>5xfeW>0hTL6&S<n*xRth+cbj&6vsNso1lHdGJG zmMu4aGmMVl_INsi4>0MBW0l#(qLDn@qH);5tBO{}$3U+a6w4TI(U_cM(U_cOy<hB+ zY1SS_n(ad=UD_ngs=u1>bm=mrc3nkC?{E#`D-3CtZD)_+89Kser61bUtmxwI!_%zm zclHKeV1?egnr2;Oo8HHoX3^Ug=c5jv8uPi7`5Zrw>yXEF$m3Kfs6!stA&<+=vz|nW zdDbI4KeXp@S$P({S2d4g7I3Zt&UKPUuqabx#E$3=95tdJaPo*WL9LN*-?MWhW@tx& zY1k82G)ct%^5BRR;A4z0Fs>hwZqoT<x>*srWdt3IT?ui#XT_NryAERJFmIQu>l&1s zATOZA0SW7^MWdEBS<h{|9{LK{G}kJ!zq;$@nAw8H5Gv*Qgkq%D3o5nf_*-hxk+&3z zCu}xfbEx1}l}_4@xv2S=kuJ9LVNV#Di3rnxJ)6%Sxx~yL_2kIqT&s1kSB$JgoLTJ( z&{tm`Ss+qkHv{ia+ct7GuD4+9vX)QW3+1u2LnB*Bnwhj`Jk9l4#y$Kj>`RT$^8Rub zb<1map4X^`YZYkHo*2$0UWR9av}|;BDBu26SxMqbzKXIES9ltQ)v?i-9B89{7-XZC z53y0N>tLf^*TqKb-o-}i9&V$S3Aa%VW7so>bH#9k80g<LzHE`tWs7`b5l0M(N1PTz zk}#T~UvacFj&`2s*zq<R;aBn+S@2nXTP%CVFp}NuR<c(#$pF&Gd93X4sM(x4pOqk9 zuOO~N9@o5pYrc%jTE=C$#4lyfjb6#QR<h?B&byx1qL|mBl=D__-h6R>YKc+F>s!gW z2%Ftn$?JZU^PXn?G;;kId6si!8o!i{NIK2+InAY<<+W>O?cMZLQZq|-I)XE!vE{px z&U1u(`}g<VJLHhLchiZa8m`G@u1ShjQ0^R)X{;zeB2VIYT4J0u*R#DXv+Wp{b)a2| zJY=SBIFXbscR|OiaNoLbOp4|AR*AIi^rf_HdGf98W9nd>sTY(jzkJJXTW+@4ZMUUZ z?RJ;Qen!{bl&e4NY)QGY<;2}|-uFqieQY4-qIT{uwt+>ZH?TItvw>C7>!i%Y_YMxi z|DMP8jqPWoZg{Ux5c_AsK07vBrd511wv=bE+0MBY&T-k!)D884qe7Yi>FhGZPTyq9 zmXB84Ij#fe4FNjC8elZW6&U$+MTzVJ<@2y|9tYCRuoa`n(HhMh7X!WFt;O71in$LL zb03bi({;{RoZDV%9E)@6g|2uz_1<h-$95l%+ay<4`~al$%0L@mK}8%~M}@y9EXhti zb|oM0(xAUQGQ&=@+zd{g%`xXg$9Ty4eAe^W&SSfPBP_$Y=iU8Q!n0n`GCS=xYdF^$ z_F2zy*4t_SD#ls-4~>iMRFhoi*-mJA*>+EfQObF#4hgb?*P@cw;wU2H?Xz~;7TI$2 zo)USQea_nNzkAa7VvLQQ!j?F@w96XbKu*g1@H#r4{xY79#lMWF<L@uy&!bo5wX5Oy zVHgt@riPijuD_>A9q2{qE#-)rEe`<?VeBgEbeZdKVYI9lREIGTh${dNYTFP8wQUF1 zyEu4!aPaux;E};WTOr&*S8gRn&Vy27bUG+wIW?YhB{8NUni!ISXf1}!<~XxCPKJZV zgf#XKH0bI#%^7|fzubJ|?RWeF`5Y?Hpw?TC@isvgF<vx(dwW1Y80Kk-6W2MPeq!9j zO-^dDP26MGIVom1pZ5h}t!0z>4*D{09%H7__nnU?7O=exGv`KO<xcw2-AcBxcf8Xg zIuqxWXUFbCS@R~HbRKypK~}@gokaM^q&2)o%Ye?X^=z+XyVx=Jk=xsqa-0g*OIdGb zrEYjCDc=^j_rpmvCmG)^49B2<O=^aISTm{8(IS6JyGq9m`5{I#T;sykA*1`=L+)%@ zx1iPJO2^F2^?>wE+0z{JtYiE0`$Nw<{NIS2+)C2-R?k9Dkd+Sla&I$9U+k@PT*_|@ zo!)$Xo^w??=u5XX$a_4rhV#}qZhCI~<o1%j%38w_no0V;Z>592rCZ~mH)B^i=o`3~ zk?Y>cmpPX-cJD2NLM1kn^abF{4th^^rGvhYYdN<+zihJQq`IAx|AeQ-+pC$RuLE07 zdTVwa_`Eu~4*S$`{`~yL$<2)LeY=2hz&hHWz8_r2NpI4wbkNs)1EFujy3jXr>p1D1 z+LaFaT5k}d{c~~<M+@R;7v$rJ6QtwNmxqI#^q%byc*^t;_6%Xq23%r@_N1>8hdAl2 z+a2K9G`$0Rc5s$IKYntx{P(^yp&hg*eX+QMlitDI1)iPLyRc__!}D$#6xA~H9pf%e zdKY&%eEO${vroA57p$+#xc}W<lf$(qed9RXNpI$kfoDp34130~XN;4!Q7hxkcc(*F zk?AYQ)Fy4=Pw(uGMXrVEv79TGb6u1*h##x{={w1>PI`lPGdXzwo9SUjWyf9Vm5#Ri zUj@?FiR0l}FDT5&-2Yj6w!CkDi3~HM9u-r<jKci^Q<6ANnv?eQN(X&Cnfh&)DeWyf zvNV&g?0>jD`A1BlBSkOddVhZ`@Yw!wz<=$}oKoqauO)|BKkR>CN`{lh!!V;x<tErP ziwMKDuM_a0ajzL)>1`%&tUMyatj?9+ObN4kSN?m-QODQ8mu$`WDnT=2Ol65|g*sSM zXFKn2*&LWt*#=+#5*fEn&0{5xl>#W!r#6#c<Lk_HZS$$ARKr(*|6cyV)K-$>w6`9w zlo?_2iOPBz?UlljqpanX)K(Y5i~B@bYhg!Oud{M&e|PxP*Nem8i7T<L)D3+z;#@Q{ zOa$(+;#`L-hi0U>zN}0H2ER8hBi+^Yy{Q@5&i?P+2ORfaUPd<ir%TLcjegel_ts`C zbJBRb5~CUJl~{Y;J0er8Z{DLLxN(3+gR~02X=`{(#aZD8I!#Nl=t}H*R`OjF4h)@^ z@4ENE=xIf+XAjJomT0XyFmKu>E~Uuz{(hz8B6WvOT{5khzin0IDu+_!+NbrRX{Am& zCYEALH3}=mHu3=J=vm4=fo343(<->Om0a7S+&4~hP0p(N*uO2AHa#74q4m?V<&*E1 zPtU}>^c}|IK&oe^RrvnJ>50}G@7JG!I=s(yU_8k90g%?B5mHaV&X&KuZ@_=mftFC* zhbYzmQ2QAvR`Ww8XsZsuB3F0lDOL>ILttlG35RybL~GWe!N`>ZrO5RN=UsJR<cwxk z!J%0*(m8LswfxW#M0g3Q&8*j;7rC~xe;MPuj7Ncit|LXATL-#YtXViC2rZ0brM5{A za?zXSYq%YPT=bs#5ZA%yo|Yl5*)Mi(jjs;DzXK~BT=b^-%k0@fr_$TzyST=`JfK}O z>%Gw9eZtv4oRx4Fy>H%vQZFdRHRI*;s5yNdK9+rAS&4Pgd*|bIYDMU8eo35~#7dHj z-aKE2Bc$n4Dnh%=%wV4kRx<GRe!rQsU3mq|<ZRcVMZa{K?dr4WJ0N{gcfN~eP<bx8 zraGT<&391`A-mbF1umMUEMr^=r4#Efk6h^*y(lPb4HUAcENT;Wl<)kmcTrC}ihI7e zPV3?;IebR8o=aJe*-pKn<z~AhcAHCVzOi!VChT#m%yPzUxJGew=5lk;k&l5RkDQ-b z>@wFalcij%Qf<>)@hhOz3#w#1%KB;cIjwDa1O8bT%>?poEjMOmo@dXqKxbHuIJEd< z;Qqy@!YcXRbS2-LuH+eMCEuH_<hg4l-=VJLv&9<z+G-8^*Klf$pf9f0aIPBdL-+1$ z#G(QC9*R5W=!#6~Zn~r}^Rg>%N!?p7yB04ALMzeL*Q4y;oxcZql<$%s<(cqNK7TyT zrJUwkb(i#Pz-cb!G}p~?(>d&EuES}rVRuPS3!LVdmYZgDmYZgDbk*I}^(<dWxa^v_ zxe7I*FM`%VgnB`jUG%-vK-kZqZdB(G_oGkb-x}hk`&l7w`aWm}*yz0+vkS+R*z14u zi*?_!?h7l{9kDLNh)0A38P7_*R_IFtN$}5W=Mr?64PQ%zPllVu!`U2jzMGCd^W8L~ z&g1$Nu+K6#9Zgm;u7Pq>=k@Fv#Ph~<>%*h?ZVdaEa<mGL7S2~DD_J?p$~oMpsF-!y zeGl&2o_1S-Z91QJ(|qzgpM7N8em**~Gu_*D&OXc0sD>+=v~nDLs?F?Hj{U&%T!%C+ zy#}cX@-oM_{HRUp_|g7S<EDKl(2r7U;OT))>lNh3Gi~<lfYi^!(vbJZ$S$m;x#?WK ziyzNvId2SOEO14Wct2`^7(dzvll-WCVxTk%i(xyQ<EQ!2UYX`cPdjAz?OprqZ5e*Y zu`OnEoY}0*)(U;Ofz}S!g1FrI@S*d{vE^}d^SG=U%&1XwgYF{~aLi?D?a)@AhpqHm z^3=1p)xbU<cA1}Mxy(<qT;}IYE~DT6(D*Vx%~HcXwubv;4fnblLEj-pdmammZRI$` zc>LJGxzxgh2Z8zcO7w?wD=_}h8Q*OoU(R*8dxdoDnu{%nd+WgW4I0%CxoHeP<ZdAE z$~<!&-J?&CbdSD%7S556mB?mR%`xYgZ0Y#mh-}~v2GX6Qn1FQawht(^S<4ccZuMmQ zM%c~JCTp|S_&xkVo6c+ep8lZwaEkViEW)?53S88-%NPq>^gPKj#`S1D?0vSlCs#B% z&1d~|CE-(}m|Hd9-s{Q8yGz-w;66~vTYNpX9qzgN(RVEJ?SXAa#?ZMy<Lqo1e;ji& zF@DQ!*@QhA2QwyRSDSQ~Gut`!czSl6i+b;TY?oUx+xb508RRWkQI<`muV|8PeTg{P zGB)|s?9(V6b>l628olV7#;3XGoaI`b=l)Z}Iz4INnU`&AcOrJ)WkkM&XAgP@B+hGZ zdQK$ZI&=RMCAg|E;lzH!s&nbp!g+P-P@lKzxYmDR1=gYV4`e-1EA+jKWn4;N9s1hE zKHG*9bVa)4MD;wnPCMOQ2lo)aY#fG`UuD{aQ?!vbQAe}``iq-@fp`|qCYs`@ESm@t zoupGFiN1zYB#VK-G%*x7StI~6L=teONCD0k6M=V&slfSSCNM|L0p^K$z(>UWzygs6 zTrBc|?{mzf9P<RnJk2qyIObW7`6b6Z&oRG6ObbuvwUd_UCT{|U%ML(~9AjD{Mux)f zE62f(l_|g>G8GsvCjdvvNx&pr-?T)soC-{n(}0uZ3}A-L1kRMRfV1Ts;N3C{IA3N1 zbL2gyQxr;Sfi;p^;AKfIuwGINY?Raj#gba!O-U_KDyap^B(*?=q!!pK=Na`xl?<@e z6K8S#&M7W)G+|J*E(S&J#&)<tkqcQ_!^+F7tT(8vy{uQV{yytRS+8RKEaR7q=NZ3c ztYQ3_@iL<@sl)=t#U|C^8MarlU1$zPZP%FB)yEpKUdnnIbgYq?fYdL|ul?(Z^XAXM zZ%tQ&dZNZ`1N_<S0laKx0EIQDfm1ZKXzK@Ak2G|O7>mm7Yf*`@7DXFk(RwA>NKdwr zo@OI`vW<I}jeD1kdzX!SmyLUujeD1kdzX!SmyLUujeD1k%35Zd-3;qx%LcBr-3KhR zEdX}2Q(575D$8T1vN}0P>Ea+IoNW)=ec6s>dx(SD<gBAkFxK7C2zcIc6YyKdZ9tsm z1Alg;11~$40)=xm&~g?7ozC6BI?lsDf9LnWKxbg9dZMZGMqrS$D=^sE3mD=Y32f(_ z3hdyV4eaDx0PNyi2JGfs1q^p?1$vyjficd*z`oAUfU(ZYz#&f84fRC4vpI02vpq1$ z8465xb_b?8V{X89chdSUcGCJTb5aXD<D_<8>7-UIbW&TbaW-j<e(nqcu6MQqe#^OP zIM>g}g>`XN-H3H@#UOH1*B7ucLk9-CeglTM>b1eTxH<wmxIAsJtzARfIYqo{c)NOH zzUzzj^+XQ7*i%pBxgNZ^o_NIdM2C8!!1ZcJr})`LHN5Pi8VWbn&~j4^oo?zC@oq{T z>88{qPEBS#jeRD&skdafskh8@Q*W8=ruDkpO}%Bln|e!*n|e#0n|jM5Zt5)sZt5+I z-PBu_xv2%7aZ_(8<g(UqSub;0>$$9rTvjoc^(L29#-0`MMBi}#`*!pVH$7us<Nob- z{BMEV=w^z`Zd*6>4ZmeT%dcH`^bNmzfpz=_gf|rZuIp<A3n8Mhw}pbgH%57gTbSGK zBR->-FBy09vY+_^bAY*p`BjVy<k#AKi<fUU_cM3a@t!rlpGWDdjl_G1cj$OqjJtSw zi}B%n()Sy^Ip2rI2<F||?+EgFkWTf>1>b3o?vX3ia+PL0^3Cb^w;$#OmJq4#<uPZ# zueyJTX>C&+?Lgbq9cZM2Q{7p87ieCs_1(O@qVE~zoqcf#;&`nzmus%pEL}QXbCl*3 z&3M3^^9O78XinC=+K=jS{jlB4D~EaNDF0;5IhqSKmuub)ruMDU%XmOpm7}>enAY2) zm!tJ^s^&timucRu^(wtA>Zy1h&B>Zm!BlRwUQVg6{BkuHX-@H1x@@SpmFCu9YX2y` zoT52bbCKq9&DEM^fR3X%N^>$ez#QdC(aX7-3$<RPm#a0O0jIk6O!5Tkd|>WZniqg+ z{R(w@P9x=41SY@LU`j93db!rCHMef8(v!hduhn2$-!i?N+C-(V2GjP5YO3O<XwKDK zq`6#kwdONmDql9!?V-6fnB(c?6wSGsi!`qWQ~q+jyjw3<>t)$o#ci!QT63!A1)5iD z-mSS>^BK*ug^FWp4hB<uwARbfnv=Djs+V&$FVI}1d9~(p&AT;MYd)hH(?%{wf@yo1 znp<gZtvOnAGMMU>s+SjNUafhz<};ej>va2Qj@F#2d4c9a&8sz+Y2K~*jArwC9Zz$# z=2XoKG_TgYTk{#svZY>6&8;-I)*P)lRr3PPt2OV|d`7bwtm3uO+*)&#=4j0+no~9B zYF?nZNb_pVyEUKDoZDKJQ=~aJL@#Uhv{B2+nuFV_Wsl|}%^~fDcml^C516)hvgQ=c zX*xYuFBje<#1<SYTXj_aQJPaU=V~s}T&}rVvkX=FT4|2boT52bbCKq9&DEOaEjqvE zD9tIFb2S%fZq-TGQ*(;uT+KzA%QaVlsXtfiWt?1cyR_2m0aJReUar#1;#Q^SXfD(& zJL~+Kqco>z&edF`xm<G<n931d6c>SM`;=?0(t5RC#=larzXX$CuwM3PF4B6LUary{ z)m8bYXwKDKq`6#kwPty{ULVa-no~6AYA({e22A~{TrXE^mfdt5%~6_DH0NqA(p;{& zTC?n~^J~rlQ+t))q4a9a@=ooiIZAVi=3LE1n#(m;YnI_Uzvd{-DVlRN7ilinT&-F5 z(D^k-X-?6ctGP&Xx#nukGD7Fq9Hlu$bFStx%~hI3q{<hp*`qmGbB^Xh&1IUaGz*W; zui2wHS#yr&Ld|8Gt2E<Z`cyyE?9rU8IY)D$ce$tXFVtM7xk__&v`UvTid$*U>7|x) zH5Y0w(p;{&TC?n}{8KcSX)f1XrMX(O=%dTk9IUyO<|xhh*G09ynmwA6HRot9)Lb+` z`Il?1idD;EppK*2qd8e~j^;wmWtyuri$OZSW{>75&B>ZmH0Nk8)Lf*wOmn&BD$UiJ z#b8~IW{>7%%{iJ2HJ53w(kzDP{F*(QlQrjPE{{`w_*YME-&UG~N2q0w=48z|nhQ0T zX)f1Xtyw1Mc$%X$=V&g}T&B6o%SkF9{^64A6EZ;`KfyHaMy2CC?3VReDVjY~m7c6Q zM{}X(GR;+*abb?*R83cme>LT0YmO=>O|zJ*^kB^%&B>arEaksObMQPhJ|=4x_vmHK z!S||o9?i*`bG*wr%0EYQnRhu?>4gPqUR<WRO0#%O>A{*knv*r>XfD)TrnyS<Suh<x z#N#@SW{>7%%{iJ2!L%LLfGJ;@UarzC7OC`@#VXx;LUGK~iqoD^Tv4d?BE@BERKBXU zipA?{y*-+%yvrMOyKYqTq#Vs+vs$hK)A@tgqSr^W2Tb)&)|{ic(5n}#^c>BFn#(j- zX%=rNzhKQC&B>Z`G#6?v(_E!lY}NTSdo(9&&e2?`xlD7FW>KQ^YxZbP)|{icP;;5) zD$V%!X>NyL%^uCknsYQ4YA(}Ur5XQn&H2l=E3VQkcIdd8J(`m>=V&g}T&B57GyY9m z)k|}k<|@tjS8bK9IeC{_F8hb#D$Sxy`)Mw$(0-b$G>bjjPjm7+YB@)9q2@BpRhsed z;i?{*J(`m>=XlfiDL;?qWX(C63pJN%_Pne7lQrjPF4SD6xk|Ixuj`@Nqd8e~j^;wm zWtyuri%Okevqy8X<{Zt1n#(j-X%_G4{F*(QlQrjPF4SD6xk|G*pz~|?XinCgqq$IX zndU0Z;-Jp2*`qnx%kQi79L<HA%QRPM#=q~Y?Wx(LIaza#=0eS7nyWP9U;R~n%^uCk znsYQ4YA(}Ur5UdUQ28}`G$(7$(OjsxOmmfHaa8Bm?9rU8IY)D$<}%Gyn#D1lU$aMZ zvgRDkg__GWS7{a>==_>Jnv*r>XfD)TrnyQpUV)(2U$aMZvgRDkg__GWS82xU5;%X^ zhl;B-=X|V|3pJN%uF@R*iAwiqPS%{GxlnVN<|@siO63dI?9rT~xlnWR=gKcfbD`!k z%~hI1&Z*;H4!Cv8fkxUl`uO<0Vyi}R@Q=E_Uj9iP-;y=wXg>S1_P?b4HCKS?cwGhN z<Ktz0eAMjGoD8PpWRB)S%^tj?h4Lk9&e2?`xlD7gTlsl3pRFso+`@X2<A(UF<=_Tt z*`qmGb5(%KUlpiWG*VpHL~)tsD$Sy)(t|a7G$(7$(Od|o^%B=Be^0RDWX)wEYFXT< z{DL)mG$(7$(Od|o_ORNh^-0rQp*g0l(knFMCNle%-6X{kT;HkE9DK7{4gu5l@#y6o z&4rrFG*@X3?x6B{H0Nk8)Lf>yN^@{W?XNjUbD?)RRHYYcuF@=S(fKqdYcA7Vr8&5h z_S2lBxlnVJW)Y_SG$(5=)LiCGzg77qYcAAW=GD8Y^c>A)nyWMi<H00qXOHHp9(p-K z$JLyp`9hEQh{TBT5l=;|j944-TEv?XyCM!m9FF)T;-`qZk<BA-i0l-3TV!%%cH~Qu zZ$$2mJQn$J<b}wlo>0$io^VgBXNl)E&pyv*9vSsg)Yhm|QC~*|^z78Lf6rMx3wxII zEbn=!=NCQy-Lro5P0`WO{i26NkBy!geP8sk=x?LbVz$KWi8&VYTTEcD=DmV@h4zZ; zHM&<yuk>CI_gd9!L$7yweb(z@ue!Zk_3qI-vG<(b`MsC*ey;c0-mmrE+WUC#PJJfz zxx3HfeV*>~LZ6L&w)ZLT6WMoA-?4o&`Y!2P-1pbM0sS8A_g=pb`hC?;^sm>yS^tFo zVFRKE+%@320iO)`*MP3EJ!AXD#>WmA_}IYZ1J@1wbl~-aIu1HD$Thgp;0c2t8GL4N zmm$v%**WCgkcM$#aed=b<7UP^6Zb~ksW|u08-}(YI%H_(&;>)E8@hVvOG7sfeRJsU zp?inQVGV~h85T8c_^|Y0_YPY!Z2PeCVebz6VAz+#ej4`6F#q_L@i)hJjgO2U5kEJ6 ze*6RRPsOi}A2)o#@aKlVGyKT#PlsO^UT;M3h+9WQjEEmGe#FcXb4T1i;;9kaM(iDN zd_>g<E1^L`+k}n@(FxNM?oU{luq5GF!iNdf3Fi_nBwS4JADJ+6-pJoZ+7jy~HcD)l z*flXKv0vig#EFSni7zL9mDqBWXVl<P_m6scRNc{cjJ|jDC!?K7Q<4gkK27>Hss5Ob zV^YUtjCo|tiZO*_UL3Q2%&{?_jrnTKg)u*k88~+4*u1eHj{RZmuVaJ94IDRN+`Ms5 zk6S;kVqE38v*Rv}lgW<cI>`-_TO_wi9+o^UIX8KE^19^X$@Nm&r1VXROBs_gG3EJ` zUs76*?>9bk{QU6+<DVYCa{TJ?)#JY!|J`^iwQXutYX8)<)VZniQ*%<6rLIfeo%&Q- z(1e#Kd^6$V1pkT6Cw84UVPfXQdnZ0S@z}(#CtjY|WYTq$x=)IplrSlC(&v*#PM$IO z(aA-VKbZW}<a+5hrhC#8(sR?FN&hbWV!C_E!YMnad_3jOso7H(PknysKc-eqZIaO@ zV^+q(jHfeJWxSJ7oslwa;k4b;KAzTadeQW))4!N*%;+?u_l)r~=FNC{##=K!ogru5 zFf(T6_?ekAH_zNX^QW1uGH=c7n>jRda^}*^b(!yE_P8tgt_62}cvt6HIkQ&I+BfUl zS(j!7%<eopfA)^q2WEdVJ8(|aoMCfP=FFe-(45ESte^AFoSHeU<|fb0pZnC@)pNJa zJvR5#x!=tF_gtA}&uX02DywZ)m#n^730WywGqUc@T9CCQYkAgtSx2(U?rxR6FneS6 zyV=LGf5;A<H*Vh4c{AtDpZD;*#q*ZW+dc2xyl>`3;Yc(Aw_7LS9`jVZN9!3p$0Ni+ zXN?qMTH^;`uj~ID>~H!P0q-2J7Pxy<Ik3g(JwWm?5yL_ZyKv#J4sMdx$9uT^VbPno z0z^I02uf2dHOJERB2csxjYTk)ZV*ADHIy4+wZUIo{I$bhd;Hyu_i}X;q2f+)3od(w ziGFxHR)2B3h=qbTZ;5!k9c#FV6eEO3B#0<65pT-ETLf?^qMyiyXRa6^7Q(wg3>J?e z$6^sDp1>Qjo)yEyD&&3vxz{51%gFtz7>QHpM6pGT5^svpVh1j$>=NU|J9vv#CEDiz z+UK-L$D5<3h#$~SKjOm3FJij*6&FNIIaAnWrf}eGNq%yUs4MRl!7>|9L(La$<^7_) z%o82ugBZ#m61U2S#ci@c^p}r`DRPmRDi@3Caw(n}dP2;RPm3)144xTUAs&*?iihQM zB3~AXN98MGq1-GA<eTC#yc=qf+%6W&Qt`ChDV~wL@GMaoo+Nr(ydw9CSLHrDPjm=R z6CD<t@gA*Wd0cFjC-6kkNwHm?!ZSq|#4c&bGHkgo1M-1~8?`lr_@q$>;6EF60@gI@ z0{pen9l&}GB7j!o7~tuq<a3cx<@F2v5y}nCF9F*(Cm*FKR1uV2zXhc>Ye5n6TMRda zSi-1MKV|#ZMighx_2iR#J^2r)cia+UM7@uIs>EL#{S)@BEy?E&AAG81vdvp(rEd); zpSN##!!E?J8{P%lTT{#ijGwpp!bLUz8u(M2?}2`S7lD`CP%e90Rv3@n@Ei0%wwp7y zY)jEn+fuXwMwLrNP-{J~1GQTc<Kzz4`3W(*LmS|IjEg(m40{#h2F6mxcRPeZKiPrm zaDkPj9j8EfuH#JLhR`2@`M1z^xy#kF4raiesFbiy<R8v<zfM%E#4u`&B*yWKlR4&^ zF!F4EE9E+FtwMwvS7}`#DqU{@zq0lMKXn}d_O*@z2X;FJG_5nh_`2tSBfI?z7;gLk zoYCzP5HpH;LM-j(08VRM7r4AzQ=n=G)pLCNmFiJ_?^pw$1jdXzUWYyFj$)v1tIoZX z*5c7SX^ocO`7=_B?lkIC|D-MT)18mQUKLJ0uZJ&#J+a3w;EW!mWcR3moyWMS#{t;C zHX?lmr@qvK+EAg&^=S)A{p-BPIIoKEctm4=Ar#&kNqKiRB)o&|s}<j}{<;(uU)AdB zIIl#`LAh$})OziYeB^gU)#ps)vryFfDxWivum3^0!BY-J)kJxw*FXBZkJ435RD_A1 zFOaI*Q~CS)*rRFPTj8xkLfpovwutW@=i9GTuT%X^^()msV|&xK9@?9teQPue5Mr}m z1Td%%>8cmC>qBEy=RQ=HZ+zc==-XFS@Ad8HzBN&GQ2lvew`GmIy;}9*VF?jUykmmz z2rxdOFZ8tuREHYIW+Tb&F>)~U(Ict0DXc6SNj<HE?X!&jiR2l{I5jaI{wotl0V`Q? zkK$BDRl4e#YHU$sks8fZ>XrH%O@-)}S_&*otpHw3rD!YD&cXgO|Jt?qC5=3N_rAah zGzR+C;nzmB<D_rPe``=)zrd*G*o)Kq0CO1^r4NDqEaPg%SJTO}l<__GImP%zdLlf( zO{aZ)X~z`UKc*8pr(^)Hn?ku9^=Vt&K7~qA<J<B(r?(*gY~YXSgx{t=2y{+a47_FP z4q&psGf0S^r&5o)Yt-uNFz(HyXkX7<2iuWJ_O!-zt{0-)U9_e8Fb=wl^psgNPO7mt zX*Su@8CCy`?M-XiH;W=CWPOAPY6Mo}{DdqT?dN1soO>A`VqBC(rKrBGjtif<=-H3H z);GW!*G1r0merER+xozjS<Qj#8Fys0hV47He{0Z@<<)G$V#XbeYRvZwJOKZE9@+j+ z+l3;OrW5*Zmv7Ul4VQL2i8#(FG)n)NPPW=2d$MWW4`kD}Q(I?Ai?c|*eF~LZ5lp@1 zcsAwwggrlJ{A?a6MNS8v6j4VIrT7I>3#(&+IyR|eU&kCe0!HU_gMR{J<J=6`t#a=I zE_slS?`q#y`>#53smSWMruKKWr>k+`T1PZ>6jS^DsR#KeIkRIsAzrH!22{$gjh<|e z)-Gsw3-u_{A6iVFOBuH=rtPan0Cike<AWL*e8&XkIb#X=pI<uhRw3Gk%m5y{;clRB zgbPcl7X>|WKXeb{#3%A#`$ki7);+NS`co|_raJy^c!F#-hNzmaY(}Hg15c5D?1tBF zyRs~$w|#@Q-K;mr=UVpdJBJMS`uN(u{&W8kJJRd(*+FV06?61RYQr}^7&6>nJYpC) z<9HZo;4Fg9B<OAG{-V2OLGK1MaQ;C5U675}Dfo+dKnZq0uMc#Jdn_lG=K~F#VYram z5a<;5T5jm~0rCD|oR2ug*H#@U-vIFrdz_j0i*GGFJ1hPLH256FUkrEnLx~6CP5g)_ zaUK!?`zFQ?@RE3AFySqt3G`6L!FZpn#Q8-FC_{h}=NQ+)9?CcZ=Q{?@Dp~=PalT{V ztl~!aq~dJH!25X#({RQ^XJUktQ6`;{-3)sQN;7cgL70IuCC)m+U{7P5Av!~u$v8{g z2K_F^**H(acPT}8q-NoK3Gc{ftPnk*>;W3$Yn(gb`#eAc=QL5kAMw^>d>2Ue0@jy( zkm}FaQ1*k;fU%_<0KKyu2<2AB{&Fys0YI#~j057L25^`h4vfdGZG2fpCc<X~5byh! zqk*I37~p6*4md`pAayKbvP^|Cj&YKl0DUshz}eCy;8d9oES6J&<#HPEEja`D0nWb+ z@rj%T|7x6r8Tc=WIk10{S+IX({2AwDQe0%bB=3R#Z=e*v$a`U5X8aZBYf}8iDDiYM zx-1a=(pUi70OA`;#)GgepcD?{Vc2#?r;!iE#pq`&gzjdnV>|}EE)c!WSOmKfV-sTu zl*T~xDB}s(9e{Xew(%tFu8iG`r=fHQ8ltDM92jjp3yd+I1NJgj0f!hx@QGs_h7X6K z{ee;pH`c<AXB=U?1SNrSw6PBQD8_NdtI$)8*Px7NoMdc(GMRCTu?c!Q<5Xh{^bE%7 z#v9P50nzu264)~sGmULf?gAQOmazl&Y#{m}-XJZ-T%aMcjDNts8)%4Zqa5};pds!t zcEg?zG{n8e9@zH*4g9yyJFs(rhR8Mc!G3@-&)5%T0b{=L9_&YrgTRHxA@~#k4e^+9 z1o*gd47dbukT%3p<0Mj_Ha>*?6yr0-N3b^-p8z)+)xa&r8Tb?f@m6x<GuT^!hA1(< z0KRFQ1LCc9z}?2zNUdOe+xQohJ&f<*O~?4M2N2&bFun)AZ~Oo}Wc&m?Y+M8$F)jg* z8o$8v7|;;Mjo*ML3@PzNBf|uKXxM<K4F~We!v*}<@B>yEb%E7JeJSxYe*@T`GJa+R z!2aB51gtTd0HxUs=rCIVo#u7$cLC8e&6cqJfQG1Rwt`*XYz?~}qrZ70lm<YIoMu~K zpxGYS$h;ZY*z5>wV%`F5YKFnHx!D<ZGsYnEHrUsjw*y<6-I3bLyc2dXP~v|IdceMc zF~p37-NuXpwl$-HH=DhH9nC(#?syZcf&UE{0PJB7MBWG>zU^QR271gmU{7-x^k^XV z2y-~Fmze<UV<rOonxm204`_(~<`~!m&2g|}83&muum_u|z#--YV4OJ#7;mNnhnrL3 zKLUvH%A5uqY0iL>WM;x14a9h5&VoG#C`Gb42lhC|Y%>eWJfI;8&1_(ic@OXf^Iqty zfd>97>VDX38DBDUp}Yu`Vx74F_RByiUNs+t{R&Wu*UX1uuLnx8!OVyKI^#xjA(Tx( zLu@u5gS`cazG^Olz0F(#`%T8}<`YnM01Z)UJ_&m#<3G%&q3mKjXD)~SPoN>bGM|Ng z9%zU=tmk0g$=JhM1tpv@!YYCu3B(q$R>O`0V!K#tVMhb8U96X2_hRg0t%K4Vi0xv% z3cD|3f9o|U0~iNd8=%KB4ze~u9}L7cvbMk;YP|tF4k$&uRRVh$<Nek)*biAdfR9)^ zf%(=y;Pa|g4*M0xjn;11Z(4hR+pKqhrPe<9>;xL(AJ%?gne`s9+&T!XunqzDSVw?w zTgQO=tmD8+>m=|!>qFoH>m%Sn>l5JnRyFXLbp|nyTc5%HfboR&1(Z*%b5N=ozqG!B zect*Sc**(~@HguMe1z=+Y-#%*wgJR`YWo3L*Y*>zq3t5@Cfg-oC)+Q;FxzkNjIc?} zo_a7w+Dt<V4-jo@vjK<M9KcaF7jU%A513@D3w*#<AGpBQ0GMYBK&}UYhFENC1p8TA z6WA*lSK_@MQas1F#?}J*YR0v;>!82L__D1f^p_ad*;+w=1!#y@ZLMLiXMEjuBb3*G zQf#!fg}nhN#b#T3*qa!)*lvbW48+)L>j--*<D0fypp-CfvxPz54m89LTW8p%jJs^N zLD|Xp58Lg~%YfLLw(hXsVyv*;31v6q9$OFSZ!_+-MM8fEh;3(!g8czdij%fz*e4iI z*?K|wkntm1ALyqUKeqLQ{t08XZ2<Ht#xL=Ii1-d05c4bBVBn9oIN)X5FyJq?;Yj@r z|DR}xUx89cdm?NBl=#2G(Xj7i>|q}RC4$jo9|t{>G0L6-y(bW}0(&a#7{>ng2~hBk zYACVxNw5bn4z#C38N@iuJ{9^<Am$17X|Usgm?79_z#hRk(w+$=5s3a{p9Om~;~4uK zC`pWC?OD*r0Wq_+XTwfmOts$wWjqjbTKm1Q(-<e(?}svpG2NaEeKJsrsrCi1rvUN& zBKw1|Gk{Xeus;lYI^#@xK9o$xS@wm{?_!*7e+>E@AZEGtMX<9Nv+YZu+|4-8{si=U zfavq~Ct>F?K4gCy%7cs#+m}Os1c=eY{w(ZA84K*sL0Jft;&J;b*pD$TvKK*F%(&FP z8u}8(C+usXFJpYl{u1;j8K1VVgZ>N<U#hpi3VQ|PO8aY2o&`$rynO@g=YUca+Bd;o z#aLwD0_6oD_CEU?u-7oYXfJ`XmhmO~Hs~({4YAI?1NJM7>+L(Cyvq2R{U6X@2V(ED zm&4x3xY@oN$|lAw_C3&x8MoTsf&K<#iG3gRHyO9v_e0;txWoP)^im-9KKnt~yMP!` z?T28O0kIF-j{qy}$Dn*-KMwn2Am-}!ld!88Kec}d<p=vmP-=jfz1u&5{UhT=do`4Q zGhVizfqn@n#jp0yVE+Qd`04lp_HXttU>lBeuq6;PcE?w+Ek=jqYuMc!{{oJ1TtI39 z5XX1N_rOHQ4^Tz{F<Lr)f}I4!QQmP8_E^Sb$0aD^fKrTi`~o|LG1c)Klr*3rCOD)i z#Y7;^;~XaJ$&6DR7@9>o5OZmV1NKzLX$}{Z>5MZSe$Zzy-tDLhJKs?s_^6`+aFHVb zxZKeQ_^zV~(BW(b?Br|#40B!w?CNX@?BQ$$jBvIF_Ho__?B{HYQu+gNgmtzD4s_lO z9OCQ<jCbAwOmv0;lboG_W1P3ae=N`tsm|MhY0mDzsm?or8O|QS>CQ;t3}+NPX9BTT zI-`NJoV|dvoqd2=&VInVodbZ`&Vj&r&cVO}XB_Y`=P=+h=Ws-L5{RR%GXeH{&P3Ri zjE9}0VIOgh0e;{d2TXLO0Fzv)z!cX6V47<ZFx{07%y3NwX1S&T=ecG8b6uIhJl8DX zqpmr?$6Q&!rLJt?ldgM!Z@caV?sMG_{MMBVyx>}3T4E5cNVvsDT!rux+kp+l0}@}k z5;^7zMthNFI$%#WUBL0C4fue$26~?PBCx>pgYvjp7grEYne{~!j=wJ$QDUrZ4KUgE zqA>~o#T<pR{a9B3aFC0>2Q<Xh1US^y3>fcf0UY7F4mi@)5;)4$3Yg?-4IJyb5t!_1 z3mori51i<_893S15je$l3oyeK2AuBd44mn@4S1L9cHnG$=Vp?a>$($ox2p$mo+}dA z-_-z^<BA48;OYg;bM*l}<mv}}#5Dl;sB0jwz%>~7xGN60*fk8e)HNKq%#{Fq%0=Hf zdd4*xxWYx>J6h=)2YlX@0xWc;;_Ai$mksw*8@eX|1KpE=&E4t1Aoo<@_3mlFVD}8* z4em@}h<g^WjhnuP)XAL%?Bb@cB6W4&1MKF$7kG#Jeqgve7Z~AQ0Q9&Y1om`442*H- z1ADs{0{glj!}XNM-8TXkyW8S!?q>H3#tc#HUIX0fe$iMa8rNHdFDf;yw*=e%QU5!E zdHy#8AM(Ei_=tZP@Bw@wdb=p_?+kq0|2E)a|L(x0{yl)p{3C%+`9}et@s9?s@b863 zbPpicPVv6~6Trj%PXdqnKMnlAe>w1k|Fgi;{`BpokNsBxtNe?ApCZmqaTalQiq8?} zkXYZ)0eroo4Y;vkHE?soO*jKCZny=wwV?^+&4zx)VevNX!(taa4~sH*9u{xG^RTFZ z=V4KbIETevcpedlp&t<k;CV#656>f_5~)YTQFtB^A0W;VaRQ#l#g`4!ZO6qw;dxw~ zhv#weO~a{BzJ=#;@f|#miyC;Iz@zBZcmVNNc%BfKk?Vvo0;U43fGtS12bj>E0e(0~ zZ_el+V8geZ8Y0(85g4!udSiH=6iwlIQq&7@;OkCppqvsR0pHtBi5uW~O0+}jDRC1b zTo8{S!UgeAz-re8@hDO+h=PE%updXR3t}-mFNme^bYq`ximP<(@Ex#j!h<IW;>1W? ztDB5#YuVx<@dUnr^&&=q|A)Fa0h9AO%S4aVc4A|UtoRzolM8kh@Q}o9Yx9z{6}MU~ zskF7M?zSW^X;*hux5}!nYE@NBY6r)1GCW{-LSQn1;b9oyxyj|i+`s@c5QcRw55qhe zVi*GnBmr(9K-e-Y!!iS0?)!erf6l*Dt6O$pke=%QKl?e~`L^?&@0|a`;itk!!Y`no zzY#tQtJ&5qH*I<Kman_z_Fd2I`Ot&^{=px6@N*BoXYYU7`<cDhyzTeihU*m`y6K@O zA6j~-^U&%;U-{6xANtyd{?CX0pND?^q5t{N7mwdQ{!hn$VEq3Zf9<{x?fZB8KC$o9 z`~Ga->n6Tt;+rS_xBUnAzp($>$=#FtCV%X}&m4H+z)c6g>)@{+yzkI~LybcpKlBB) z+iI_?y`{FVcBD2_tJk`<XKKG$d&A+sIQ+MVZ$9$IBh4e{j=bgQ!qE>Nz3JE+kNxbi zzdiQ#$A9?vi^tzO)tS0D_3YHoO#R-}Url}Ci7!9#;)ze3_{|e{K79Z5(dj3r2h(3O z{WUZ1o%zw3Kc2Z`_RiVa*>khsF#EpQAD{h|+0V^}N51sb$x}ad>XoN&KmF~ef9mw- zPTziJ_RJG!{?(c9J@boaK6&Q1&iwV6>mL2$NAG*|z@w)hoqx3b=!HlB<D-A}=*VMV z{MhRs+ws^3ANzNY{lsJc>9H3c``BZD^Vsgk_dkB@@&Ekz=N|uy$A9377oK?C+kffp zch8N_&CI=b?gMi_HuvJ({ZBsh<l!fmp1k<vH$3^mlYj8!e}D3SukWnCrGBA)x&C$a zZ?2!3e{%jU3(E`J8n14=p>cm>Ut_BAM5Eof+<3lmbF<!DZhlqs8=K$O{OQG?Tl~!8 zwM%y{y?5zbm%eN1`<DL0(%&uJc6QI%LuXH%T{`=&v)^*|ht9rm_P?EdMe9pi_qV26 zi>+^HeWdjzPks4Q-~H4VoV)Yf!{_GD{ZRWS+P5r^FMrGOzghmB<<Bioc8+)6-ua`> zA9wb4pX$D=`!BoS(fxt$f9Ss0{Y3Xqy8om5s+E7T^5&JXmE$X?SC&@3YUMpE?_c@$ zmG54;{`~gyx1E2}`TNg5bpG`Dcb@<H^WS&=C(i%X`CEHm*1Nkm(R;Y}Xzz($zxR&b zzwCW`@4I?G(EEwr&-8BS-`u~ge|!JV{#?J?zu5ob{*U)R+yBzRn+InGX9wNE#ldrf z|9kL*gP$FIeDJ4(zZ+b)`li);SI?}rR|l&fUOj!`y%+xNg`d6f?=RkYY0ss_OW%6w zLzi}}{o&ddJ^iImfBDlVpZ=bwf8gmKefpZq{O%^dW*=aa3gJh_-j2VF@qf_Buy<38 zdnb0+i*Yc`m*PG#R*tK`tsM8+in80kf%*84J^MIV$+hU&E8%f;9e%F@_0n5tAEhho z>}Mfe+TiY#9q$hBg69u8`1`^)h4;hj=R3pyf!_zi2SCvu2;YU@|ApU&!w<vb=f}g( z!`J7(LSny&-!J0#OYjK#W&A#d-^bx&^eg!ND(e3P>i%Tt!z*Yl{91T6{5pQWf#0X$ zYxJ9F@3+v_XVA`X<M%uG{Vsl=#qam<`+fZW0KY%P?~m~N9De@|zdsHi0zdjt_){Fu z`7`|fJAQwT-(TSOm-zh^`uIQa`=9vzHGY4C-`|Fxgcs7!hQGrvY=Ny5zmY8;4cFjz z?Uondne+lYlU@+m?G8%<+r#q2TF}D3WY7DzX!<^T?ziUwdp>E;MLanVi(9_vRjl~z zmM?x4!)Ld=M#Akauaj_l%U4KvVC9~+d{0}xue9)2B8-(U?n!zO(t^*UDEDgIn8g0P z8rqij>z~{6o9y|$c(UE^-SXUFJmB@D;eTu4|7`g_V$Ywm=L`1y1$(||&tJ0V$L#ql z_WXoBKV{Efx94Z<`8)RftUZ6<o_}c1&)M@&?fDn-d^PqTbX<R9&ug|WeT?I|X6ps` z`@{Qc>m%R9=Pz#6e4n)Eui3LdqVcQt+;feF-*%lo$L%>`&q;g!h&}&{JwIyCU%pP^ z_;Gvwsy#o6boT3$7XCF0|C)t=!^(f!;y-Qizh&XyvhZ(P__r<mdsglb?D<Fb{1eOf zCzkKl>vcRgZqw&Bdv2HKHQ_rg{84-UygmP`J%7=jziiKs+w)iL`AK_z+Md5{&)>7> zAKCLy?D^;R+<uex=Vp7}V$WOcd7C|7W6!tQbKIVj_B?3MBlbLI&xh?fEzehnzqaS! z+Vk)1xn(>1@#-*Q&uh1T`h8mvzIOZXzK`**lxGOnOL_=5+H?E%JO2gKZ?^E;>^W}F zNqZi&XU(2R?0IbavG)@%j%}ZMKk;<R;-~F7YtK{mJY&zt?D>Q}=j>Ux=Yl<(_I!su zpRwn&+m$Y!v+%p^`BnD(YD<6Ko?mD2KX1=lU#0VQr#<ho=iT<)VbAAYrR}`W!ta;o ztHTHJ{EltEJpLWqer#WN>xU=KZ~e%`^IOOFceg&g|2=sB%ynNr`IB28oE(fiIQil0 z9-Q2L&0ikeea&Cv_jmY>9NK-&_4sYW?`HgN#qTxv-HzYu@%zZc?rZME^G*2OedvX4 zyAJ&ven&?B>d<2&yKDQm@2<Ub`=@Hp;Qd+ro*DUqBi*fc9C>!+BNN{~^7Ti*6VDHh zym<66Jf9i4{aAPF1IM1<`XBN9#beKod<yS>iTCS|cenmDeg~(X-}<Rz=dbzHvG?Hj zUi|iN|J1R)+h6<e_h0kcho3?I{pkN*{Emzqnc2Vn{LFFuejnj)ANk14AIdY#e)hWS zXFqk_HvGPP@)P)d=DJtUe&)J9e(#w5f3N$<%pYI(L$kk$xX)hqsoIf|>mE5W@=x%4 z^CLfg?Wbx#iQjLc%-3K{pSteRQ;&_zpE@$Kgx}h!=eJ%z+uizer@CAJ>h!yi{`B^b zOx*p7kDhsU<fCW0TmQ?W-B$qi*L?KM{_P(<6SjZvW6z9y?_<y6_sq!N$DhUTrfqM> zukrZt?H_*p2Ve2x<Ijw|i1-&De-ZHf1^j;Z6$jq_?8t|a=hEA6+V;-3KfiU?p=U>S z9s0>@KYHf-uNkTT<hA?iZ+_LO`VYQhU;P!^Uvub#BcqMWuUc)KN4^h^tToSXJwJ2P zw(U#rLHIki?OGar)q|5?xb4BoFT(GpZS&~=-DiLB6?dQQZv9y6{MO%S9p66sRCnuZ zm(Oo~<8pWFk>zJb?mqkM$QzeGbp87~zl1U$!|xOL{R(~$-Spt(0sLOr&fl&>NAZ3V zzvs8kpL!NBdVcGVoPT!YN6tUL^-s@ZK6`(Iw7<D&{?u0Fe-(b`uX(ZeitTqFdd2ov z^q&VVJ_}rYW~9~sTBJRLG499ni_zc1cpgXnkKp-s{N}gMpPI+>EAe|Le(%EXYw+8@ zee~jcui5+f^IP{mzU>R=E_JuQ>(Xn!aPQ;yppJk3sy8mb2l3BuZS{|D-~0GK+y3gO zpFw-?z2?Em_n_^6fjlG7v97`IT6jv_8d~5wE%-@%0R9mlfcHWRo(dlb6Zq{9|7y!2 zJP+e{6u)EmP2u-2e$)8P;5UmrkKlO*bv=sTWB9G1j;HZ^2Yz3P-!u5V6TfHiyNutv zv0MIbtmOU~-v4#@T9m#GI^b>40dEW6gShWOn*19()@`h|n|s6Y=EXs`)9)@0Mi2M9 zozX_S9rod6U#nB^t<9{hG)q~Soa8yxYYm#cz|tq1^#)!Gb%yCdebAH=mVq_st0ql! zJA-<w)9mr3d1)YdXS(N_+RoJcQ_Tg99Pjn&Ym)p(y}hcHP7ivm&XU!?)Vw5bCwkqL zW^bT*Wf<(B^_A@>uUdbt5o$W>;s8d`tqZ8E+3U+NvtZEtF}%M%s82Ts`G}eNe7hN= zCRY1{?(#&hSs%2zoh<&z9Ra1AGp*&AcVE5V47JJ5pfy-K(p)>%T%HGb3*oGWFnMXE z-f47oI58R}1z0EQEdp3aB0%zi$Jmoi05Ap>w3D6H<rs&z{-C$IFzEIUp}96;PuM2$ z;{!~|{3<4W|5~TM+*-hFTw22j3z#uFJ7~3AgH{u8Jy9Q=Eu|lAb<T&{VRoR|BN$Of zjg29pS?erz!(l~}!-@|2NE;1@Pu4Gnlg;z1tzNU?9Ub)QK&WDGrklN1z1@0RCPuPa zO&FS%VABV>z2*7<$Phuo=vr1<XFC?)AaXUF=(UzH>I<A`%~sP1>UA%)NI=$XK60|x z>-HuuG&_Uw-Vy<37{0&RU+A?~Fdd<GvbosoH9HH<lg)m&jhPPnn~U|;_Mn__qSx#< zdl!(?iu8-KsU#WalA{#*i&0j)bhKDp@hs+LaYeX%oWrfz1LX{WO-f{_v(>;%be4>1 zOfQ^mF4w0QK-V_wljm2FX|OiYU0$j8FjyrJuTZDasKLlB>twCpEs<L|471C$0J9<u z-S^mB3*e+6($mZB<9xB_sa|~r{72&VcNc)ZodMJQJx!l%!-}KH_L`>dZ!Wa!Jsk@x zh(XVFT8EegtFjQ&gu}*M)~hUH28=fg0Ho|w76!5{H2ZzbM$M=nyemu1CNzsF48)+4 zrx-S9bIV7@q#d!dT7?vAOPy}7nUB*hXW~a$`sAgB<_cM>6w?OsaalJey6rY3i|4g? zDVLk#d~r|0^iEzHG&_Bi2hL_7Htfs@tAwV^>MS(mncHEV%Z$3gyPJ))o0^{Ia4Hm) z6B<A@;G)?fYCWrz>PlWZcA>Pwpe2sD-cIsJc;9NPO|C@(*IGykX{U1uNxqCr#iG{b z*-oRm2p-?i3TuswaGfu7JCPR_;v_GLk5fWUpS5HBsuVv_Uv9Q1AZ;B|#}UylW%GJG z0kh)_){#O86>0$iLO8P`W)tqsa#U|JPU;m&%>I_3BGj&2w0BAJcp!o9v6j%K^1cjA z$5-3!JX_B2@o2ZZlI4q#Pv)gOqv%n8Q11+sH|wMvY(itJQ|@q}CH%?g**LJvI%&}) zZL?PcM0%ZiTU4E_i$XH}GT{)BN<|Ij9W)}?AOdwfrQA*vxvEh15>8VxPL#=P0&2&a zgL(s69Y<4OK8Qhw$v^;ti8=_Rk>xlDQP%81TR94yZeMq`)2Ob<DgXzO$wEI=1DNUv z!esRY!sp)Ilbr>SR;D0Gw^^}*7|k81x7+jeg>wNNP%Ip8)K}0I*D0uR^{Ka_8t2x> zyEA8d-HTZf(=#m^QdzU-qoHg<gCOsFq}6W`1jdxCR+-RzRInuU_-wb~NM?}{`vsj6 z&KhB~zu9gs0iXv!)7>6uPqoY888Xy!c1!*7Xok2+BiTF+uS~l=0SG_2GXP?cS=05! z<}|v}WQ<XJMq2w?4d|Ro)9pAsWCeOrJJPveIg22ps3|;BrXPvjV1-e*Dx!6Gq1Z}@ zuU=?p5@#AUx{mHJp!M)g5D_h8?|lTKW~3QODyn%4J?S#eG-ECZl#KSRE<(A)a9zyi zdwaCi9|ZpK9_|RounN#jvOq%j5ZCH2G~4ZZr`cWYYhs)v4Qq4yJ}waF0wF}Xph?U9 zg>J9inh(=!{XuhCh8Pq_LT!JmztZh<kzvVL_Ibbiqk=Q71+K=DwTRY}W=u0+86OF_ zHqq{On)&K`SyP6|=!K$mB<hSM)W{A8z6df{OMQ@YRDg@`0G;*k<SLes%gs^eovKaN zDpoR4re;H1UO~S*0~P(~jV!CvMMsfM6L&HlEUPAhQUR{@MG&J@jkv&PK9&U15M1H< zyaDvmfc}kneY7#AKRUhAT#z*#)_%3)-9c*+!qvMnakk!BLV{pCTOks-3<h0*9F;3$ z;f$b_cMy2#nl9*BK81=lmdi}SK%J>VhiZ>udDdE}_Xp11LT;Lr_d2VKctH-Y9NBV! zPv^se<D*=Cjz(tPr<I_<xM!f(pc7$+{AWgn9A*x*dZ5mk>E=SW1K35tgc&0NFb^pZ zgIYAn8<R4+K&fznwwt**GoOS5s~v$lnOZ%}uC^NCSYuC^?KgW+*r)~i)KcLG)j|jg zlhF~@rmP|p?Q<8^A~lC~(^7Na8U(QNp}FPyCCnPEIL<wf0H4BAb1;`iWDLANECKAM zSzivLBKZ%rSNmrJs2jE#=|RF5<<(W3K%LC!*SMw_uH=1ouy{Ya+J`35URZ5I%1yVP zZstR$T7$Dko1G<CO2(>sy!YJP+&)Nu0Ovrf*>2><nGI`hAeOZ`nFSGBLEnW0=FYEn z2Tg)0%q^_;U`-e(clQu;Q9t5T5Ofo{OoQ%<uyl~|95hB~B9e#DsdZLX2Z2BpU}=!4 z4`(ehF@~5X5R{HFqzIP1W%2v5;yW8oa3W5i11FlzbFlf;@$nt}8Sg7#u`X^c*V`87 zA&2AC;v!o2M6<+M<n+1L3aE0RGmB2pGSQp_?jTa2Lv5!q)iGFvT0gV#Xqj3c*?LyN zLDU2wg8d30G>0+DL=26pM1m1bBvUhtMYxq_IyVb3Ux?6*kW?rt%%oe8(90|BChT?2 zuS_axs;n=QS4H*)K;0i`UBCy|u)M-7LYF?_K�Lq&#&7b2875fq>6H_x9WCnn0+ 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 Cbs+Zu diff --git a/cb-tools/SuperWebSocket/SuperSocket.Common.dll b/cb-tools/SuperWebSocket/SuperSocket.Common.dll deleted file mode 100644 index cb07bd2690a91d73aacd993d1fed128c9c009085..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29696 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 diff --git a/cb-tools/SuperWebSocket/SuperSocket.Common.pdb b/cb-tools/SuperWebSocket/SuperSocket.Common.pdb deleted file mode 100644 index 33b1455a58d509cecc6f786207c63260ff5f76a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 89600 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 diff --git a/cb-tools/SuperWebSocket/SuperSocket.Common.xml b/cb-tools/SuperWebSocket/SuperSocket.Common.xml deleted file mode 100644 index 884adfb8..00000000 --- a/cb-tools/SuperWebSocket/SuperSocket.Common.xml +++ /dev/null @@ -1,1224 +0,0 @@ -<?xml version="1.0"?> -<doc> - <assembly> - <name>SuperSocket.Common</name> - </assembly> - <members> - <member name="P:SuperSocket.Common.ArraySegmentEx`1.Array"> - <summary> - Gets the array. - </summary> - </member> - <member name="P:SuperSocket.Common.ArraySegmentEx`1.Count"> - <summary> - Gets the count. - </summary> - </member> - <member name="P:SuperSocket.Common.ArraySegmentEx`1.Offset"> - <summary> - Gets the offset. - </summary> - </member> - <member name="T:SuperSocket.Common.ArraySegmentList`1"> - <summary> - ArraySegmentList - </summary> - <typeparam name="T"></typeparam> - </member> - <member name="M:SuperSocket.Common.ArraySegmentList`1.#ctor"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.Common.ArraySegmentList`1"/> class. - </summary> - </member> - <member name="M:SuperSocket.Common.ArraySegmentList`1.IndexOf(`0)"> - <summary> - Determines the index of a specific item in the <see cref="T:System.Collections.Generic.IList`1"/>. - </summary> - <param name="item">The object to locate in the <see cref="T:System.Collections.Generic.IList`1"/>.</param> - <returns> - The index of <paramref name="item"/> if found in the list; otherwise, -1. - </returns> - </member> - <member name="M:SuperSocket.Common.ArraySegmentList`1.Insert(System.Int32,`0)"> - <summary> - NotSupported - </summary> - </member> - <member name="M:SuperSocket.Common.ArraySegmentList`1.RemoveAt(System.Int32)"> - <summary> - NotSupported - </summary> - </member> - <member name="M:SuperSocket.Common.ArraySegmentList`1.Add(`0)"> - <summary> - NotSupported - </summary> - </member> - <member name="M:SuperSocket.Common.ArraySegmentList`1.Clear"> - <summary> - NotSupported - </summary> - </member> - <member name="M:SuperSocket.Common.ArraySegmentList`1.Contains(`0)"> - <summary> - NotSupported - </summary> - </member> - <member name="M:SuperSocket.Common.ArraySegmentList`1.CopyTo(`0[],System.Int32)"> - <summary> - Copies to. - </summary> - <param name="array">The array.</param> - <param name="arrayIndex">Index of the array.</param> - </member> - <member name="M:SuperSocket.Common.ArraySegmentList`1.Remove(`0)"> - <summary> - NotSupported - </summary> - </member> - <member name="M:SuperSocket.Common.ArraySegmentList`1.GetEnumerator"> - <summary> - NotSupported - </summary> - </member> - <member name="M:SuperSocket.Common.ArraySegmentList`1.System#Collections#IEnumerable#GetEnumerator"> - <summary> - NotSupported - </summary> - </member> - <member name="M:SuperSocket.Common.ArraySegmentList`1.RemoveSegmentAt(System.Int32)"> - <summary> - Removes the segment at. - </summary> - <param name="index">The index.</param> - </member> - <member name="M:SuperSocket.Common.ArraySegmentList`1.AddSegment(`0[],System.Int32,System.Int32)"> - <summary> - Adds the segment to the list. - </summary> - <param name="array">The array.</param> - <param name="offset">The offset.</param> - <param name="length">The length.</param> - </member> - <member name="M:SuperSocket.Common.ArraySegmentList`1.AddSegment(`0[],System.Int32,System.Int32,System.Boolean)"> - <summary> - Adds the segment to the list. - </summary> - <param name="array">The array.</param> - <param name="offset">The offset.</param> - <param name="length">The length.</param> - <param name="toBeCopied">if set to <c>true</c> [to be copied].</param> - </member> - <member name="M:SuperSocket.Common.ArraySegmentList`1.ClearSegements"> - <summary> - Clears all the segements. - </summary> - </member> - <member name="M:SuperSocket.Common.ArraySegmentList`1.ToArrayData"> - <summary> - Read all data in this list to the array data. - </summary> - <returns></returns> - </member> - <member name="M:SuperSocket.Common.ArraySegmentList`1.ToArrayData(System.Int32,System.Int32)"> - <summary> - Read the data in specific range to the array data. - </summary> - <param name="startIndex">The start index.</param> - <param name="length">The length.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Common.ArraySegmentList`1.TrimEnd(System.Int32)"> - <summary> - Trims the end. - </summary> - <param name="trimSize">Size of the trim.</param> - </member> - <member name="M:SuperSocket.Common.ArraySegmentList`1.SearchLastSegment(SuperSocket.Common.SearchMarkState{`0})"> - <summary> - Searches the last segment. - </summary> - <param name="state">The state.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Common.ArraySegmentList`1.CopyTo(`0[])"> - <summary> - Copies to. - </summary> - <param name="to">To.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Common.ArraySegmentList`1.CopyTo(`0[],System.Int32,System.Int32,System.Int32)"> - <summary> - Copies to. - </summary> - <param name="to">To.</param> - <param name="srcIndex">Index of the SRC.</param> - <param name="toIndex">To index.</param> - <param name="length">The length.</param> - <returns></returns> - </member> - <member name="P:SuperSocket.Common.ArraySegmentList`1.Item(System.Int32)"> - <summary> - Gets or sets the element at the specified index. - </summary> - <returns> - The element at the specified index. - </returns> - - <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="index"/> is not a valid index in the <see cref="T:System.Collections.Generic.IList`1"/>. - </exception> - - <exception cref="T:System.NotSupportedException"> - The property is set and the <see cref="T:System.Collections.Generic.IList`1"/> is read-only. - </exception> - </member> - <member name="P:SuperSocket.Common.ArraySegmentList`1.Count"> - <summary> - Gets the number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1"/>. - </summary> - <returns> - The number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1"/>. - </returns> - </member> - <member name="P:SuperSocket.Common.ArraySegmentList`1.IsReadOnly"> - <summary> - Gets a value indicating whether the <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only. - </summary> - <returns>true if the <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only; otherwise, false. - </returns> - </member> - <member name="P:SuperSocket.Common.ArraySegmentList`1.SegmentCount"> - <summary> - Gets the segment count. - </summary> - </member> - <member name="T:SuperSocket.Common.ArraySegmentList"> - <summary> - ArraySegmentList - </summary> - </member> - <member name="M:SuperSocket.Common.ArraySegmentList.Decode(System.Text.Encoding)"> - <summary> - Decodes bytes to string by the specified encoding. - </summary> - <param name="encoding">The encoding.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Common.ArraySegmentList.Decode(System.Text.Encoding,System.Int32,System.Int32)"> - <summary> - Decodes bytes to string by the specified encoding. - </summary> - <param name="encoding">The encoding.</param> - <param name="offset">The offset.</param> - <param name="length">The length.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Common.ArraySegmentList.DecodeMask(System.Byte[],System.Int32,System.Int32)"> - <summary> - Decodes data by the mask. - </summary> - <param name="mask">The mask.</param> - <param name="offset">The offset.</param> - <param name="length">The length.</param> - </member> - <member name="T:SuperSocket.Common.AssemblyUtil"> - <summary> - Assembly Util Class - </summary> - </member> - <member name="M:SuperSocket.Common.AssemblyUtil.CreateInstance``1(System.String)"> - <summary> - Creates the instance from type name. - </summary> - <typeparam name="T"></typeparam> - <param name="type">The type.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Common.AssemblyUtil.CreateInstance``1(System.String,System.Object[])"> - <summary> - Creates the instance from type name and parameters. - </summary> - <typeparam name="T"></typeparam> - <param name="type">The type.</param> - <param name="parameters">The parameters.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Common.AssemblyUtil.GetImplementTypes``1(System.Reflection.Assembly)"> - <summary> - Gets the implement types from assembly. - </summary> - <typeparam name="TBaseType">The type of the base type.</typeparam> - <param name="assembly">The assembly.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Common.AssemblyUtil.GetImplementedObjectsByInterface``1(System.Reflection.Assembly)"> - <summary> - Gets the implemented objects by interface. - </summary> - <typeparam name="TBaseInterface">The type of the base interface.</typeparam> - <param name="assembly">The assembly.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Common.AssemblyUtil.GetImplementedObjectsByInterface``1(System.Reflection.Assembly,System.Type)"> - <summary> - Gets the implemented objects by interface. - </summary> - <typeparam name="TBaseInterface">The type of the base interface.</typeparam> - <param name="assembly">The assembly.</param> - <param name="targetType">Type of the target.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Common.AssemblyUtil.BinaryClone``1(``0)"> - <summary> - Clone object in binary format. - </summary> - <typeparam name="T"></typeparam> - <param name="target">The target.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Common.AssemblyUtil.CopyPropertiesTo``1(``0,``0)"> - <summary> - Copies the properties of one object to another object. - </summary> - <param name="source">The source.</param> - <param name="target">The target.</param> - </member> - <member name="M:SuperSocket.Common.AssemblyUtil.GetAssembliesFromString(System.String)"> - <summary> - Gets the assemblies from string. - </summary> - <param name="assemblyDef">The assembly def.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Common.AssemblyUtil.GetAssembliesFromStrings(System.String[])"> - <summary> - Gets the assemblies from strings. - </summary> - <param name="assemblies">The assemblies.</param> - <returns></returns> - </member> - <member name="T:SuperSocket.Common.BinaryUtil"> - <summary> - Binary util class - </summary> - </member> - <member name="M:SuperSocket.Common.BinaryUtil.IndexOf``1(System.Collections.Generic.IList{``0},``0,System.Int32,System.Int32)"> - <summary> - Search target from source. - </summary> - <typeparam name="T"></typeparam> - <param name="source">The source.</param> - <param name="target">The target.</param> - <param name="pos">The pos.</param> - <param name="length">The length.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Common.BinaryUtil.SearchMark``1(System.Collections.Generic.IList{``0},``0[])"> - <summary> - Searches the mark from source. - </summary> - <typeparam name="T"></typeparam> - <param name="source">The source.</param> - <param name="mark">The mark.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Common.BinaryUtil.SearchMark``1(System.Collections.Generic.IList{``0},System.Int32,System.Int32,``0[])"> - <summary> - Searches the mark from source. - </summary> - <typeparam name="T"></typeparam> - <param name="source">The source.</param> - <param name="offset">The offset.</param> - <param name="length">The length.</param> - <param name="mark">The mark.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Common.BinaryUtil.SearchMark``1(System.Collections.Generic.IList{``0},System.Int32,System.Int32,``0[],System.Int32)"> - <summary> - Searches the mark from source. - </summary> - <typeparam name="T"></typeparam> - <param name="source">The source.</param> - <param name="offset">The offset.</param> - <param name="length">The length.</param> - <param name="mark">The mark.</param> - <param name="matched">The matched.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Common.BinaryUtil.SearchMark``1(System.Collections.Generic.IList{``0},System.Int32,System.Int32,SuperSocket.Common.SearchMarkState{``0})"> - <summary> - Searches the mark from source. - </summary> - <typeparam name="T"></typeparam> - <param name="source">The source.</param> - <param name="offset">The offset.</param> - <param name="length">The length.</param> - <param name="searchState">State of the search.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Common.BinaryUtil.StartsWith``1(System.Collections.Generic.IList{``0},``0[])"> - <summary> - Startses the with. - </summary> - <typeparam name="T"></typeparam> - <param name="source">The source.</param> - <param name="mark">The mark.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Common.BinaryUtil.StartsWith``1(System.Collections.Generic.IList{``0},System.Int32,System.Int32,``0[])"> - <summary> - Startses the with. - </summary> - <typeparam name="T"></typeparam> - <param name="source">The source.</param> - <param name="offset">The offset.</param> - <param name="length">The length.</param> - <param name="mark">The mark.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Common.BinaryUtil.EndsWith``1(System.Collections.Generic.IList{``0},``0[])"> - <summary> - Endses the with. - </summary> - <typeparam name="T"></typeparam> - <param name="source">The source.</param> - <param name="mark">The mark.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Common.BinaryUtil.EndsWith``1(System.Collections.Generic.IList{``0},System.Int32,System.Int32,``0[])"> - <summary> - Endses the with. - </summary> - <typeparam name="T"></typeparam> - <param name="source">The source.</param> - <param name="offset">The offset.</param> - <param name="length">The length.</param> - <param name="mark">The mark.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Common.BinaryUtil.CloneRange``1(System.Collections.Generic.IList{``0},System.Int32,System.Int32)"> - <summary> - Clones the elements in the specific range. - </summary> - <typeparam name="T"></typeparam> - <param name="source">The source.</param> - <param name="offset">The offset.</param> - <param name="length">The length.</param> - <returns></returns> - </member> - <member name="T:SuperSocket.Common.BufferManager"> - <summary> - This class creates a single large buffer which can be divided up and assigned to SocketAsyncEventArgs objects for use - with each socket I/O operation. This enables bufffers to be easily reused and gaurds against fragmenting heap memory. - - The operations exposed on the BufferManager class are not thread safe. - </summary> - </member> - <member name="M:SuperSocket.Common.BufferManager.#ctor(System.Int32,System.Int32)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.Common.BufferManager"/> class. - </summary> - <param name="totalBytes">The total bytes.</param> - <param name="bufferSize">Size of the buffer.</param> - </member> - <member name="M:SuperSocket.Common.BufferManager.InitBuffer"> - <summary> - Allocates buffer space used by the buffer pool - </summary> - </member> - <member name="M:SuperSocket.Common.BufferManager.SetBuffer(System.Net.Sockets.SocketAsyncEventArgs)"> - <summary> - Assigns a buffer from the buffer pool to the specified SocketAsyncEventArgs object - </summary> - <returns>true if the buffer was successfully set, else false</returns> - </member> - <member name="M:SuperSocket.Common.BufferManager.FreeBuffer(System.Net.Sockets.SocketAsyncEventArgs)"> - <summary> - Removes the buffer from a SocketAsyncEventArg object. This frees the buffer back to the - buffer pool - </summary> - </member> - <member name="T:SuperSocket.Common.ConfigurationElementBase"> - <summary> - ConfigurationElementBase - </summary> - </member> - <member name="M:SuperSocket.Common.ConfigurationElementBase.#ctor"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.Common.ConfigurationElementBase"/> class. - </summary> - </member> - <member name="M:SuperSocket.Common.ConfigurationElementBase.#ctor(System.Boolean)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.Common.ConfigurationElementBase"/> class. - </summary> - <param name="nameRequired">if set to <c>true</c> [name required].</param> - </member> - <member name="M:SuperSocket.Common.ConfigurationElementBase.DeserializeElement(System.Xml.XmlReader,System.Boolean)"> - <summary> - Reads XML from the configuration file. - </summary> - <param name="reader">The <see cref="T:System.Xml.XmlReader"/> that reads from the configuration file.</param> - <param name="serializeCollectionKey">true to serialize only the collection key properties; otherwise, false.</param> - <exception cref="T:System.Configuration.ConfigurationErrorsException">The element to read is locked.- or -An attribute of the current node is not recognized.- or -The lock status of the current node cannot be determined. </exception> - </member> - <member name="M:SuperSocket.Common.ConfigurationElementBase.OnDeserializeUnrecognizedAttribute(System.String,System.String)"> - <summary> - Gets a value indicating whether an unknown attribute is encountered during deserialization. - </summary> - <param name="name">The name of the unrecognized attribute.</param> - <param name="value">The value of the unrecognized attribute.</param> - <returns> - true when an unknown attribute is encountered while deserializing; otherwise, false. - </returns> - </member> - <member name="M:SuperSocket.Common.ConfigurationElementBase.OnDeserializeUnrecognizedElement(System.String,System.Xml.XmlReader)"> - <summary> - Gets a value indicating whether an unknown element is encountered during deserialization. - </summary> - <param name="elementName">The name of the unknown subelement.</param> - <param name="reader">The <see cref="T:System.Xml.XmlReader"/> being used for deserialization.</param> - <returns> - true when an unknown element is encountered while deserializing; otherwise, false. - </returns> - <exception cref="T:System.Configuration.ConfigurationErrorsException">The element identified by <paramref name="elementName"/> is locked.- or -One or more of the element's attributes is locked.- or -<paramref name="elementName"/> is unrecognized, or the element has an unrecognized attribute.- or -The element has a Boolean attribute with an invalid value.- or -An attempt was made to deserialize a property more than once.- or -An attempt was made to deserialize a property that is not a valid member of the element.- or -The element cannot contain a CDATA or text element.</exception> - </member> - <member name="P:SuperSocket.Common.ConfigurationElementBase.Name"> - <summary> - Gets the name. - </summary> - </member> - <member name="P:SuperSocket.Common.ConfigurationElementBase.Options"> - <summary> - Gets the options. - </summary> - </member> - <member name="P:SuperSocket.Common.ConfigurationElementBase.OptionElements"> - <summary> - Gets the option elements. - </summary> - </member> - <member name="T:SuperSocket.Common.ConfigurationExtension"> - <summary> - Configuration extension class - </summary> - </member> - <member name="M:SuperSocket.Common.ConfigurationExtension.GetValue(System.Collections.Specialized.NameValueCollection,System.String)"> - <summary> - Gets the value from namevalue collection by key. - </summary> - <param name="collection">The collection.</param> - <param name="key">The key.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Common.ConfigurationExtension.GetValue(System.Collections.Specialized.NameValueCollection,System.String,System.String)"> - <summary> - Gets the value from namevalue collection by key. - </summary> - <param name="collection">The collection.</param> - <param name="key">The key.</param> - <param name="defaultValue">The default value.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Common.ConfigurationExtension.Deserialize``1(``0,System.Xml.XmlReader)"> - <summary> - Deserializes the specified configuration section. - </summary> - <typeparam name="TElement">The type of the element.</typeparam> - <param name="section">The section.</param> - <param name="reader">The reader.</param> - </member> - <member name="M:SuperSocket.Common.ConfigurationExtension.GetChildConfig``1(System.Collections.Specialized.NameValueCollection,System.String)"> - <summary> - Gets the child config. - </summary> - <typeparam name="TConfig">The type of the config.</typeparam> - <param name="childElements">The child elements.</param> - <param name="childConfigName">Name of the child config.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Common.ConfigurationExtension.GetConfigSource(System.Configuration.ConfigurationElement)"> - <summary> - Gets the config source path. - </summary> - <param name="config">The config.</param> - <returns></returns> - </member> - <member name="T:SuperSocket.Common.DictionaryExtension"> - <summary> - Extension class for IDictionary - </summary> - </member> - <member name="M:SuperSocket.Common.DictionaryExtension.GetValue``1(System.Collections.Generic.IDictionary{System.Object,System.Object},System.Object)"> - <summary> - Gets the value by key. - </summary> - <typeparam name="T"></typeparam> - <param name="dictionary">The dictionary.</param> - <param name="key">The key.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Common.DictionaryExtension.GetValue``1(System.Collections.Generic.IDictionary{System.Object,System.Object},System.Object,``0)"> - <summary> - Gets the value by key and default value. - </summary> - <typeparam name="T"></typeparam> - <param name="dictionary">The dictionary.</param> - <param name="key">The key.</param> - <param name="defaultValue">The default value.</param> - <returns></returns> - </member> - <member name="T:SuperSocket.Common.ErrorEventArgs"> - <summary> - EventArgs for error and exception - </summary> - </member> - <member name="M:SuperSocket.Common.ErrorEventArgs.#ctor(System.String)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.Common.ErrorEventArgs"/> class. - </summary> - <param name="message">The message.</param> - </member> - <member name="M:SuperSocket.Common.ErrorEventArgs.#ctor(System.Exception)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.Common.ErrorEventArgs"/> class. - </summary> - <param name="exception">The exception.</param> - </member> - <member name="P:SuperSocket.Common.ErrorEventArgs.Exception"> - <summary> - Gets the exception. - </summary> - </member> - <member name="T:SuperSocket.Common.GenericConfigurationElementCollectionBase`2"> - <summary> - GenericConfigurationElementCollectionBase - </summary> - <typeparam name="TConfigElement">The type of the config element.</typeparam> - <typeparam name="TConfigInterface">The type of the config interface.</typeparam> - </member> - <member name="M:SuperSocket.Common.GenericConfigurationElementCollectionBase`2.CreateNewElement"> - <summary> - When overridden in a derived class, creates a new <see cref="T:System.Configuration.ConfigurationElement"/>. - </summary> - <returns> - A new <see cref="T:System.Configuration.ConfigurationElement"/>. - </returns> - </member> - <member name="M:SuperSocket.Common.GenericConfigurationElementCollectionBase`2.GetElementKey(System.Configuration.ConfigurationElement)"> - <summary> - Gets the element key for a specified configuration element when overridden in a derived class. - </summary> - <param name="element">The <see cref="T:System.Configuration.ConfigurationElement"/> to return the key for.</param> - <returns> - An <see cref="T:System.Object"/> that acts as the key for the specified <see cref="T:System.Configuration.ConfigurationElement"/>. - </returns> - </member> - <member name="M:SuperSocket.Common.GenericConfigurationElementCollectionBase`2.GetEnumerator"> - <summary> - Returns an enumerator that iterates through the collection. - </summary> - <returns> - A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection. - </returns> - </member> - <member name="P:SuperSocket.Common.GenericConfigurationElementCollectionBase`2.Item(System.Int32)"> - <summary> - Gets or sets a property, attribute, or child element of this configuration element. - </summary> - <returns>The specified property, attribute, or child element</returns> - </member> - <member name="T:SuperSocket.Common.GenericConfigurationElementCollection`2"> - <summary> - GenericConfigurationElementCollection - </summary> - <typeparam name="TConfigElement">The type of the config element.</typeparam> - <typeparam name="TConfigInterface">The type of the config interface.</typeparam> - </member> - <member name="M:SuperSocket.Common.GenericConfigurationElementCollection`2.GetElementKey(System.Configuration.ConfigurationElement)"> - <summary> - Gets the element key. - </summary> - <param name="element">The element.</param> - <returns></returns> - </member> - <member name="T:SuperSocket.Common.Platform"> - <summary> - This class is designed for detect platform attribute in runtime - </summary> - </member> - <member name="P:SuperSocket.Common.Platform.SupportSocketIOControlByCodeEnum"> - <summary> - Gets a value indicating whether [support socket IO control by code enum]. - </summary> - <value> - <c>true</c> if [support socket IO control by code enum]; otherwise, <c>false</c>. - </value> - </member> - <member name="P:SuperSocket.Common.Platform.IsMono"> - <summary> - Gets a value indicating whether this instance is mono. - </summary> - <value> - <c>true</c> if this instance is mono; otherwise, <c>false</c>. - </value> - </member> - <member name="T:SuperSocket.Common.SearchMarkState`1"> - <summary> - SearchMarkState - </summary> - <typeparam name="T"></typeparam> - </member> - <member name="M:SuperSocket.Common.SearchMarkState`1.#ctor(`0[])"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.Common.SearchMarkState`1"/> class. - </summary> - <param name="mark">The mark.</param> - </member> - <member name="P:SuperSocket.Common.SearchMarkState`1.Mark"> - <summary> - Gets the mark. - </summary> - </member> - <member name="P:SuperSocket.Common.SearchMarkState`1.Matched"> - <summary> - Gets or sets whether matched already. - </summary> - <value> - The matched. - </value> - </member> - <member name="T:SuperSocket.Common.SendingQueue"> - <summary> - SendingQueue - </summary> - </member> - <member name="M:SuperSocket.Common.SendingQueue.#ctor(System.ArraySegment{System.Byte}[],System.Int32,System.Int32)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.Common.SendingQueue"/> class. - </summary> - <param name="globalQueue">The global queue.</param> - <param name="offset">The offset.</param> - <param name="capacity">The capacity.</param> - </member> - <member name="M:SuperSocket.Common.SendingQueue.Enqueue(System.ArraySegment{System.Byte},System.UInt16)"> - <summary> - Enqueues the specified item. - </summary> - <param name="item">The item.</param> - <param name="trackID">The track ID.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Common.SendingQueue.Enqueue(System.Collections.Generic.IList{System.ArraySegment{System.Byte}},System.UInt16)"> - <summary> - Enqueues the specified items. - </summary> - <param name="items">The items.</param> - <param name="trackID">The track ID.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Common.SendingQueue.StopEnqueue"> - <summary> - Stops the enqueue, and then wait all current excueting enqueu threads exit. - </summary> - </member> - <member name="M:SuperSocket.Common.SendingQueue.StartEnqueue"> - <summary> - Starts to allow enqueue. - </summary> - </member> - <member name="M:SuperSocket.Common.SendingQueue.IndexOf(System.ArraySegment{System.Byte})"> - <summary> - Determines the index of a specific item in the <see cref="T:System.Collections.Generic.IList`1"/>. - </summary> - <param name="item">The object to locate in the <see cref="T:System.Collections.Generic.IList`1"/>.</param> - <returns> - The index of <paramref name="item"/> if found in the list; otherwise, -1. - </returns> - <exception cref="T:System.NotSupportedException"></exception> - </member> - <member name="M:SuperSocket.Common.SendingQueue.Insert(System.Int32,System.ArraySegment{System.Byte})"> - <summary> - Inserts an item to the <see cref="T:System.Collections.Generic.IList`1"/> at the specified index. - </summary> - <param name="index">The zero-based index at which <paramref name="item"/> should be inserted.</param> - <param name="item">The object to insert into the <see cref="T:System.Collections.Generic.IList`1"/>.</param> - <exception cref="T:System.NotSupportedException"></exception> - </member> - <member name="M:SuperSocket.Common.SendingQueue.RemoveAt(System.Int32)"> - <summary> - Removes the <see cref="T:System.Collections.Generic.IList`1"/> item at the specified index. - </summary> - <param name="index">The zero-based index of the item to remove.</param> - <exception cref="T:System.NotSupportedException"></exception> - </member> - <member name="M:SuperSocket.Common.SendingQueue.Add(System.ArraySegment{System.Byte})"> - <summary> - Adds an item to the <see cref="T:System.Collections.Generic.ICollection`1"/>. - </summary> - <param name="item">The object to add to the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param> - <exception cref="T:System.NotSupportedException"></exception> - </member> - <member name="M:SuperSocket.Common.SendingQueue.Clear"> - <summary> - Removes all items from the <see cref="T:System.Collections.Generic.ICollection`1"/>. - </summary> - <exception cref="T:System.NotSupportedException"></exception> - </member> - <member name="M:SuperSocket.Common.SendingQueue.Contains(System.ArraySegment{System.Byte})"> - <summary> - Determines whether the <see cref="T:System.Collections.Generic.ICollection`1"/> contains a specific value. - </summary> - <param name="item">The object to locate in the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param> - <returns> - true if <paramref name="item"/> is found in the <see cref="T:System.Collections.Generic.ICollection`1"/>; otherwise, false. - </returns> - <exception cref="T:System.NotSupportedException"></exception> - </member> - <member name="M:SuperSocket.Common.SendingQueue.CopyTo(System.ArraySegment{System.Byte}[],System.Int32)"> - <summary> - Copies to. - </summary> - <param name="array">The array.</param> - <param name="arrayIndex">Index of the array.</param> - </member> - <member name="M:SuperSocket.Common.SendingQueue.Remove(System.ArraySegment{System.Byte})"> - <summary> - Removes the first occurrence of a specific object from the <see cref="T:System.Collections.Generic.ICollection`1"/>. - </summary> - <param name="item">The object to remove from the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param> - <returns> - true if <paramref name="item"/> was successfully removed from the <see cref="T:System.Collections.Generic.ICollection`1"/>; otherwise, false. This method also returns false if <paramref name="item"/> is not found in the original <see cref="T:System.Collections.Generic.ICollection`1"/>. - </returns> - <exception cref="T:System.NotSupportedException"></exception> - </member> - <member name="M:SuperSocket.Common.SendingQueue.GetEnumerator"> - <summary> - Returns an enumerator that iterates through the collection. - </summary> - <returns> - A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection. - </returns> - <exception cref="T:System.NotSupportedException"></exception> - </member> - <member name="M:SuperSocket.Common.SendingQueue.System#Collections#IEnumerable#GetEnumerator"> - <summary> - Returns an enumerator that iterates through a collection. - </summary> - <returns> - An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection. - </returns> - <exception cref="T:System.NotSupportedException"></exception> - </member> - <member name="P:SuperSocket.Common.SendingQueue.TrackID"> - <summary> - Gets the track ID. - </summary> - <value> - The track ID. - </value> - </member> - <member name="P:SuperSocket.Common.SendingQueue.GlobalQueue"> - <summary> - Gets the global queue. - </summary> - <value> - The global queue. - </value> - </member> - <member name="P:SuperSocket.Common.SendingQueue.Offset"> - <summary> - Gets the offset. - </summary> - <value> - The offset. - </value> - </member> - <member name="P:SuperSocket.Common.SendingQueue.Capacity"> - <summary> - Gets the capacity. - </summary> - <value> - The capacity. - </value> - </member> - <member name="P:SuperSocket.Common.SendingQueue.Count"> - <summary> - Gets the number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1" />. - </summary> - <returns>The number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1" />.</returns> - </member> - <member name="P:SuperSocket.Common.SendingQueue.Position"> - <summary> - Gets or sets the position. - </summary> - <value> - The position. - </value> - </member> - <member name="P:SuperSocket.Common.SendingQueue.Item(System.Int32)"> - <summary> - Gets or sets the element at the specified index. - </summary> - <param name="index">The index.</param> - <returns></returns> - <exception cref="T:System.NotSupportedException"></exception> - </member> - <member name="P:SuperSocket.Common.SendingQueue.IsReadOnly"> - <summary> - Gets a value indicating whether the <see cref="T:System.Collections.Generic.ICollection`1" /> is read-only. - </summary> - <returns>true if the <see cref="T:System.Collections.Generic.ICollection`1" /> is read-only; otherwise, false.</returns> - </member> - <member name="T:SuperSocket.Common.SendingQueueSourceCreator"> - <summary> - SendingQueueSourceCreator - </summary> - </member> - <member name="T:SuperSocket.Common.ISmartPoolSourceCreator`1"> - <summary> - ISmartPoolSourceCreator - </summary> - <typeparam name="T"></typeparam> - </member> - <member name="M:SuperSocket.Common.ISmartPoolSourceCreator`1.Create(System.Int32,`0[]@)"> - <summary> - Creates the specified size. - </summary> - <param name="size">The size.</param> - <param name="poolItems">The pool items.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Common.SendingQueueSourceCreator.#ctor(System.Int32)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.Common.SendingQueueSourceCreator"/> class. - </summary> - <param name="sendingQueueSize">Size of the sending queue.</param> - </member> - <member name="M:SuperSocket.Common.SendingQueueSourceCreator.Create(System.Int32,SuperSocket.Common.SendingQueue[]@)"> - <summary> - Creates the specified size. - </summary> - <param name="size">The size.</param> - <param name="poolItems">The pool items.</param> - <returns></returns> - </member> - <member name="T:SuperSocket.Common.IPoolInfo"> - <summary> - The pool information class - </summary> - </member> - <member name="P:SuperSocket.Common.IPoolInfo.MinPoolSize"> - <summary> - Gets the min size of the pool. - </summary> - <value> - The min size of the pool. - </value> - </member> - <member name="P:SuperSocket.Common.IPoolInfo.MaxPoolSize"> - <summary> - Gets the max size of the pool. - </summary> - <value> - The max size of the pool. - </value> - </member> - <member name="P:SuperSocket.Common.IPoolInfo.AvialableItemsCount"> - <summary> - Gets the avialable items count. - </summary> - <value> - The avialable items count. - </value> - </member> - <member name="P:SuperSocket.Common.IPoolInfo.TotalItemsCount"> - <summary> - Gets the total items count, include items in the pool and outside the pool. - </summary> - <value> - The total items count. - </value> - </member> - <member name="T:SuperSocket.Common.ISmartPool`1"> - <summary> - The basic interface of smart pool - </summary> - <typeparam name="T"></typeparam> - </member> - <member name="M:SuperSocket.Common.ISmartPool`1.Initialize(System.Int32,System.Int32,SuperSocket.Common.ISmartPoolSourceCreator{`0})"> - <summary> - Initializes the specified min pool size. - </summary> - <param name="minPoolSize">The min size of the pool.</param> - <param name="maxPoolSize">The max size of the pool.</param> - <param name="sourceCreator">The source creator.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Common.ISmartPool`1.Push(`0)"> - <summary> - Pushes the specified item into the pool. - </summary> - <param name="item">The item.</param> - </member> - <member name="M:SuperSocket.Common.ISmartPool`1.TryGet(`0@)"> - <summary> - Tries to get one item from the pool. - </summary> - <param name="item">The item.</param> - <returns></returns> - </member> - <member name="T:SuperSocket.Common.ISmartPoolSource"> - <summary> - ISmartPoolSource - </summary> - </member> - <member name="P:SuperSocket.Common.ISmartPoolSource.Count"> - <summary> - Gets the count. - </summary> - <value> - The count. - </value> - </member> - <member name="T:SuperSocket.Common.SmartPoolSource"> - <summary> - SmartPoolSource - </summary> - </member> - <member name="M:SuperSocket.Common.SmartPoolSource.#ctor(System.Object,System.Int32)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.Common.SmartPoolSource"/> class. - </summary> - <param name="source">The source.</param> - <param name="itemsCount">The items count.</param> - </member> - <member name="P:SuperSocket.Common.SmartPoolSource.Source"> - <summary> - Gets the source. - </summary> - <value> - The source. - </value> - </member> - <member name="P:SuperSocket.Common.SmartPoolSource.Count"> - <summary> - Gets the count. - </summary> - <value> - The count. - </value> - </member> - <member name="T:SuperSocket.Common.SmartPool`1"> - <summary> - The smart pool - </summary> - <typeparam name="T"></typeparam> - </member> - <member name="M:SuperSocket.Common.SmartPool`1.Initialize(System.Int32,System.Int32,SuperSocket.Common.ISmartPoolSourceCreator{`0})"> - <summary> - Initializes the specified min and max pool size. - </summary> - <param name="minPoolSize">The min size of the pool.</param> - <param name="maxPoolSize">The max size of the pool.</param> - <param name="sourceCreator">The source creator.</param> - </member> - <member name="M:SuperSocket.Common.SmartPool`1.Push(`0)"> - <summary> - Pushes the specified item into the pool. - </summary> - <param name="item">The item.</param> - </member> - <member name="M:SuperSocket.Common.SmartPool`1.TryGet(`0@)"> - <summary> - Tries to get one item from the pool. - </summary> - <param name="item">The item.</param> - <returns></returns> - <exception cref="T:System.NotImplementedException"></exception> - </member> - <member name="P:SuperSocket.Common.SmartPool`1.MinPoolSize"> - <summary> - Gets the size of the min pool. - </summary> - <value> - The size of the min pool. - </value> - </member> - <member name="P:SuperSocket.Common.SmartPool`1.MaxPoolSize"> - <summary> - Gets the size of the max pool. - </summary> - <value> - The size of the max pool. - </value> - </member> - <member name="P:SuperSocket.Common.SmartPool`1.AvialableItemsCount"> - <summary> - Gets the avialable items count. - </summary> - <value> - The avialable items count. - </value> - </member> - <member name="P:SuperSocket.Common.SmartPool`1.TotalItemsCount"> - <summary> - Gets the total items count, include items in the pool and outside the pool. - </summary> - <value> - The total items count. - </value> - </member> - <member name="T:SuperSocket.Common.SocketEx"> - <summary> - Socket extension class - </summary> - </member> - <member name="M:SuperSocket.Common.SocketEx.SafeClose(System.Net.Sockets.Socket)"> - <summary> - Close the socket safely. - </summary> - <param name="socket">The socket.</param> - </member> - <member name="M:SuperSocket.Common.SocketEx.SendData(System.Net.Sockets.Socket,System.Byte[])"> - <summary> - Sends the data. - </summary> - <param name="client">The client.</param> - <param name="data">The data.</param> - </member> - <member name="M:SuperSocket.Common.SocketEx.SendData(System.Net.Sockets.Socket,System.Byte[],System.Int32,System.Int32)"> - <summary> - Sends the data. - </summary> - <param name="client">The client.</param> - <param name="data">The data.</param> - <param name="offset">The offset.</param> - <param name="length">The length.</param> - </member> - <member name="T:SuperSocket.Common.StringExtension"> - <summary> - String extension class - </summary> - <summary> - String extension - </summary> - </member> - <member name="M:SuperSocket.Common.StringExtension.ToInt32(System.String)"> - <summary> - Convert string to int32. - </summary> - <param name="source">The source.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Common.StringExtension.ToInt32(System.String,System.Int32)"> - <summary> - Convert string to int32. - </summary> - <param name="source">The source.</param> - <param name="defaultValue">The default value.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Common.StringExtension.ToLong(System.String)"> - <summary> - Convert string to long. - </summary> - <param name="source">The source.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Common.StringExtension.ToLong(System.String,System.Int64)"> - <summary> - Convert string to long. - </summary> - <param name="source">The source.</param> - <param name="defaultValue">The default value.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Common.StringExtension.ToShort(System.String)"> - <summary> - Convert string to short. - </summary> - <param name="source">The source.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Common.StringExtension.ToShort(System.String,System.Int16)"> - <summary> - Convert string to short. - </summary> - <param name="source">The source.</param> - <param name="defaultValue">The default value.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Common.StringExtension.ToDecimal(System.String)"> - <summary> - Convert string to decimal. - </summary> - <param name="source">The source.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Common.StringExtension.ToDecimal(System.String,System.Decimal)"> - <summary> - Convert string to decimal. - </summary> - <param name="source">The source.</param> - <param name="defaultValue">The default value.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Common.StringExtension.ToDateTime(System.String)"> - <summary> - Convert string to date time. - </summary> - <param name="source">The source.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Common.StringExtension.ToDateTime(System.String,System.DateTime)"> - <summary> - Convert string to date time. - </summary> - <param name="source">The source.</param> - <param name="defaultValue">The default value.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Common.StringExtension.ToBoolean(System.String)"> - <summary> - Convert string to boolean. - </summary> - <param name="source">The source.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Common.StringExtension.ToBoolean(System.String,System.Boolean)"> - <summary> - Convert string tp boolean. - </summary> - <param name="source">The source.</param> - <param name="defaultValue">if set to <c>true</c> [default value].</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Common.StringExtension.TryParseEnum``1(System.String,System.Boolean,``0@)"> - <summary> - Tries parse string to enum. - </summary> - <typeparam name="T">the enum type</typeparam> - <param name="value">The value.</param> - <param name="ignoreCase">if set to <c>true</c> [ignore case].</param> - <param name="enumValue">The enum value.</param> - <returns></returns> - </member> - <member name="T:SuperSocket.Common.TheadPoolEx"> - <summary> - Thread pool extension class - </summary> - </member> - <member name="M:SuperSocket.Common.TheadPoolEx.ResetThreadPool(System.Nullable{System.Int32},System.Nullable{System.Int32},System.Nullable{System.Int32},System.Nullable{System.Int32})"> - <summary> - Resets the thread pool. - </summary> - <param name="maxWorkingThreads">The max working threads.</param> - <param name="maxCompletionPortThreads">The max completion port threads.</param> - <param name="minWorkingThreads">The min working threads.</param> - <param name="minCompletionPortThreads">The min completion port threads.</param> - <returns></returns> - </member> - </members> -</doc> diff --git a/cb-tools/SuperWebSocket/SuperSocket.Facility.XML b/cb-tools/SuperWebSocket/SuperSocket.Facility.XML deleted file mode 100644 index 77c6fc99..00000000 --- a/cb-tools/SuperWebSocket/SuperSocket.Facility.XML +++ /dev/null @@ -1,467 +0,0 @@ -<?xml version="1.0"?> -<doc> - <assembly> - <name>SuperSocket.Facility</name> - </assembly> - <members> - <member name="T:SuperSocket.Facility.PolicyServer.FlashPolicyServer"> - <summary> - Flash policy AppServer - </summary> - </member> - <member name="T:SuperSocket.Facility.PolicyServer.PolicyServer"> - <summary> - PolicyServer base class - </summary> - </member> - <member name="M:SuperSocket.Facility.PolicyServer.PolicyServer.#ctor"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.Facility.PolicyServer.PolicyServer"/> class. - </summary> - </member> - <member name="M:SuperSocket.Facility.PolicyServer.PolicyServer.Setup(SuperSocket.SocketBase.Config.IRootConfig,SuperSocket.SocketBase.Config.IServerConfig)"> - <summary> - Setups the specified root config. - </summary> - <param name="rootConfig">The root config.</param> - <param name="config">The config.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Facility.PolicyServer.PolicyServer.SetupPolicyResponse(System.Byte[])"> - <summary> - Setups the policy response. - </summary> - <param name="policyFileData">The policy file data.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Facility.PolicyServer.PolicyServer.GetPolicyFileResponse(System.Net.IPEndPoint)"> - <summary> - Gets the policy file response. - </summary> - <param name="clientEndPoint">The client end point.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Facility.PolicyServer.PolicyServer.ProcessRequest(SuperSocket.Facility.PolicyServer.PolicySession,System.Byte[])"> - <summary> - Processes the request. - </summary> - <param name="session">The session.</param> - <param name="data">The data.</param> - </member> - <member name="P:SuperSocket.Facility.PolicyServer.PolicyServer.PolicyResponse"> - <summary> - Gets the policy response. - </summary> - </member> - <member name="M:SuperSocket.Facility.PolicyServer.FlashPolicyServer.#ctor"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.Facility.PolicyServer.FlashPolicyServer"/> class. - </summary> - </member> - <member name="M:SuperSocket.Facility.PolicyServer.FlashPolicyServer.SetupPolicyResponse(System.Byte[])"> - <summary> - Setups the policy response. - </summary> - <param name="policyFileData">The policy file data.</param> - <returns></returns> - </member> - <member name="T:SuperSocket.Facility.PolicyServer.PolicyReceiveFilter"> - <summary> - PolicyReceiveFilter - </summary> - </member> - <member name="T:SuperSocket.Facility.Protocol.FixedSizeReceiveFilter`1"> - <summary> - FixedSizeReceiveFilter - </summary> - <typeparam name="TRequestInfo">The type of the request info.</typeparam> - </member> - <member name="F:SuperSocket.Facility.Protocol.FixedSizeReceiveFilter`1.NullRequestInfo"> - <summary> - Null RequestInfo - </summary> - </member> - <member name="M:SuperSocket.Facility.Protocol.FixedSizeReceiveFilter`1.#ctor(System.Int32)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.Facility.Protocol.FixedSizeReceiveFilter`1"/> class. - </summary> - <param name="size">The size.</param> - </member> - <member name="M:SuperSocket.Facility.Protocol.FixedSizeReceiveFilter`1.Filter(System.Byte[],System.Int32,System.Int32,System.Boolean,System.Int32@)"> - <summary> - Filters the specified session. - </summary> - <param name="readBuffer">The read buffer.</param> - <param name="offset">The offset.</param> - <param name="length">The length.</param> - <param name="toBeCopied">if set to <c>true</c> [to be copied].</param> - <param name="rest">The rest.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Facility.Protocol.FixedSizeReceiveFilter`1.ProcessMatchedRequest(System.Byte[],System.Int32,System.Int32,System.Boolean)"> - <summary> - Filters the buffer after the server receive the enough size of data. - </summary> - <param name="buffer">The buffer.</param> - <param name="offset">The offset.</param> - <param name="length">The length.</param> - <param name="toBeCopied">if set to <c>true</c> [to be copied].</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Facility.Protocol.FixedSizeReceiveFilter`1.Reset"> - <summary> - Resets this instance. - </summary> - </member> - <member name="P:SuperSocket.Facility.Protocol.FixedSizeReceiveFilter`1.Size"> - <summary> - Gets the size of the fixed size Receive filter. - </summary> - </member> - <member name="P:SuperSocket.Facility.Protocol.FixedSizeReceiveFilter`1.LeftBufferSize"> - <summary> - Gets the size of the rest buffer. - </summary> - <value> - The size of the rest buffer. - </value> - </member> - <member name="P:SuperSocket.Facility.Protocol.FixedSizeReceiveFilter`1.NextReceiveFilter"> - <summary> - Gets the next Receive filter. - </summary> - </member> - <member name="P:SuperSocket.Facility.Protocol.FixedSizeReceiveFilter`1.SuperSocket#SocketBase#Protocol#IOffsetAdapter#OffsetDelta"> - <summary> - Gets the offset delta. - </summary> - </member> - <member name="P:SuperSocket.Facility.Protocol.FixedSizeReceiveFilter`1.State"> - <summary> - Gets the filter state. - </summary> - <value> - The filter state. - </value> - </member> - <member name="M:SuperSocket.Facility.PolicyServer.PolicyReceiveFilter.#ctor(System.Int32)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.Facility.PolicyServer.PolicyReceiveFilter"/> class. - </summary> - <param name="size">The size.</param> - </member> - <member name="M:SuperSocket.Facility.PolicyServer.PolicyReceiveFilter.ProcessMatchedRequest(System.Byte[],System.Int32,System.Int32,System.Boolean)"> - <summary> - Filters the buffer after the server receive the enough size of data. - </summary> - <param name="buffer">The buffer.</param> - <param name="offset">The offset.</param> - <param name="length">The length.</param> - <param name="toBeCopied">if set to <c>true</c> [to be copied].</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Facility.PolicyServer.PolicyReceiveFilterFactory.#ctor(System.Int32)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.Facility.PolicyServer.PolicyReceiveFilterFactory"/> class. - </summary> - <param name="fixRequestSize">Size of the fix request.</param> - </member> - <member name="M:SuperSocket.Facility.PolicyServer.PolicyReceiveFilterFactory.CreateFilter(SuperSocket.SocketBase.IAppServer,SuperSocket.SocketBase.IAppSession,System.Net.IPEndPoint)"> - <summary> - Creates the filter. - </summary> - <param name="appServer">The app server.</param> - <param name="appSession">The app session.</param> - <param name="remoteEndPoint">The remote end point.</param> - <returns></returns> - </member> - <member name="P:SuperSocket.Facility.PolicyServer.PolicyReceiveFilterFactory.FixRequestSize"> - <summary> - Gets the size of the fix request. - </summary> - <value> - The size of the fix request. - </value> - </member> - <member name="T:SuperSocket.Facility.PolicyServer.PolicySession"> - <summary> - PolicySession - </summary> - </member> - <member name="T:SuperSocket.Facility.PolicyServer.SilverlightPolicyServer"> - <summary> - Silverlight policy AppServer - </summary> - </member> - <member name="M:SuperSocket.Facility.PolicyServer.SilverlightPolicyServer.#ctor"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.Facility.PolicyServer.SilverlightPolicyServer"/> class. - </summary> - </member> - <member name="M:SuperSocket.Facility.PolicyServer.SilverlightPolicyServer.ProcessRequest(SuperSocket.Facility.PolicyServer.PolicySession,System.Byte[])"> - <summary> - Processes the request. - </summary> - <param name="session">The session.</param> - <param name="data">The data.</param> - </member> - <member name="T:SuperSocket.Facility.Protocol.BeginEndMarkReceiveFilter`1"> - <summary> - ReceiveFilter for the protocol that each request has bengin and end mark - </summary> - <typeparam name="TRequestInfo">The type of the request info.</typeparam> - </member> - <member name="F:SuperSocket.Facility.Protocol.BeginEndMarkReceiveFilter`1.NullRequestInfo"> - <summary> - Null request info - </summary> - </member> - <member name="M:SuperSocket.Facility.Protocol.BeginEndMarkReceiveFilter`1.#ctor(System.Byte[],System.Byte[])"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.Facility.Protocol.BeginEndMarkReceiveFilter`1"/> class. - </summary> - <param name="beginMark">The begin mark.</param> - <param name="endMark">The end mark.</param> - </member> - <member name="M:SuperSocket.Facility.Protocol.BeginEndMarkReceiveFilter`1.Filter(System.Byte[],System.Int32,System.Int32,System.Boolean,System.Int32@)"> - <summary> - Filters the specified session. - </summary> - <param name="readBuffer">The read buffer.</param> - <param name="offset">The offset.</param> - <param name="length">The length.</param> - <param name="toBeCopied">if set to <c>true</c> [to be copied].</param> - <param name="rest">The rest.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Facility.Protocol.BeginEndMarkReceiveFilter`1.ProcessMatchedRequest(System.Byte[],System.Int32,System.Int32)"> - <summary> - Processes the matched request. - </summary> - <param name="readBuffer">The read buffer.</param> - <param name="offset">The offset.</param> - <param name="length">The length.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Facility.Protocol.BeginEndMarkReceiveFilter`1.Reset"> - <summary> - Resets this instance. - </summary> - </member> - <member name="T:SuperSocket.Facility.Protocol.CountSpliterReceiveFilter`1"> - <summary> - This Receive filter is designed for this kind protocol: - each request has fixed count part which splited by a char(byte) - for instance, request is defined like this "#12122#23343#4545456565#343435446#", - because this request is splited into many parts by 5 '#', we can create a Receive filter by CountSpliterRequestFilter((byte)'#', 5) - </summary> - <typeparam name="TRequestInfo">The type of the request info.</typeparam> - </member> - <member name="F:SuperSocket.Facility.Protocol.CountSpliterReceiveFilter`1.NullRequestInfo"> - <summary> - Null request info instance - </summary> - </member> - <member name="M:SuperSocket.Facility.Protocol.CountSpliterReceiveFilter`1.#ctor(System.Byte,System.Int32)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.Facility.Protocol.CountSpliterReceiveFilter`1"/> class. - </summary> - <param name="spliter">The spliter.</param> - <param name="spliterCount">The spliter count.</param> - </member> - <member name="M:SuperSocket.Facility.Protocol.CountSpliterReceiveFilter`1.Filter(System.Byte[],System.Int32,System.Int32,System.Boolean,System.Int32@)"> - <summary> - Filters the specified session. - </summary> - <param name="readBuffer">The read buffer.</param> - <param name="offset">The offset.</param> - <param name="length">The length.</param> - <param name="toBeCopied">if set to <c>true</c> [to be copied].</param> - <param name="rest">The rest.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Facility.Protocol.CountSpliterReceiveFilter`1.ProcessMatchedRequest(System.Byte[],System.Int32,System.Int32)"> - <summary> - Processes the matched request. - </summary> - <param name="readBuffer">The read buffer.</param> - <param name="offset">The offset.</param> - <param name="length">The length.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Facility.Protocol.CountSpliterReceiveFilter`1.Reset"> - <summary> - Resets this instance. - </summary> - </member> - <member name="P:SuperSocket.Facility.Protocol.CountSpliterReceiveFilter`1.LeftBufferSize"> - <summary> - Gets the size of the rest buffer. - </summary> - <value> - The size of the rest buffer. - </value> - </member> - <member name="P:SuperSocket.Facility.Protocol.CountSpliterReceiveFilter`1.NextReceiveFilter"> - <summary> - Gets the next Receive filter. - </summary> - </member> - <member name="P:SuperSocket.Facility.Protocol.CountSpliterReceiveFilter`1.OffsetDelta"> - <summary> - Gets the offset delta relative original receiving offset which will be used for next round receiving. - </summary> - </member> - <member name="P:SuperSocket.Facility.Protocol.CountSpliterReceiveFilter`1.State"> - <summary> - Gets the filter state. - </summary> - <value> - The filter state. - </value> - </member> - <member name="T:SuperSocket.Facility.Protocol.CountSpliterReceiveFilter"> - <summary> - This Receive filter is designed for this kind protocol: - each request has fixed count part which splited by a char(byte) - for instance, request is defined like this "#12122#23343#4545456565#343435446#", - because this request is splited into many parts by 5 '#', we can create a Receive filter by CountSpliterRequestFilter((byte)'#', 5) - </summary> - </member> - <member name="M:SuperSocket.Facility.Protocol.CountSpliterReceiveFilter.#ctor(System.Byte,System.Int32)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.Facility.Protocol.CountSpliterReceiveFilter"/> class. - </summary> - <param name="spliter">The spliter.</param> - <param name="spliterCount">The spliter count.</param> - </member> - <member name="M:SuperSocket.Facility.Protocol.CountSpliterReceiveFilter.#ctor(System.Byte,System.Int32,System.Text.Encoding)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.Facility.Protocol.CountSpliterReceiveFilter"/> class. - </summary> - <param name="spliter">The spliter.</param> - <param name="spliterCount">The spliter count.</param> - <param name="encoding">The encoding.</param> - </member> - <member name="M:SuperSocket.Facility.Protocol.CountSpliterReceiveFilter.#ctor(System.Byte,System.Int32,System.Text.Encoding,System.Int32)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.Facility.Protocol.CountSpliterReceiveFilter"/> class. - </summary> - <param name="spliter">The spliter.</param> - <param name="spliterCount">The spliter count.</param> - <param name="encoding">The encoding.</param> - <param name="keyIndex">Index of the key.</param> - </member> - <member name="M:SuperSocket.Facility.Protocol.CountSpliterReceiveFilter.ProcessMatchedRequest(System.Byte[],System.Int32,System.Int32)"> - <summary> - Processes the matched request. - </summary> - <param name="readBuffer">The read buffer.</param> - <param name="offset">The offset.</param> - <param name="length">The length.</param> - <returns></returns> - </member> - <member name="T:SuperSocket.Facility.Protocol.CountSpliterReceiveFilterFactory`2"> - <summary> - ReceiveFilterFactory for CountSpliterReceiveFilter - </summary> - <typeparam name="TRequestFilter">The type of the Receive filter.</typeparam> - <typeparam name="TRequestInfo">The type of the request info.</typeparam> - </member> - <member name="M:SuperSocket.Facility.Protocol.CountSpliterReceiveFilterFactory`2.CreateFilter(SuperSocket.SocketBase.IAppServer,SuperSocket.SocketBase.IAppSession,System.Net.IPEndPoint)"> - <summary> - Creates the filter. - </summary> - <param name="appServer">The app server.</param> - <param name="appSession">The app session.</param> - <param name="remoteEndPoint">The remote end point.</param> - <returns></returns> - </member> - <member name="T:SuperSocket.Facility.Protocol.CountSpliterReceiveFilterFactory`1"> - <summary> - ReceiveFilterFactory for CountSpliterReceiveFilter - </summary> - <typeparam name="TRequestFilter">The type of the Receive filter.</typeparam> - </member> - <member name="T:SuperSocket.Facility.Protocol.CountSpliterReceiveFilterFactory"> - <summary> - receiveFilterFactory for CountSpliterRequestFilter - </summary> - </member> - <member name="M:SuperSocket.Facility.Protocol.CountSpliterReceiveFilterFactory.#ctor(System.Byte,System.Int32)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.Facility.Protocol.CountSpliterReceiveFilterFactory"/> class. - </summary> - <param name="spliter">The spliter.</param> - <param name="count">The count.</param> - </member> - <member name="M:SuperSocket.Facility.Protocol.CountSpliterReceiveFilterFactory.CreateFilter(SuperSocket.SocketBase.IAppServer,SuperSocket.SocketBase.IAppSession,System.Net.IPEndPoint)"> - <summary> - Creates the filter. - </summary> - <param name="appServer">The app server.</param> - <param name="appSession">The app session.</param> - <param name="remoteEndPoint">The remote end point.</param> - <returns></returns> - </member> - <member name="T:SuperSocket.Facility.Protocol.FixedHeaderReceiveFilter`1"> - <summary> - FixedHeaderReceiveFilter, - it is the Receive filter base for the protocol which define fixed length header and the header contains the request body length, - you can implement your own Receive filter for this kind protocol easily by inheriting this class - </summary> - <typeparam name="TRequestInfo">The type of the request info.</typeparam> - </member> - <member name="M:SuperSocket.Facility.Protocol.FixedHeaderReceiveFilter`1.#ctor(System.Int32)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.Facility.Protocol.FixedHeaderReceiveFilter`1"/> class. - </summary> - <param name="headerSize">Size of the header.</param> - </member> - <member name="M:SuperSocket.Facility.Protocol.FixedHeaderReceiveFilter`1.Filter(System.Byte[],System.Int32,System.Int32,System.Boolean,System.Int32@)"> - <summary> - Filters the specified session. - </summary> - <param name="readBuffer">The read buffer.</param> - <param name="offset">The offset.</param> - <param name="length">The length.</param> - <param name="toBeCopied">if set to <c>true</c> [to be copied].</param> - <param name="rest">The rest.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Facility.Protocol.FixedHeaderReceiveFilter`1.ProcessMatchedRequest(System.Byte[],System.Int32,System.Int32,System.Boolean)"> - <summary> - Processes the fix size request. - </summary> - <param name="buffer">The buffer.</param> - <param name="offset">The offset.</param> - <param name="length">The length.</param> - <param name="toBeCopied">if set to <c>true</c> [to be copied].</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Facility.Protocol.FixedHeaderReceiveFilter`1.GetBodyLengthFromHeader(System.Byte[],System.Int32,System.Int32)"> - <summary> - Gets the body length from header. - </summary> - <param name="header">The header.</param> - <param name="offset">The offset.</param> - <param name="length">The length.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Facility.Protocol.FixedHeaderReceiveFilter`1.ResolveRequestInfo(System.ArraySegment{System.Byte},System.Byte[],System.Int32,System.Int32)"> - <summary> - Resolves the request data. - </summary> - <param name="header">The header.</param> - <param name="bodyBuffer">The body buffer.</param> - <param name="offset">The offset.</param> - <param name="length">The length.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.Facility.Protocol.FixedHeaderReceiveFilter`1.Reset"> - <summary> - Resets this instance. - </summary> - </member> - </members> -</doc> diff --git a/cb-tools/SuperWebSocket/SuperSocket.Facility.dll b/cb-tools/SuperWebSocket/SuperSocket.Facility.dll deleted file mode 100644 index 159a9d98bce46b10ae85dc37587dec48fe8f7d6b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15872 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 diff --git a/cb-tools/SuperWebSocket/SuperSocket.Facility.pdb b/cb-tools/SuperWebSocket/SuperSocket.Facility.pdb deleted file mode 100644 index 5193d9703c0adaefe65af1d14a7bdfc528691e89..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42496 zcmeI53wT{smB-I*!)-$wZ2F=vAT8;OmNZFUDFV%_p@kMoQ=mYAhUBJ=O_Pv>(h9=G z5iJT@MnK`CBT5~M!tfCJ90e_khyyALA~5O)@)%Sc3gS121?KnPkDGIEZ_=AI3ZdEU z%DQLowO?zmz4qGc>~qeF#=5qa_Lhc@yoxy$vu5S3sIJOeSWsA)bMS=9>VPoF{15c| zo`@oX;1r7n{(b;Bu<PA{a3FJ_Bq75GG8Ycqz7PAaqKJ`!5eR~SaAffAyIJ7Daa|3B z4>AYRjeq{Y_<xXxJgINj78FG;pSj~ynReT^SIu8v`Sg-I#@sUYx~x&7JfYH@>F1+@ zvTKS{qY`NYKl%>`(#?OZhGG4$YxO7<_n%%4w6rS@42*wQoH;P!-#q`+#Saa6Ja=1l zu<Mjt27UR#B`4kRhnIetyKS+(9=83HXXQ3Lo_qgMLGb3#W%sZB_vX@{*MEHZy}Qew zxV-IaH#n0q_`kvT-aYrHn?F`~*`@!{{Mwce9~j*0|Gn(1@n8Mwy2)Fw+O*=eA0Kwd zUr*WfKmDIG2SyM2YW(k-_Qb}>Gp~H*(y70@=<oNeT6th_umAV5uf~7tf46+|V-H-m z`K;S-|LAwFm{8yUIdfq2ps&V1>&Cj8cOx@5lpgV=qwczK)Z+&R_xgV?`)d3z{=qwi zw_ki?%ek*Vf9>zCzw5{SpECzW5Bh5SzcOptmmWR%`6<meZNF{S4Le^rFu2$Md)Zgx z|MlPQ>X`r57r*h&q$4WFl>YhN{?C~MqX&I8{)=CE@{mWqeEFPp*K9c@`1NQ1c3^O? z|M#-5#=mjUukVg6tFBpJHTj1#cTRk;|8wTR>49$KaLgwd49M?)5D*2107HRn;9y`F zFdWDM4go#{$p4oMj0AMUXf!Ye7z>O8#sd?8iNGY_FyL_D2p|ub3`_xz1oDBYz%*bw zFawwg;Cc*Z0keUlfdXI-Py`eMbAfrld|&~v5YRpCMZhtDoRP-@#{tI!i-A($P@oVP z2tjay2ASZoy&CxYLFd3bufFrD=Koqv`1)gNPKvd)ZK*k<rENp|`j*z3>I++AZPhJx z8)6+bn~DnN*HqQkH8wSNY^hn<*3!{Z*V0r|)wnrUzcf}`A8T6`tBW;mid8i>b;Q~V z>e@|-8%&88`4ZbzVtc5>247;UFR?)-t~VvN*%BKPCHAU`6)hJwcT~61?vystn~s*Y zEov$S_q?TbD!APioYYi06)L#rOzpD8ecx19V$a)H9$VMgyrj8)MQz)Ly+*!Q>^xs3 z#+vIlQeqN2d)&sBrpCH0)v>lsv9_A(#wJix<GS@7D?{-;=A_BnZp&=%EVDHfzhCXN zjZ5NWZ{;)8q#E14BzE>zIz!y6Nz>Z>;%J&?_L!Sqff$uk-T2X15;J?;JYSsAN9`1s z#Ka!AF3i2ErrP%PmW5VId)&D25I3ka%Rnoi`_;BSYgTHDWY2}e4ldqy5!YgPJ}=`l zCA@)AT8u#&2P#A9Z?R}Cm(dyu|Hie}vTC4;eA|K7i94Ohok}3AvdH}X8D$=-buScT zyB<hk;_nM1!{O2OZ#;6z>1Y2e;@Ffi3$!ng&98hV7i+g*`LD57*;S3FUbTkbN*C`n zHZi~ljf<y!a`i`U5v|5cv`P!pYCJvd-*!!XOSBqi(JC!WE7|b0pI-j?9io-Yh*oJ~ zTFI8DU32qk5z$KKM60wgtz^{GZoF&O-$g6g6|K_3w31g(+i?Av^`ezLi&kl2TFnDb zd(Vx#Cy90kAX=q`X(iL1_D@g!>=n^U_C>3-Fs<a<(=NJi`JJMb+>2IeVOpXF!U4ts z#E|{;QQE=>FT1{L-|qu^_2?tW6jKL68MWHm56)G-n*q`K^n-)^+|#PJw0Fcdvd2&S zE~sc}YKql$G`2Li7n~4lj<q$`;V<Y+U*6b!0mUT6t&VN(P~0Fq5#>Qv)=w7y@u6>? z`Q)<ure+WN?l<mPtF#qCrP=>y(LS|DdPrD7IBU>C<F8=KMODrM*U#0aGuD4$i1~v* zBKk|;Pxdx-vG(>=u?sGYwRgauebi5RS%7>a;+N`ro!{~;Sl&bopSU!BmVEsn;5)fo z%Mb02XX7==#>=97@>STjM2wGxOXHjIzKX-3A4(i`#pQ>g?eQFH%fY*%GTtCg>Cy=b z)MJA9BOl6gdujfPPk#JIIKC*a4<+>HTn5=BrHuc>5Q)~OhsQrWzJ4snnlbO(Q6d-{ zH?}n6ACfGD;_ZgBytX}tFR0730>1@STYD=fsKUME#Pg%l@o%H!i|WCr6m9VQup3=7 zA4Wqn0(#N<bj_1WuC=~?kGx}GmWnz&$TRZ|my5wFw+&fqY>U<FxAN6M_$W}y^@*W; z%AcAK8C}-g&|-Mt^A4rXWzc>)l$WS(^NNRpp2)l8LwSyObL-oxgrN1s2YhhR$6Y%D zH@8;QcGRtp)%z*dt2&Qyb<SXdE66YD13tg#R2U_G1*>B1E$jhZWrkP1>7$p6t6Y6y zTk&}6D-P8c>(IQ)L~l$UEWP3R==Fx3QxP-2)CIBC>ka1p<<h%TjNUjRp*QZNvsU&Z zMh=w^nqY@vg7KLey@4Ky%RgHTmM?W4SUyvHr*Zj4`5iwIjI77c0yBQ`bHVU5E&)9b zyb%m-T#CCCyct{u-U?m<{yew}d?WY-@Eu_EPW%Vplfe&xSAw4ep9+2kjLgKJ1+NCb z06rc32KWr{o8UFzcfn_Z52a7EEj|T&HaH)A9(W#jE%+F4Ew~JPK6ov-4!i+OAJCV1 zOuo~RnKJM!v~K~v>c!yGfWv{=%)=u0Tj`1~2K4)E@{|M9UA(71I&>|%wZL3p25<&Y z04Tl%u<;u2Ccw^*NMhb-eCdY?_|f9Jz7RYSd=dBvFg!Qo_P?*b6PK_1qg+e3Ujoiy z<a2@(fl0IzjuLXw2sucR>Xa<m`4J(`23TW!_PV;<lyRz|UCOnU?Fj9*Aq%8+O5rAG zwT^8!d02PAqq+W)xt>7zM*@c@txc*+VFti>s4u}bU?;E(cmdc0<Uv>hEcY3R8wh>J z0ga>AYZ?ayKgRlvPKYi<_ze5q`k$Fzv-Z$j@@swYw%21SlrYjF^A~*+UGD3sXp7Z$ zI2SUhnbr055b01~m*iVAYulY(W~Iv{v(jaffAqJJ*~Q>&GfyTXv-t_#nh%X_V<gdb z@njsJfWD-h6$#sGhmAJ2S3QKAfo;HT%Je=8;qgD#wQXpqeTOnOR}4xXe{cUwH;dM% zhwZ<f*#F)>oRVtm;r=u?8$y=educ05UQ+iI-tKwX@lJB$1F{jl+VidpyGOh%C9mGC zpuC=bg0m3^hit_9+KyV4-BTM)W2iiu7p(nmywcki%za~9j5qUOd_w-E>uuX2rkz|S z?MbFWuQ@mktT{2=%!fJ5p-Ny`VqVDqp)dk?kUl>J>;^`|r-AU#=YZyav9qJKhf$EN z;RE;lquYr$N$X-A=gF;3qSpbB@>}$tCaeQK2zx;3Z3XSKeL&8mJ3LQ%u_lVAnvczn z?~|PDhu1{br0Ctgj`VxugjOtAmUN$ES#x7YV{Mb}R}^&qrtz7rrC%`n5#L5{8<e<t z`#H}9Fg%HB4jUGQ_QHAeCkuMf`gF<5O5$$@@`(S9%jfsP$YIi6ICW2}yiz!4fcCh& zQ_zvBJDfMRxfNGatp1dShIZ}2RKBu?X_{jf`DCL@GuNlTT`V2vdE@t=I~{Mr^9KDE zt)^nNFVMVM9&6|*zp$Z!+v%D?nT>_Dp?rOQt(l}rOHjXjfO*lg!u6eu11(7`G9CZq zIvf1H-qjiIBk~%p@pbM~MlH+B>T6qdI6y41F_p0<mbKpS!`qbka^es^WRdyHIu@<- zZCln%mCdzH_yIECms{VV=e?f?TU}jYK8&TVHeXkd>c~qw*IsdTNY;G7E{$&SbrjSY zM{H(1ORhsUKt5pgb*Eoaa_Vhat^K1onI&4S`&QRQ5;{=gE;~YDDzvhTrh{ci&j4p* ziRW<Q%lk7$Yjs(t?Mzn^b_70%xF-J`Q$}SX-#x@x{Sh&`giFe`dqn-FZE@*L+3sq4 zDOh!tf!VjPLl5F5;3_a+{1B_a><!}h7L3h~4<R^->-FFj;3n`X;48t<(x>^RFS$V* z3TiTt4;%#)0uz7*!13mKOmHc9Mq+<$=YM)0Qy)B@nV_|EH<Fms;qhoSv_~Xp?VL+r z?!|`Iwn1ZO=RkTt@>Xai$Mn_skv|QVUnB>Qa(L{lY<Jcd9=mJEqw)V&lh^v6U*}rm zVC6Mp{Nh}izDf2J@L$B4TY-ER+4AfV{C-0B(-c+$v#CseSoOIT*b4jxcmo)YAm#&0 zfwjOVfSZ6<fGo<$14@A9Ks|5?Fc6YCAeMOD_OeR~>o#1P(Tfm9$zxade|&n__Up<1 z@1J%5$FHAXG@Y?$P+gaGzBPKO?`L(a<HA;Z3<7oD%6+St=w!pZ0<MP+`3*e1w_y;w zkPXwBFLV3C>T2;kykE2YO+WuexcN5<oQ<E&&)cPm_C-uvxePSc(g_Napx6A<y(IRB z*hxX0-)3Is1QqCjQ-FEEVTpOEd*TYzY3_ONq`+Ok3o3XZ^h*wC=)4^@&drNip`OU6 zHXMaul2Uwgeh>H<^vdJu!g}Bk<I^$sgbPl_`05M)jrWZV4b8W@mga`Wb=Ei1SNDsx zj%gm*enm_lxeUZJKaVCbZ=^3ZpX4{ye98lBeaj(-=2C8O0eEI&9_=7qf%XS`C~TaY zS0?hG{6~H9`rrHi!h2orH$?AZtu^DHa{pKBwP<~Mc-(uV|5Ifpo&O-y`@!AGnRIJ! zWJKef>i_d|$Dc0?+w90h^!uK7DRU<?+x=U^RG!AbuLqJl%kT8|xNLXXW8^{ptXyVe zpXTu7gkKx-iMQ?Qw9PcW-u|C%@*bOzSDmp^klrsuRx5!TVDoz@x28b@fA*UL;rXxg z3koAGGJhF*iBsD7{{%wgX(d1VJO9(o_wMHLUiS++&y8@j#I6TjojFO>HR0=JYyfS( z#JW{ri*9%8myKV&o_kgGjm_%{R@b&~Xiv(MNP{o7oi<PSEEH|5yRfY-){LL3`@U@4 zSle9Rqq8szGC6xd+eQPD0R{O_tgUL#gWcogVU$t+hD5Ycjk$?3cCxjL<-d;uqV?(W z&nXvC3A@;y2~HgQaP?(!PQv=7B#&yNx8?oW57$)FNYqkvDwTG&ac^6`C;B(FPVF%j zv>y1tB#BP<b)FD&Pa5D9<<yDa3G}AU6TOev4N`b*rv7MwuhVwX9wtBU<k&@y#m)s- zuXbiufBG|X&${|Q>^ls`eu>IvFnmrqGbh_Hk36>l%H!$0?Z$W|WTMTTv(r9GWhJM1 z?s?hFL{`4LN$aK`x7Srapb+7o*I63ILKADAQ#AXbBN1aYY#KNl+b!FjTTmH$04v)O zBj?J>zAM<R9|X!Wd5gfT!{&TV9(W$t@<##j6TtYS;^%;m0e=Kc+u|32j{|=SEPs~# zPi5fi!R6q)z?I<dfvdoefGI2f5*Qmb{xbL^@Vj97vBXIEvFsTT`KXGp0agR&0Oi0u z;*R28|0?j|To;4S2hRrUjLJDKzM8m8f$<6dlbu`XeN|_gI*&}y+Bwm=ch&fMJEJZ{ zt8uWh+-cWnPUiy(>kVxXG#b1xVaqg;W@Rg4<V>{8i(svO5Za)ZETCe|*_ln?$y~$l z_!O|t%*+8_3}!ss*_lQ3w~7AC-#3omI!9At>U;wl<lda21{wL;hQiqa+y^`cybKIS zA>;!~fwjOkU?=b}@O}zR&xHM7?-~}?G4LjO18vs0dH+vldc^8$#?R<$zsGpk)iKOr zA;v!XwLYw4wrOm#e}_34y}NgH7$3dwhhKO6{pzqjET)cM_&U0|-(N`ko&}WG(@XbB z-jQ9YA3|18QJr6yUr6A}>&?z|&b77FZxN-^)a7tmZChKd6771PiOx(OFX=ngYsWtQ zocvGD3TTgUKXWG=ZRYnE%4f$jqI+c~gy-^)$cMiz_=(BaIhR)xXUC}1XT?-9dvF!< zOY&*%{fd0DRh|UPclB#9{;S|AQ{GX`Rh>5zOi#2)`rq=pQ%{8T{|n@mE`Jd`3;Yr| zhm(mp+?CM$_w!!QrYPhA&ob6;0P+vy0VTk4pdQ!?^oNi|BmLYQ;rgcj`;doBKgZyi zq?G-?=CWvgdf5I?XUlhGi+5+^XS&~)ocCr=FZoK{(|dmC9)rTs4(s=1_@Vm_h99AO z-a0$63p&yI^l8jX<)IEYr-Pt9R@+v$-aR$z?m~-B<#kE7Jl4FfV?C?1)e&L3*0$It zcb3}trz<IF4{$f-9p>6OmVA=+q<k&yMmNY#Q=a>1xBS|_a(UDy$wpEh>T)gjRT|v` zO%feX@UEnI(_W@WQd(<mTYIdY4iT^ZXqtrUsN<0U_Bxe1>ZEkd_i(zi6%*&(GuLnS z-hCe9XY<%S&Kfa>FwG+K_ef|KQ7P;&XU8+YXKZz7o~uJTR65Phk52!g__Y*Jz!oz0 ziTs5lxt<J`E}H^A3QSwjtxM5kC!>c>0A!Qe=R|Zr%*t9MA*03QsV80mJsj5<%I?%S z$nKON%I?HZY0h)c2Fvc0&MpP71eb%)09Sy|1y_M%;FG}EU&g*{1)t0{c9fp!i(d*p z4ZID!1}wYvEbzVHv%#zp0cFL12-ZEnAA!&3`p4ipFl&La@zCkU#(N2jZ5RI&7+HzG z3T^@a1x(%XAq>t1;E~`q@Obbhu+~=E6kiBt{fnOrmTgx9z67j$DYP|yG59j@jo{0{ zx<CCd;0M50fS&<>3j8d1EBJZvXTW~|Uk!c>{8{ij;Lm{v!}HIBhk&mIPXu2Fo(6^& z@f>Dn4x_7ktGSFoF5Jq+TRetS-Q&4mKAzFine#D7qQB1N?s_g<%w>l#2JR?5SKKG$ zqO~o^zuXGsk*;&-Gsu4?c!i^nkv0oB577DZ)!=-v>|25C{7SCXmKpq38K(kj&*5B) zR&8Ad*!#+oX*)k6iFp&2>7DdfHutSy<U9B}82Jvq0iFuJ4Xk}e4*L&#PJM!rX}`vb zw+hO8GYaA!;8EZOU=J`tN?{=M2?xUK|C_Ev-N*1DA6<4HgKLH)W&OVsBGLNveXalR z<vOo^cD2sy`78zL79Wt)Xov52Xq}f2KxbA(>(jMPDIYpEvCgNibE+bo-q6T-q}44Q zwJ15|ADogNUgxw%YfmG8tM&|=T)VWcYh1$RN<V9T%Y0q5YnH}YdHk8SOB_#_Ziz4* znsrpBI(vVn%|7qs>+}1m-L5`|Or0~RuQ^;_ug|n4*Xi%s)QIT>?InDeNu8~}&iyzy z=KJSu$z86lLwrSG*7xXTzOEjf8%rKDUq`-#oFLunLn(D!?d#~#xv>wMa(q8z%Pa-# zTu=W@1+;OhGQKnI3y~M|Oa<eJ4YM3wuk~+M!hY#Mkn9|aB<7y%lJ!Qw%=^6Jl81%l zSJz}WsQg7-j{-9n;*-I$U*>=pgXe-v!Slh&2d%--7(0eERx<1Yu<V(QU{=&PHiEHd zJ_S}iSAtg&{{?V0_^aU4!MB3X0N(+Yee*5w+2HSi&jJ4&d>;5$;9BrA;5zVgV3qqk zcpcZTfj<J4JuuR|8BNNg1br&s+wrX7vw$PGYdo8kc{+TP-5~p5Goa_rW>JFlul;S$ zqRoI#w8g-Az+u2d@`+0-U+ZmN!Z&B<a(dsKY@|_Gp&EC_HL&wAz0dEHhSr`>(>~kI zpY&(WWJ3*s<|?q}!qs5;7IUz5yj`m@?7ZmoJ81e80t&LPBnQ`-yxzW2Uz5+DS-J1L z$0=D-UlfqDF3+F&J$?yvGq5#|1y%!1z$bv6z%JkgU=J`Zo3;U!Kn>6V^p~J-`b@~4 z^V+j8M`yN|fFm^i&-@l%;?|lg88lr_=8y1KUHk=(ev7--hE3_?-Ssk8&bNGihcARj zqMPaHzwF`{Ih^D0a}2cTu5slRIeZ-oLGkCf`hUcQu45kaTh|`0aP>@e?V0MzJ<Y}c z)YbP_SKc))?~AUUD_p)89o-Vw{uS=}MMtmwzv^7(%3tB~qhn0`UtRnkTt6Og>04a8 zKjyBladc0+_*-2&Z+F*kyX%7-{W-3@w|#y`_X`*QTi32D9X`(GuXXk0c<lN)*_AWK zwR@wZU*XD)xqiRqt{-r8=QzH^T>hW9a^H68V_f|&y7Jhg8NS@==*PSKxB7UO|7dru z_q<SL@PKc>yDoD1-gfO;<jPy*+IPR>LqPl>@+#;V`P2|0T@HSSQj^WKviWp(&-~-1 zCdDS0bm~nhU(GgcH}yx@#-ytsnsE8HMCA-h^~bc2%2oeffk>sRe3PE0{JrvY9Z{OD zl0kX44`N}uHux&LO7FV5y;C9ql^IA)Q+#U3OO5FES~imQm-&F!6|Ff-0qt$q0+{9I zndDD^w*fZ+I|2F1<V%o^qI0A>fIETvfQJFu=+6SL0B-_%H;H6q5-<-a1@!)*HGupy z^5MwVz828C6CMN}1AYU%3>Y6IGA=*QI6&u}3xQ*SwZLXT=bpC#x-YF~QSJd`Z$1h< z1?&d)0HYbO%9KGH8B8^M7De;s2lHIP*kEQTu5f{FK@`QbPqDPG7}}H@zQp-_Hr?fG z&^^=6I)<mIV|ZX`=d$3Ab+q!Q4iDVMD^#|Pck~!{Qyl$Lcs0C>mrM9|SsItOwlnX2 zCNBnf>PsR*^;#O2_x#R2&4#bT^@0hUguWgd=V;+uxc|e_^naM|{{r31vAkMO-NS<9 zLu0;to94>L$PJgDm8SeGSAG$+o?oSsP53o}db$eUHs~sSAin=|z`W=v<z*~h&a8}B zIV%dyP3elk#sOd-2KDqd-qII_=7`P}4^ELw8|P^2wAZq7e~F>p=fh-4PquYF<e2=x zqi>fj)74eqsblh(_k+{u7#r{CnZJI{Naxyqr##FDKYU%5#^x<zVexryOXS_#iQ)5F z8ke_7`%%l2JQzDT*e6fAf50x(Mq8e%r?^w++-K@Zc6#`>*)*4Dey7fvfn7B?MgQ42 zM~hDLI{hj`>zojw_Skq!KR0E)7?dJ6lCeQfpB0Amzt)^VLHY+bd><^0t)~$E?{%%p zJh%&8YtvlYa4v*(?LleUc97S#$iFXN^XQ;1%C~8bSImQO`H?i`M||HyZ5fRmM{M5{ za%kgRU*~nop==CCyVs#O&nHXc@-o(5hPAFmg7k;md|pfA@-p@XPKIwbdG~!t&6i{8 zT{)~<-u95}M*KSO&bTpriGJI3mk*s4=4W;qer7v<7Kh|c>uV~ljkD#>WnTMoA5BAR z;~XvX*z-^Krm}t8yU@9o#^q&h`g$aXL(;T=h;Kh_^7BkK(vUQI&BoiZi)oiH`%PqG zNXi_xagH`5Yih^PH0>Db+kve4cI+{<d)sg}-jyAaFWJdM(>azY$IFGKad|`bOgxdd zYyE0zTwY|u&rjLdLxc2s&!)LN=o`<owaD|(6rR~QM~i;(e0b2%?ydK1yrrMV_`O$w z?k57GW1boHKJnBvJqyYf+l16KJ?}ZZi!{9>W~fVp9f_d&^j?vC7n7Q%XC1uHT(akb z^641{UtemP-n;1A$+D9O%BOcL`u?S+Jx7}LtZF~h8I*50jq_(gQqy!lbebbiO_R@D zeiQ}YCjC~K@_+wD7lhO_`DcBc$D&g{`D1CS38`uFzxw{Arpd4B`KdHs>|fCNnw`)q zstf$`5PGG5uxR0e%F3dJ=mQ1&Rq*h4!EL<#J2CODqc{<f;E%q!COK@)WL!UldaHi0 zu&iXk!m9bGM-#el$LL^<7)H1X=y^N3czt|OSyf)Pu&}($6ul2#93Ip-!7c@7)&}?X z@q#X2v%j_|s9aFKV9~saicH#YL@>im66v&_wV}(;5zvN;1q&9Imz7s#(uTu=aZU>I z7?VC~L$Ii_qPVQMVu@|RzT|R3Fv-coS~B!e8=PdWEe=YSRF=&xsbHxvp?ldJ8$99Y zGM7y+7hR5tpCyH5#bp%>t61m=-M3?m@gIq9caLSGtM7jx8dVh~6$?w|sRc)Wr$DY^ z6Gl25MoLqB_*oO5t~8xFPf{Q*QE5739@cZ=d^%I^^_`CwR=#8Tqx<QpL1{WC<jd5x z5Bhy6e{`NjAvI0!)$sYk?G>GTg}yDRX*z%8`I4IUAZed7wI%aFW$GOhUDc;|O8Bxo zRx!#~<MPSJb5V9tL~(f>ahe>Ad1y+IT{VMsVmNElY~rtVx{IIKH1?ZL+|AsjYlZG* zYf|F*aMhXFeA{~+vJbWd#Y^TcT2eB%GJ|@@2RErxgf9X;uXnFcGz1G+`YNj`+11$4 zeZR*AyWyY0Z5j95&+YUduwX{bT~J<CQBi@BP3XS<LyZqwblvF|r7MV4x?einTy@Hn zPt!SYFT383^J!lrZJ5iKnx^yIey*gZ={&Zdf2nCYQ|;|AZtWz3+Nt+CdVA{NM2hW$ z&R_d^l$xe@>iT+odzD>f>b<!eoxG%`>D{cWTs^62dWULG7ioHDs&8jF-ymjXj;Slc z7}`<PcenigARi|@+>D6syAKYs@FGN+?L*C-@?o?){l9YLO?pUYAK#&Ee{%iOJ;2X6 z_$u*&)j*HFsT7);`}nd>&%Y?t)l$;q&zblyx9n?;>~X>2-ls8;dt9DxX!;4?<Kn{2 z+e?4#S7~WF>+Fm^C^qR6I6W7eE=Re?P4mOirOmAp1OLr5H-(u&E@;BqFbXB)fBV$; zCBC+xD02DC9jB@<+qYNEUtjt3k~_xSGWNQxQKP=kx<7}P^K3Au?dLStZj7}DbK2YL z=Gc$3o!`V~+-yVx54yI`>D5QS8oK$~o2hI+%C5`c`)vQPIwULmTX?r$MC)zgMoZWa zMb&ikA+x?HzSY##7XFG~L+7XQ{y{bL1>=oWrv2tuUo<-H2fO;BV9Reiwqk=!lhJ^t z_q)Tbp)!Tv-VBn3s(bxdKjtWvOgxVMVPM!#zG9)7V+nF6Z9pA}_U{y?l4M@XT>L#C zQ)?vYBfyJ^_vTL7HN~oWAiQ5Wa3l@O1M2V%X`f!lNL!Bqf`e4(Z2TJHGl49IdIq#Q z_i`JO(*eGU{JW7hoxRw~bt$-ma^(;EHZ*Ja{RUE~edbk29nm_Mt8Yb^CtW0fCji>< zEdi>43ScR)3^);>2qWaOwY5Jz6c`7L2PEiTm?e}FSP8ELT|zB^l~5bs19$>35tsyU zMwI(bcK?zGo(xO@#It;0DliS04yfNVfun#~fckSZPyoyU<lg6&i{1&K_tfj10Q>x2 s@$t|c2XL-AC<V%Za-c86$$eSaKv@GE7~sGF2L?DWz<~h{^a~FBKZ}`#6aWAK diff --git a/cb-tools/SuperWebSocket/SuperSocket.SocketBase.dll b/cb-tools/SuperWebSocket/SuperSocket.SocketBase.dll deleted file mode 100644 index ca403ad0a6dc3de9720cbd6a9c23325d4fe2bb59..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 94720 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 b{}29Wmp}%~Qpn-+>;7kF^Z(_pcnSP}JXIYh diff --git a/cb-tools/SuperWebSocket/SuperSocket.SocketBase.pdb b/cb-tools/SuperWebSocket/SuperSocket.SocketBase.pdb deleted file mode 100644 index f877f7096dba2fafe166f963fdf7a565e6a29349..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 196096 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 Q{auo#cY|I2UrEFN50a)k=>Px# diff --git a/cb-tools/SuperWebSocket/SuperSocket.SocketBase.xml b/cb-tools/SuperWebSocket/SuperSocket.SocketBase.xml deleted file mode 100644 index 61d2d077..00000000 --- a/cb-tools/SuperWebSocket/SuperSocket.SocketBase.xml +++ /dev/null @@ -1,4974 +0,0 @@ -<?xml version="1.0"?> -<doc> - <assembly> - <name>SuperSocket.SocketBase</name> - </assembly> - <members> - <member name="T:SuperSocket.SocketBase.Async"> - <summary> - Async extension class - </summary> - </member> - <member name="M:SuperSocket.SocketBase.Async.AsyncRun(SuperSocket.SocketBase.ILoggerProvider,System.Action)"> - <summary> - Runs the specified task. - </summary> - <param name="logProvider">The log provider.</param> - <param name="task">The task.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.Async.AsyncRun(SuperSocket.SocketBase.ILoggerProvider,System.Action,System.Threading.Tasks.TaskCreationOptions)"> - <summary> - Runs the specified task. - </summary> - <param name="logProvider">The log provider.</param> - <param name="task">The task.</param> - <param name="taskOption">The task option.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.Async.AsyncRun(SuperSocket.SocketBase.ILoggerProvider,System.Action,System.Action{System.Exception})"> - <summary> - Runs the specified task. - </summary> - <param name="logProvider">The log provider.</param> - <param name="task">The task.</param> - <param name="exceptionHandler">The exception handler.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.Async.AsyncRun(SuperSocket.SocketBase.ILoggerProvider,System.Action,System.Threading.Tasks.TaskCreationOptions,System.Action{System.Exception})"> - <summary> - Runs the specified task. - </summary> - <param name="logProvider">The log provider.</param> - <param name="task">The task.</param> - <param name="taskOption">The task option.</param> - <param name="exceptionHandler">The exception handler.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.Async.AsyncRun(SuperSocket.SocketBase.ILoggerProvider,System.Action{System.Object},System.Object)"> - <summary> - Runs the specified task. - </summary> - <param name="logProvider">The log provider.</param> - <param name="task">The task.</param> - <param name="state">The state.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.Async.AsyncRun(SuperSocket.SocketBase.ILoggerProvider,System.Action{System.Object},System.Object,System.Threading.Tasks.TaskCreationOptions)"> - <summary> - Runs the specified task. - </summary> - <param name="logProvider">The log provider.</param> - <param name="task">The task.</param> - <param name="state">The state.</param> - <param name="taskOption">The task option.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.Async.AsyncRun(SuperSocket.SocketBase.ILoggerProvider,System.Action{System.Object},System.Object,System.Action{System.Exception})"> - <summary> - Runs the specified task. - </summary> - <param name="logProvider">The log provider.</param> - <param name="task">The task.</param> - <param name="state">The state.</param> - <param name="exceptionHandler">The exception handler.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.Async.AsyncRun(SuperSocket.SocketBase.ILoggerProvider,System.Action{System.Object},System.Object,System.Threading.Tasks.TaskCreationOptions,System.Action{System.Exception})"> - <summary> - Runs the specified task. - </summary> - <param name="logProvider">The log provider.</param> - <param name="task">The task.</param> - <param name="state">The state.</param> - <param name="taskOption">The task option.</param> - <param name="exceptionHandler">The exception handler.</param> - <returns></returns> - </member> - <member name="T:SuperSocket.SocketBase.CommandExecutingContext"> - <summary> - Command Executing Context - </summary> - </member> - <member name="M:SuperSocket.SocketBase.CommandExecutingContext.#ctor(SuperSocket.SocketBase.IAppSession,SuperSocket.SocketBase.Protocol.IRequestInfo,SuperSocket.SocketBase.Command.ICommand)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.CommandExecutingContext"/> class. - </summary> - <param name="session">The session.</param> - <param name="requestInfo">The request info.</param> - <param name="command">The command.</param> - </member> - <member name="P:SuperSocket.SocketBase.CommandExecutingContext.Session"> - <summary> - Gets the session. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.CommandExecutingContext.RequestInfo"> - <summary> - Gets the request info. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.CommandExecutingContext.CurrentCommand"> - <summary> - Gets the current command. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.CommandExecutingContext.Cancel"> - <summary> - Gets or sets a value indicating whether this command executing is cancelled. - </summary> - <value> - <c>true</c> if cancel; otherwise, <c>false</c>. - </value> - </member> - <member name="T:SuperSocket.SocketBase.CommandFilterAttribute"> - <summary> - Command filter attribute - </summary> - </member> - <member name="M:SuperSocket.SocketBase.CommandFilterAttribute.OnCommandExecuting(SuperSocket.SocketBase.CommandExecutingContext)"> - <summary> - Called when [command executing]. - </summary> - <param name="commandContext">The command context.</param> - </member> - <member name="M:SuperSocket.SocketBase.CommandFilterAttribute.OnCommandExecuted(SuperSocket.SocketBase.CommandExecutingContext)"> - <summary> - Called when [command executed]. - </summary> - <param name="commandContext">The command context.</param> - </member> - <member name="P:SuperSocket.SocketBase.CommandFilterAttribute.Order"> - <summary> - Gets or sets the execution order. - </summary> - <value> - The order. - </value> - </member> - <member name="T:SuperSocket.SocketBase.Command.CommandLoaderBase"> - <summary> - CommandLoader base class - </summary> - </member> - <member name="T:SuperSocket.SocketBase.Command.ICommandLoader"> - <summary> - Command loader's interface - </summary> - </member> - <member name="M:SuperSocket.SocketBase.Command.ICommandLoader.Initialize``1(SuperSocket.SocketBase.Config.IRootConfig,SuperSocket.SocketBase.IAppServer)"> - <summary> - Initializes the command loader - </summary> - <typeparam name="TCommand">The type of the command.</typeparam> - <param name="rootConfig">The root config.</param> - <param name="appServer">The app server.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.Command.ICommandLoader.TryLoadCommands(System.Collections.Generic.IEnumerable{SuperSocket.SocketBase.Command.ICommand}@)"> - <summary> - Tries to load commands. - </summary> - <param name="commands">The commands.</param> - <returns></returns> - </member> - <member name="E:SuperSocket.SocketBase.Command.ICommandLoader.Updated"> - <summary> - Occurs when [updated]. - </summary> - </member> - <member name="E:SuperSocket.SocketBase.Command.ICommandLoader.Error"> - <summary> - Occurs when [error]. - </summary> - </member> - <member name="M:SuperSocket.SocketBase.Command.CommandLoaderBase.Initialize``1(SuperSocket.SocketBase.Config.IRootConfig,SuperSocket.SocketBase.IAppServer)"> - <summary> - Initializes the command loader - </summary> - <typeparam name="TCommand">The type of the command.</typeparam> - <param name="rootConfig">The root config.</param> - <param name="appServer">The app server.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.Command.CommandLoaderBase.TryLoadCommands(System.Collections.Generic.IEnumerable{SuperSocket.SocketBase.Command.ICommand}@)"> - <summary> - Tries to load commands. - </summary> - <param name="commands">The commands.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.Command.CommandLoaderBase.OnUpdated(System.Collections.Generic.IEnumerable{SuperSocket.SocketBase.Command.CommandUpdateInfo{SuperSocket.SocketBase.Command.ICommand}})"> - <summary> - Called when [updated]. - </summary> - <param name="commands">The commands.</param> - </member> - <member name="M:SuperSocket.SocketBase.Command.CommandLoaderBase.OnError(System.String)"> - <summary> - Called when [error]. - </summary> - <param name="message">The message.</param> - </member> - <member name="M:SuperSocket.SocketBase.Command.CommandLoaderBase.OnError(System.Exception)"> - <summary> - Called when [error]. - </summary> - <param name="e">The e.</param> - </member> - <member name="E:SuperSocket.SocketBase.Command.CommandLoaderBase.Updated"> - <summary> - Occurs when [updated]. - </summary> - </member> - <member name="E:SuperSocket.SocketBase.Command.CommandLoaderBase.Error"> - <summary> - Occurs when [error]. - </summary> - </member> - <member name="T:SuperSocket.SocketBase.Command.CommandUpdateEventArgs`1"> - <summary> - CommandUpdateEventArgs - </summary> - <typeparam name="T"></typeparam> - </member> - <member name="M:SuperSocket.SocketBase.Command.CommandUpdateEventArgs`1.#ctor(System.Collections.Generic.IEnumerable{SuperSocket.SocketBase.Command.CommandUpdateInfo{`0}})"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.Command.CommandUpdateEventArgs`1"/> class. - </summary> - <param name="commands">The commands.</param> - </member> - <member name="P:SuperSocket.SocketBase.Command.CommandUpdateEventArgs`1.Commands"> - <summary> - Gets the commands updated. - </summary> - </member> - <member name="T:SuperSocket.SocketBase.Config.CommandAssemblyConfig"> - <summary> - Command assembly config - </summary> - </member> - <member name="T:SuperSocket.SocketBase.Config.ICommandAssemblyConfig"> - <summary> - The basic interface for command assembly config - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.ICommandAssemblyConfig.Assembly"> - <summary> - Gets the assembly name. - </summary> - <value> - The assembly. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Config.CommandAssemblyConfig.Assembly"> - <summary> - Gets or sets the assembly name. - </summary> - <value> - The assembly. - </value> - </member> - <member name="T:SuperSocket.SocketBase.Config.IConfigurationSource"> - <summary> - Configuration source interface - </summary> - </member> - <member name="T:SuperSocket.SocketBase.Config.IRootConfig"> - <summary> - The root configuration interface - </summary> - </member> - <member name="M:SuperSocket.SocketBase.Config.IRootConfig.GetChildConfig``1(System.String)"> - <summary> - Gets the child config. - </summary> - <typeparam name="TConfig">The type of the config.</typeparam> - <param name="childConfigName">Name of the child config.</param> - <returns></returns> - </member> - <member name="P:SuperSocket.SocketBase.Config.IRootConfig.MaxWorkingThreads"> - <summary> - Gets the max working threads. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.IRootConfig.MinWorkingThreads"> - <summary> - Gets the min working threads. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.IRootConfig.MaxCompletionPortThreads"> - <summary> - Gets the max completion port threads. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.IRootConfig.MinCompletionPortThreads"> - <summary> - Gets the min completion port threads. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.IRootConfig.DisablePerformanceDataCollector"> - <summary> - Gets a value indicating whether [disable performance data collector]. - </summary> - <value> - <c>true</c> if [disable performance data collector]; otherwise, <c>false</c>. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Config.IRootConfig.PerformanceDataCollectInterval"> - <summary> - Gets the performance data collect interval, in seconds. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.IRootConfig.LogFactory"> - <summary> - Gets the log factory name. - </summary> - <value> - The log factory. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Config.IRootConfig.Isolation"> - <summary> - Gets the isolation mode. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.IRootConfig.OptionElements"> - <summary> - Gets the option elements. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.IConfigurationSource.Servers"> - <summary> - Gets the servers definitions. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.IConfigurationSource.ServerTypes"> - <summary> - Gets the appServer types definition. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.IConfigurationSource.ConnectionFilters"> - <summary> - Gets the connection filters definition. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.IConfigurationSource.LogFactories"> - <summary> - Gets the log factories definition. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.IConfigurationSource.ReceiveFilterFactories"> - <summary> - Gets the Receive filter factories definition. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.IConfigurationSource.CommandLoaders"> - <summary> - Gets the command loaders definition. - </summary> - </member> - <member name="T:SuperSocket.SocketBase.Config.ITypeProvider"> - <summary> - TypeProvider's interface - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.ITypeProvider.Name"> - <summary> - Gets the name. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.ITypeProvider.Type"> - <summary> - Gets the type. - </summary> - </member> - <member name="T:SuperSocket.SocketBase.Config.ConfigurationSource"> - <summary> - Poco configuration source - </summary> - </member> - <member name="T:SuperSocket.SocketBase.Config.RootConfig"> - <summary> - Root configuration model - </summary> - </member> - <member name="M:SuperSocket.SocketBase.Config.RootConfig.#ctor(SuperSocket.SocketBase.Config.IRootConfig)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.Config.RootConfig"/> class. - </summary> - <param name="rootConfig">The root config.</param> - </member> - <member name="M:SuperSocket.SocketBase.Config.RootConfig.#ctor"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.Config.RootConfig"/> class. - </summary> - </member> - <member name="M:SuperSocket.SocketBase.Config.RootConfig.GetChildConfig``1(System.String)"> - <summary> - Gets the child config. - </summary> - <typeparam name="TConfig">The type of the config.</typeparam> - <param name="childConfigName">Name of the child config.</param> - <returns></returns> - </member> - <member name="P:SuperSocket.SocketBase.Config.RootConfig.MaxWorkingThreads"> - <summary> - Gets/Sets the max working threads. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.RootConfig.MinWorkingThreads"> - <summary> - Gets/sets the min working threads. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.RootConfig.MaxCompletionPortThreads"> - <summary> - Gets/sets the max completion port threads. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.RootConfig.MinCompletionPortThreads"> - <summary> - Gets/sets the min completion port threads. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.RootConfig.PerformanceDataCollectInterval"> - <summary> - Gets/sets the performance data collect interval, in seconds. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.RootConfig.DisablePerformanceDataCollector"> - <summary> - Gets/sets a value indicating whether [disable performance data collector]. - </summary> - <value> - <c>true</c> if [disable performance data collector]; otherwise, <c>false</c>. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Config.RootConfig.Isolation"> - <summary> - Gets/sets the isolation mode. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.RootConfig.LogFactory"> - <summary> - Gets/sets the log factory name. - </summary> - <value> - The log factory. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Config.RootConfig.OptionElements"> - <summary> - Gets/sets the option elements. - </summary> - </member> - <member name="M:SuperSocket.SocketBase.Config.ConfigurationSource.#ctor"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.Config.ConfigurationSource"/> class. - </summary> - </member> - <member name="M:SuperSocket.SocketBase.Config.ConfigurationSource.#ctor(SuperSocket.SocketBase.Config.IConfigurationSource)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.Config.ConfigurationSource"/> class. - </summary> - <param name="source">The source.</param> - </member> - <member name="P:SuperSocket.SocketBase.Config.ConfigurationSource.Servers"> - <summary> - Gets the servers definitions. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.ConfigurationSource.ServerTypes"> - <summary> - Gets/sets the server types definition. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.ConfigurationSource.ConnectionFilters"> - <summary> - Gets/sets the connection filters definition. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.ConfigurationSource.LogFactories"> - <summary> - Gets/sets the log factories definition. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.ConfigurationSource.ReceiveFilterFactories"> - <summary> - Gets/sets the Receive filter factories definition. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.ConfigurationSource.CommandLoaders"> - <summary> - Gets/sets the command loaders definition. - </summary> - </member> - <member name="T:SuperSocket.SocketBase.Config.TypeProvider"> - <summary> - Type provider configuration - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.TypeProvider.Name"> - <summary> - Gets the name. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.TypeProvider.Type"> - <summary> - Gets the type. - </summary> - </member> - <member name="T:SuperSocket.SocketBase.Config.TypeProviderCollection"> - <summary> - Type provider colletion configuration - </summary> - </member> - <member name="M:SuperSocket.SocketBase.Config.TypeProviderCollection.CreateNewElement"> - <summary> - When overridden in a derived class, creates a new <see cref="T:System.Configuration.ConfigurationElement"/>. - </summary> - <returns> - A new <see cref="T:System.Configuration.ConfigurationElement"/>. - </returns> - </member> - <member name="M:SuperSocket.SocketBase.Config.TypeProviderCollection.GetElementKey(System.Configuration.ConfigurationElement)"> - <summary> - Gets the element key for a specified configuration element when overridden in a derived class. - </summary> - <param name="element">The <see cref="T:System.Configuration.ConfigurationElement"/> to return the key for.</param> - <returns> - An <see cref="T:System.Object"/> that acts as the key for the specified <see cref="T:System.Configuration.ConfigurationElement"/>. - </returns> - </member> - <member name="M:SuperSocket.SocketBase.Config.TypeProviderCollection.GetEnumerator"> - <summary> - Returns an enumerator that iterates through the collection. - </summary> - <returns> - A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection. - </returns> - </member> - <member name="T:SuperSocket.SocketBase.Config.TypeProviderConfig"> - <summary> - TypeProviderConfig - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.TypeProviderConfig.Name"> - <summary> - Gets the name. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.TypeProviderConfig.Type"> - <summary> - Gets the type. - </summary> - </member> - <member name="T:SuperSocket.SocketBase.DisplayAttribute"> - <summary> - Display attribute - </summary> - </member> - <member name="M:SuperSocket.SocketBase.DisplayAttribute.#ctor"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.DisplayAttribute"/> class. - </summary> - </member> - <member name="M:SuperSocket.SocketBase.DisplayAttribute.#ctor(System.String)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.DisplayAttribute"/> class. - </summary> - <param name="name">The name.</param> - </member> - <member name="P:SuperSocket.SocketBase.DisplayAttribute.Name"> - <summary> - Gets the name. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.DisplayAttribute.ShortName"> - <summary> - Gets or sets the short name. - </summary> - <value> - The short name. - </value> - </member> - <member name="P:SuperSocket.SocketBase.DisplayAttribute.Format"> - <summary> - Gets or sets the format. - </summary> - <value> - The format. - </value> - </member> - <member name="P:SuperSocket.SocketBase.DisplayAttribute.Order"> - <summary> - Gets or sets the order. - </summary> - <value> - The order. - </value> - </member> - <member name="P:SuperSocket.SocketBase.DisplayAttribute.OutputInPerfLog"> - <summary> - Gets or sets a value indicating whether [output in perf log]. - </summary> - <value> - <c>true</c> if [output in perf log]; otherwise, <c>false</c>. - </value> - </member> - <member name="T:SuperSocket.SocketBase.Extensions"> - <summary> - Extensions class for SocketBase project - </summary> - </member> - <member name="M:SuperSocket.SocketBase.Extensions.GetServerByName(SuperSocket.SocketBase.IBootstrap,System.String)"> - <summary> - Gets the app server instance in the bootstrap by name, ignore case - </summary> - <param name="bootstrap">The bootstrap.</param> - <param name="name">The name of the appserver instance.</param> - <returns></returns> - <exception cref="T:System.ArgumentNullException"></exception> - </member> - <member name="T:SuperSocket.SocketBase.StartResult"> - <summary> - The bootstrap start result - </summary> - </member> - <member name="F:SuperSocket.SocketBase.StartResult.None"> - <summary> - No appserver has been set in the bootstrap, so nothing was started - </summary> - </member> - <member name="F:SuperSocket.SocketBase.StartResult.Success"> - <summary> - All appserver instances were started successfully - </summary> - </member> - <member name="F:SuperSocket.SocketBase.StartResult.PartialSuccess"> - <summary> - Some appserver instances were started successfully, but some of them failed - </summary> - </member> - <member name="F:SuperSocket.SocketBase.StartResult.Failed"> - <summary> - All appserver instances failed to start - </summary> - </member> - <member name="T:SuperSocket.SocketBase.IBootstrap"> - <summary> - SuperSocket bootstrap - </summary> - </member> - <member name="M:SuperSocket.SocketBase.IBootstrap.Initialize"> - <summary> - Initializes the bootstrap with the configuration - </summary> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.IBootstrap.Initialize(System.Collections.Generic.IDictionary{System.String,System.Net.IPEndPoint})"> - <summary> - Initializes the bootstrap with a listen endpoint replacement dictionary - </summary> - <param name="listenEndPointReplacement">The listen end point replacement.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.IBootstrap.Initialize(System.Func{SuperSocket.SocketBase.Config.IServerConfig,SuperSocket.SocketBase.Config.IServerConfig})"> - <summary> - Initializes the bootstrap with the configuration - </summary> - <param name="serverConfigResolver">The server config resolver.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.IBootstrap.Initialize(SuperSocket.SocketBase.Logging.ILogFactory)"> - <summary> - Initializes the bootstrap with the configuration - </summary> - <param name="logFactory">The log factory.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.IBootstrap.Initialize(System.Func{SuperSocket.SocketBase.Config.IServerConfig,SuperSocket.SocketBase.Config.IServerConfig},SuperSocket.SocketBase.Logging.ILogFactory)"> - <summary> - Initializes the bootstrap with the configuration - </summary> - <param name="serverConfigResolver">The server config resolver.</param> - <param name="logFactory">The log factory.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.IBootstrap.Start"> - <summary> - Starts this bootstrap. - </summary> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.IBootstrap.Stop"> - <summary> - Stops this bootstrap. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.IBootstrap.AppServers"> - <summary> - Gets all the app servers running in this bootstrap - </summary> - </member> - <member name="P:SuperSocket.SocketBase.IBootstrap.Config"> - <summary> - Gets the config. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.IBootstrap.StartupConfigFile"> - <summary> - Gets the startup config file. - </summary> - </member> - <member name="T:SuperSocket.SocketBase.ILoggerProvider"> - <summary> - The interface for who provides logger - </summary> - </member> - <member name="P:SuperSocket.SocketBase.ILoggerProvider.Logger"> - <summary> - Gets the logger assosiated with this object. - </summary> - </member> - <member name="T:SuperSocket.SocketBase.IsolationMode"> - <summary> - AppServer instance running isolation mode - </summary> - </member> - <member name="F:SuperSocket.SocketBase.IsolationMode.None"> - <summary> - No isolation - </summary> - </member> - <member name="F:SuperSocket.SocketBase.IsolationMode.AppDomain"> - <summary> - Isolation by AppDomain - </summary> - </member> - <member name="T:SuperSocket.SocketBase.IWorkItem"> - <summary> - An item can be started and stopped - </summary> - </member> - <member name="M:SuperSocket.SocketBase.IWorkItem.Setup(SuperSocket.SocketBase.IBootstrap,SuperSocket.SocketBase.Config.IServerConfig,SuperSocket.SocketBase.Provider.ProviderFactoryInfo[])"> - <summary> - Setups with the specified root config. - </summary> - <param name="bootstrap">The bootstrap.</param> - <param name="config">The socket server instance config.</param> - <param name="factories">The factories.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.IWorkItem.Start"> - <summary> - Starts this server instance. - </summary> - <returns>return true if start successfull, else false</returns> - </member> - <member name="M:SuperSocket.SocketBase.IWorkItem.Stop"> - <summary> - Stops this server instance. - </summary> - </member> - <member name="M:SuperSocket.SocketBase.IWorkItem.CollectServerSummary(SuperSocket.SocketBase.NodeSummary)"> - <summary> - Collects the server summary. - </summary> - <param name="nodeSummary">The node summary.</param> - <returns></returns> - </member> - <member name="P:SuperSocket.SocketBase.IWorkItem.Name"> - <summary> - Gets the name. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.IWorkItem.State"> - <summary> - Gets the current state of the work item. - </summary> - <value> - The state. - </value> - </member> - <member name="P:SuperSocket.SocketBase.IWorkItem.SessionCount"> - <summary> - Gets the total session count. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.IWorkItem.Summary"> - <summary> - Gets the state of the server. - </summary> - <value> - The state of the server. - </value> - </member> - <member name="T:SuperSocket.SocketBase.Logging.ConsoleLog"> - <summary> - Console Log - </summary> - </member> - <member name="T:SuperSocket.SocketBase.Logging.ILog"> - <summary> - Log interface - </summary> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ILog.Debug(System.Object)"> - <summary> - Logs the debug message. - </summary> - <param name="message">The message.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ILog.Debug(System.Object,System.Exception)"> - <summary> - Logs the debug message. - </summary> - <param name="message">The message.</param> - <param name="exception">The exception.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ILog.DebugFormat(System.String,System.Object)"> - <summary> - Logs the debug message. - </summary> - <param name="format">The format.</param> - <param name="arg0">The arg0.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ILog.DebugFormat(System.String,System.Object[])"> - <summary> - Logs the debug message. - </summary> - <param name="format">The format.</param> - <param name="args">The args.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ILog.DebugFormat(System.IFormatProvider,System.String,System.Object[])"> - <summary> - Logs the debug message. - </summary> - <param name="provider">The provider.</param> - <param name="format">The format.</param> - <param name="args">The args.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ILog.DebugFormat(System.String,System.Object,System.Object)"> - <summary> - Logs the debug message. - </summary> - <param name="format">The format.</param> - <param name="arg0">The arg0.</param> - <param name="arg1">The arg1.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ILog.DebugFormat(System.String,System.Object,System.Object,System.Object)"> - <summary> - Logs the debug message. - </summary> - <param name="format">The format.</param> - <param name="arg0">The arg0.</param> - <param name="arg1">The arg1.</param> - <param name="arg2">The arg2.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ILog.Error(System.Object)"> - <summary> - Logs the error message. - </summary> - <param name="message">The message.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ILog.Error(System.Object,System.Exception)"> - <summary> - Logs the error message. - </summary> - <param name="message">The message.</param> - <param name="exception">The exception.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ILog.ErrorFormat(System.String,System.Object)"> - <summary> - Logs the error message. - </summary> - <param name="format">The format.</param> - <param name="arg0">The arg0.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ILog.ErrorFormat(System.String,System.Object[])"> - <summary> - Logs the error message. - </summary> - <param name="format">The format.</param> - <param name="args">The args.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ILog.ErrorFormat(System.IFormatProvider,System.String,System.Object[])"> - <summary> - Logs the error message. - </summary> - <param name="provider">The provider.</param> - <param name="format">The format.</param> - <param name="args">The args.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ILog.ErrorFormat(System.String,System.Object,System.Object)"> - <summary> - Logs the error message. - </summary> - <param name="format">The format.</param> - <param name="arg0">The arg0.</param> - <param name="arg1">The arg1.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ILog.ErrorFormat(System.String,System.Object,System.Object,System.Object)"> - <summary> - Logs the error message. - </summary> - <param name="format">The format.</param> - <param name="arg0">The arg0.</param> - <param name="arg1">The arg1.</param> - <param name="arg2">The arg2.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ILog.Fatal(System.Object)"> - <summary> - Logs the fatal error message. - </summary> - <param name="message">The message.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ILog.Fatal(System.Object,System.Exception)"> - <summary> - Logs the fatal error message. - </summary> - <param name="message">The message.</param> - <param name="exception">The exception.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ILog.FatalFormat(System.String,System.Object)"> - <summary> - Logs the fatal error message. - </summary> - <param name="format">The format.</param> - <param name="arg0">The arg0.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ILog.FatalFormat(System.String,System.Object[])"> - <summary> - Logs the fatal error message. - </summary> - <param name="format">The format.</param> - <param name="args">The args.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ILog.FatalFormat(System.IFormatProvider,System.String,System.Object[])"> - <summary> - Logs the fatal error message. - </summary> - <param name="provider">The provider.</param> - <param name="format">The format.</param> - <param name="args">The args.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ILog.FatalFormat(System.String,System.Object,System.Object)"> - <summary> - Logs the fatal error message. - </summary> - <param name="format">The format.</param> - <param name="arg0">The arg0.</param> - <param name="arg1">The arg1.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ILog.FatalFormat(System.String,System.Object,System.Object,System.Object)"> - <summary> - Logs the fatal error message. - </summary> - <param name="format">The format.</param> - <param name="arg0">The arg0.</param> - <param name="arg1">The arg1.</param> - <param name="arg2">The arg2.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ILog.Info(System.Object)"> - <summary> - Logs the info message. - </summary> - <param name="message">The message.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ILog.Info(System.Object,System.Exception)"> - <summary> - Logs the info message. - </summary> - <param name="message">The message.</param> - <param name="exception">The exception.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ILog.InfoFormat(System.String,System.Object)"> - <summary> - Logs the info message. - </summary> - <param name="format">The format.</param> - <param name="arg0">The arg0.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ILog.InfoFormat(System.String,System.Object[])"> - <summary> - Logs the info message. - </summary> - <param name="format">The format.</param> - <param name="args">The args.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ILog.InfoFormat(System.IFormatProvider,System.String,System.Object[])"> - <summary> - Logs the info message. - </summary> - <param name="provider">The provider.</param> - <param name="format">The format.</param> - <param name="args">The args.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ILog.InfoFormat(System.String,System.Object,System.Object)"> - <summary> - Logs the info message. - </summary> - <param name="format">The format.</param> - <param name="arg0">The arg0.</param> - <param name="arg1">The arg1.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ILog.InfoFormat(System.String,System.Object,System.Object,System.Object)"> - <summary> - Logs the info message. - </summary> - <param name="format">The format.</param> - <param name="arg0">The arg0.</param> - <param name="arg1">The arg1.</param> - <param name="arg2">The arg2.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ILog.Warn(System.Object)"> - <summary> - Logs the warning message. - </summary> - <param name="message">The message.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ILog.Warn(System.Object,System.Exception)"> - <summary> - Logs the warning message. - </summary> - <param name="message">The message.</param> - <param name="exception">The exception.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ILog.WarnFormat(System.String,System.Object)"> - <summary> - Logs the warning message. - </summary> - <param name="format">The format.</param> - <param name="arg0">The arg0.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ILog.WarnFormat(System.String,System.Object[])"> - <summary> - Logs the warning message. - </summary> - <param name="format">The format.</param> - <param name="args">The args.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ILog.WarnFormat(System.IFormatProvider,System.String,System.Object[])"> - <summary> - Logs the warning message. - </summary> - <param name="provider">The provider.</param> - <param name="format">The format.</param> - <param name="args">The args.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ILog.WarnFormat(System.String,System.Object,System.Object)"> - <summary> - Logs the warning message. - </summary> - <param name="format">The format.</param> - <param name="arg0">The arg0.</param> - <param name="arg1">The arg1.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ILog.WarnFormat(System.String,System.Object,System.Object,System.Object)"> - <summary> - Logs the warning message. - </summary> - <param name="format">The format.</param> - <param name="arg0">The arg0.</param> - <param name="arg1">The arg1.</param> - <param name="arg2">The arg2.</param> - </member> - <member name="P:SuperSocket.SocketBase.Logging.ILog.IsDebugEnabled"> - <summary> - Gets a value indicating whether this instance is debug enabled. - </summary> - <value> - <c>true</c> if this instance is debug enabled; otherwise, <c>false</c>. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Logging.ILog.IsErrorEnabled"> - <summary> - Gets a value indicating whether this instance is error enabled. - </summary> - <value> - <c>true</c> if this instance is error enabled; otherwise, <c>false</c>. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Logging.ILog.IsFatalEnabled"> - <summary> - Gets a value indicating whether this instance is fatal enabled. - </summary> - <value> - <c>true</c> if this instance is fatal enabled; otherwise, <c>false</c>. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Logging.ILog.IsInfoEnabled"> - <summary> - Gets a value indicating whether this instance is info enabled. - </summary> - <value> - <c>true</c> if this instance is info enabled; otherwise, <c>false</c>. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Logging.ILog.IsWarnEnabled"> - <summary> - Gets a value indicating whether this instance is warn enabled. - </summary> - <value> - <c>true</c> if this instance is warn enabled; otherwise, <c>false</c>. - </value> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ConsoleLog.#ctor(System.String)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.Logging.ConsoleLog"/> class. - </summary> - <param name="name">The name.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ConsoleLog.Debug(System.Object)"> - <summary> - Logs the debug message. - </summary> - <param name="message">The message.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ConsoleLog.Debug(System.Object,System.Exception)"> - <summary> - Logs the debug message. - </summary> - <param name="message">The message.</param> - <param name="exception">The exception.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ConsoleLog.DebugFormat(System.String,System.Object)"> - <summary> - Logs the debug message. - </summary> - <param name="format">The format.</param> - <param name="arg0">The arg0.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ConsoleLog.DebugFormat(System.String,System.Object[])"> - <summary> - Logs the debug message. - </summary> - <param name="format">The format.</param> - <param name="args">The args.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ConsoleLog.DebugFormat(System.IFormatProvider,System.String,System.Object[])"> - <summary> - Logs the debug message. - </summary> - <param name="provider">The provider.</param> - <param name="format">The format.</param> - <param name="args">The args.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ConsoleLog.DebugFormat(System.String,System.Object,System.Object)"> - <summary> - Logs the debug message. - </summary> - <param name="format">The format.</param> - <param name="arg0">The arg0.</param> - <param name="arg1">The arg1.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ConsoleLog.DebugFormat(System.String,System.Object,System.Object,System.Object)"> - <summary> - Logs the debug message. - </summary> - <param name="format">The format.</param> - <param name="arg0">The arg0.</param> - <param name="arg1">The arg1.</param> - <param name="arg2">The arg2.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ConsoleLog.Error(System.Object)"> - <summary> - Logs the error message. - </summary> - <param name="message">The message.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ConsoleLog.Error(System.Object,System.Exception)"> - <summary> - Logs the error message. - </summary> - <param name="message">The message.</param> - <param name="exception">The exception.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ConsoleLog.ErrorFormat(System.String,System.Object)"> - <summary> - Logs the error message. - </summary> - <param name="format">The format.</param> - <param name="arg0">The arg0.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ConsoleLog.ErrorFormat(System.String,System.Object[])"> - <summary> - Logs the error message. - </summary> - <param name="format">The format.</param> - <param name="args">The args.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ConsoleLog.ErrorFormat(System.IFormatProvider,System.String,System.Object[])"> - <summary> - Logs the error message. - </summary> - <param name="provider">The provider.</param> - <param name="format">The format.</param> - <param name="args">The args.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ConsoleLog.ErrorFormat(System.String,System.Object,System.Object)"> - <summary> - Logs the error message. - </summary> - <param name="format">The format.</param> - <param name="arg0">The arg0.</param> - <param name="arg1">The arg1.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ConsoleLog.ErrorFormat(System.String,System.Object,System.Object,System.Object)"> - <summary> - Logs the error message. - </summary> - <param name="format">The format.</param> - <param name="arg0">The arg0.</param> - <param name="arg1">The arg1.</param> - <param name="arg2">The arg2.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ConsoleLog.Fatal(System.Object)"> - <summary> - Logs the fatal error message. - </summary> - <param name="message">The message.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ConsoleLog.Fatal(System.Object,System.Exception)"> - <summary> - Logs the fatal error message. - </summary> - <param name="message">The message.</param> - <param name="exception">The exception.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ConsoleLog.FatalFormat(System.String,System.Object)"> - <summary> - Logs the fatal error message. - </summary> - <param name="format">The format.</param> - <param name="arg0">The arg0.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ConsoleLog.FatalFormat(System.String,System.Object[])"> - <summary> - Logs the fatal error message. - </summary> - <param name="format">The format.</param> - <param name="args">The args.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ConsoleLog.FatalFormat(System.IFormatProvider,System.String,System.Object[])"> - <summary> - Logs the fatal error message. - </summary> - <param name="provider">The provider.</param> - <param name="format">The format.</param> - <param name="args">The args.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ConsoleLog.FatalFormat(System.String,System.Object,System.Object)"> - <summary> - Logs the fatal error message. - </summary> - <param name="format">The format.</param> - <param name="arg0">The arg0.</param> - <param name="arg1">The arg1.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ConsoleLog.FatalFormat(System.String,System.Object,System.Object,System.Object)"> - <summary> - Logs the fatal error message. - </summary> - <param name="format">The format.</param> - <param name="arg0">The arg0.</param> - <param name="arg1">The arg1.</param> - <param name="arg2">The arg2.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ConsoleLog.Info(System.Object)"> - <summary> - Logs the info message. - </summary> - <param name="message">The message.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ConsoleLog.Info(System.Object,System.Exception)"> - <summary> - Logs the info message. - </summary> - <param name="message">The message.</param> - <param name="exception">The exception.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ConsoleLog.InfoFormat(System.String,System.Object)"> - <summary> - Logs the info message. - </summary> - <param name="format">The format.</param> - <param name="arg0">The arg0.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ConsoleLog.InfoFormat(System.String,System.Object[])"> - <summary> - Logs the info message. - </summary> - <param name="format">The format.</param> - <param name="args">The args.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ConsoleLog.InfoFormat(System.IFormatProvider,System.String,System.Object[])"> - <summary> - Logs the info message. - </summary> - <param name="provider">The provider.</param> - <param name="format">The format.</param> - <param name="args">The args.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ConsoleLog.InfoFormat(System.String,System.Object,System.Object)"> - <summary> - Logs the info message. - </summary> - <param name="format">The format.</param> - <param name="arg0">The arg0.</param> - <param name="arg1">The arg1.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ConsoleLog.InfoFormat(System.String,System.Object,System.Object,System.Object)"> - <summary> - Logs the info message. - </summary> - <param name="format">The format.</param> - <param name="arg0">The arg0.</param> - <param name="arg1">The arg1.</param> - <param name="arg2">The arg2.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ConsoleLog.Warn(System.Object)"> - <summary> - Logs the warning message. - </summary> - <param name="message">The message.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ConsoleLog.Warn(System.Object,System.Exception)"> - <summary> - Logs the warning message. - </summary> - <param name="message">The message.</param> - <param name="exception">The exception.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ConsoleLog.WarnFormat(System.String,System.Object)"> - <summary> - Logs the warning message. - </summary> - <param name="format">The format.</param> - <param name="arg0">The arg0.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ConsoleLog.WarnFormat(System.String,System.Object[])"> - <summary> - Logs the warning message. - </summary> - <param name="format">The format.</param> - <param name="args">The args.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ConsoleLog.WarnFormat(System.IFormatProvider,System.String,System.Object[])"> - <summary> - Logs the warning message. - </summary> - <param name="provider">The provider.</param> - <param name="format">The format.</param> - <param name="args">The args.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ConsoleLog.WarnFormat(System.String,System.Object,System.Object)"> - <summary> - Logs the warning message. - </summary> - <param name="format">The format.</param> - <param name="arg0">The arg0.</param> - <param name="arg1">The arg1.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ConsoleLog.WarnFormat(System.String,System.Object,System.Object,System.Object)"> - <summary> - Logs the warning message. - </summary> - <param name="format">The format.</param> - <param name="arg0">The arg0.</param> - <param name="arg1">The arg1.</param> - <param name="arg2">The arg2.</param> - </member> - <member name="P:SuperSocket.SocketBase.Logging.ConsoleLog.IsDebugEnabled"> - <summary> - Gets a value indicating whether this instance is debug enabled. - </summary> - <value> - <c>true</c> if this instance is debug enabled; otherwise, <c>false</c>. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Logging.ConsoleLog.IsErrorEnabled"> - <summary> - Gets a value indicating whether this instance is error enabled. - </summary> - <value> - <c>true</c> if this instance is error enabled; otherwise, <c>false</c>. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Logging.ConsoleLog.IsFatalEnabled"> - <summary> - Gets a value indicating whether this instance is fatal enabled. - </summary> - <value> - <c>true</c> if this instance is fatal enabled; otherwise, <c>false</c>. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Logging.ConsoleLog.IsInfoEnabled"> - <summary> - Gets a value indicating whether this instance is info enabled. - </summary> - <value> - <c>true</c> if this instance is info enabled; otherwise, <c>false</c>. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Logging.ConsoleLog.IsWarnEnabled"> - <summary> - Gets a value indicating whether this instance is warn enabled. - </summary> - <value> - <c>true</c> if this instance is warn enabled; otherwise, <c>false</c>. - </value> - </member> - <member name="T:SuperSocket.SocketBase.Logging.ConsoleLogFactory"> - <summary> - Console log factory - </summary> - </member> - <member name="T:SuperSocket.SocketBase.Logging.ILogFactory"> - <summary> - LogFactory Interface - </summary> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ILogFactory.GetLog(System.String)"> - <summary> - Gets the log by name. - </summary> - <param name="name">The name.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.Logging.ConsoleLogFactory.GetLog(System.String)"> - <summary> - Gets the log by name. - </summary> - <param name="name">The name.</param> - <returns></returns> - </member> - <member name="T:SuperSocket.SocketBase.Logging.Log4NetLog"> - <summary> - Log4NetLog - </summary> - </member> - <member name="M:SuperSocket.SocketBase.Logging.Log4NetLog.#ctor(log4net.ILog)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.Logging.Log4NetLog"/> class. - </summary> - <param name="log">The log.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.Log4NetLog.Debug(System.Object)"> - <summary> - Logs the debug message. - </summary> - <param name="message">The message.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.Log4NetLog.Debug(System.Object,System.Exception)"> - <summary> - Logs the debug message. - </summary> - <param name="message">The message.</param> - <param name="exception">The exception.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.Log4NetLog.DebugFormat(System.String,System.Object)"> - <summary> - Logs the debug message. - </summary> - <param name="format">The format.</param> - <param name="arg0">The arg0.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.Log4NetLog.DebugFormat(System.String,System.Object[])"> - <summary> - Logs the debug message. - </summary> - <param name="format">The format.</param> - <param name="args">The args.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.Log4NetLog.DebugFormat(System.IFormatProvider,System.String,System.Object[])"> - <summary> - Logs the debug message. - </summary> - <param name="provider">The provider.</param> - <param name="format">The format.</param> - <param name="args">The args.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.Log4NetLog.DebugFormat(System.String,System.Object,System.Object)"> - <summary> - Logs the debug message. - </summary> - <param name="format">The format.</param> - <param name="arg0">The arg0.</param> - <param name="arg1">The arg1.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.Log4NetLog.DebugFormat(System.String,System.Object,System.Object,System.Object)"> - <summary> - Logs the debug message. - </summary> - <param name="format">The format.</param> - <param name="arg0">The arg0.</param> - <param name="arg1">The arg1.</param> - <param name="arg2">The arg2.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.Log4NetLog.Error(System.Object)"> - <summary> - Logs the error message. - </summary> - <param name="message">The message.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.Log4NetLog.Error(System.Object,System.Exception)"> - <summary> - Logs the error message. - </summary> - <param name="message">The message.</param> - <param name="exception">The exception.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.Log4NetLog.ErrorFormat(System.String,System.Object)"> - <summary> - Logs the error message. - </summary> - <param name="format">The format.</param> - <param name="arg0">The arg0.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.Log4NetLog.ErrorFormat(System.String,System.Object[])"> - <summary> - Logs the error message. - </summary> - <param name="format">The format.</param> - <param name="args">The args.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.Log4NetLog.ErrorFormat(System.IFormatProvider,System.String,System.Object[])"> - <summary> - Logs the error message. - </summary> - <param name="provider">The provider.</param> - <param name="format">The format.</param> - <param name="args">The args.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.Log4NetLog.ErrorFormat(System.String,System.Object,System.Object)"> - <summary> - Logs the error message. - </summary> - <param name="format">The format.</param> - <param name="arg0">The arg0.</param> - <param name="arg1">The arg1.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.Log4NetLog.ErrorFormat(System.String,System.Object,System.Object,System.Object)"> - <summary> - Logs the error message. - </summary> - <param name="format">The format.</param> - <param name="arg0">The arg0.</param> - <param name="arg1">The arg1.</param> - <param name="arg2">The arg2.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.Log4NetLog.Fatal(System.Object)"> - <summary> - Logs the fatal error message. - </summary> - <param name="message">The message.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.Log4NetLog.Fatal(System.Object,System.Exception)"> - <summary> - Logs the fatal error message. - </summary> - <param name="message">The message.</param> - <param name="exception">The exception.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.Log4NetLog.FatalFormat(System.String,System.Object)"> - <summary> - Logs the fatal error message. - </summary> - <param name="format">The format.</param> - <param name="arg0">The arg0.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.Log4NetLog.FatalFormat(System.String,System.Object[])"> - <summary> - Logs the fatal error message. - </summary> - <param name="format">The format.</param> - <param name="args">The args.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.Log4NetLog.FatalFormat(System.IFormatProvider,System.String,System.Object[])"> - <summary> - Logs the fatal error message. - </summary> - <param name="provider">The provider.</param> - <param name="format">The format.</param> - <param name="args">The args.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.Log4NetLog.FatalFormat(System.String,System.Object,System.Object)"> - <summary> - Logs the fatal error message. - </summary> - <param name="format">The format.</param> - <param name="arg0">The arg0.</param> - <param name="arg1">The arg1.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.Log4NetLog.FatalFormat(System.String,System.Object,System.Object,System.Object)"> - <summary> - Logs the fatal error message. - </summary> - <param name="format">The format.</param> - <param name="arg0">The arg0.</param> - <param name="arg1">The arg1.</param> - <param name="arg2">The arg2.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.Log4NetLog.Info(System.Object)"> - <summary> - Logs the info message. - </summary> - <param name="message">The message.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.Log4NetLog.Info(System.Object,System.Exception)"> - <summary> - Logs the info message. - </summary> - <param name="message">The message.</param> - <param name="exception">The exception.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.Log4NetLog.InfoFormat(System.String,System.Object)"> - <summary> - Logs the info message. - </summary> - <param name="format">The format.</param> - <param name="arg0">The arg0.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.Log4NetLog.InfoFormat(System.String,System.Object[])"> - <summary> - Logs the info message. - </summary> - <param name="format">The format.</param> - <param name="args">The args.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.Log4NetLog.InfoFormat(System.IFormatProvider,System.String,System.Object[])"> - <summary> - Logs the info message. - </summary> - <param name="provider">The provider.</param> - <param name="format">The format.</param> - <param name="args">The args.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.Log4NetLog.InfoFormat(System.String,System.Object,System.Object)"> - <summary> - Logs the info message. - </summary> - <param name="format">The format.</param> - <param name="arg0">The arg0.</param> - <param name="arg1">The arg1.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.Log4NetLog.InfoFormat(System.String,System.Object,System.Object,System.Object)"> - <summary> - Logs the info message. - </summary> - <param name="format">The format.</param> - <param name="arg0">The arg0.</param> - <param name="arg1">The arg1.</param> - <param name="arg2">The arg2.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.Log4NetLog.Warn(System.Object)"> - <summary> - Logs the warning message. - </summary> - <param name="message">The message.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.Log4NetLog.Warn(System.Object,System.Exception)"> - <summary> - Logs the warning message. - </summary> - <param name="message">The message.</param> - <param name="exception">The exception.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.Log4NetLog.WarnFormat(System.String,System.Object)"> - <summary> - Logs the warning message. - </summary> - <param name="format">The format.</param> - <param name="arg0">The arg0.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.Log4NetLog.WarnFormat(System.String,System.Object[])"> - <summary> - Logs the warning message. - </summary> - <param name="format">The format.</param> - <param name="args">The args.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.Log4NetLog.WarnFormat(System.IFormatProvider,System.String,System.Object[])"> - <summary> - Logs the warning message. - </summary> - <param name="provider">The provider.</param> - <param name="format">The format.</param> - <param name="args">The args.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.Log4NetLog.WarnFormat(System.String,System.Object,System.Object)"> - <summary> - Logs the warning message. - </summary> - <param name="format">The format.</param> - <param name="arg0">The arg0.</param> - <param name="arg1">The arg1.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.Log4NetLog.WarnFormat(System.String,System.Object,System.Object,System.Object)"> - <summary> - Logs the warning message. - </summary> - <param name="format">The format.</param> - <param name="arg0">The arg0.</param> - <param name="arg1">The arg1.</param> - <param name="arg2">The arg2.</param> - </member> - <member name="P:SuperSocket.SocketBase.Logging.Log4NetLog.IsDebugEnabled"> - <summary> - Gets a value indicating whether this instance is debug enabled. - </summary> - <value> - <c>true</c> if this instance is debug enabled; otherwise, <c>false</c>. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Logging.Log4NetLog.IsErrorEnabled"> - <summary> - Gets a value indicating whether this instance is error enabled. - </summary> - <value> - <c>true</c> if this instance is error enabled; otherwise, <c>false</c>. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Logging.Log4NetLog.IsFatalEnabled"> - <summary> - Gets a value indicating whether this instance is fatal enabled. - </summary> - <value> - <c>true</c> if this instance is fatal enabled; otherwise, <c>false</c>. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Logging.Log4NetLog.IsInfoEnabled"> - <summary> - Gets a value indicating whether this instance is info enabled. - </summary> - <value> - <c>true</c> if this instance is info enabled; otherwise, <c>false</c>. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Logging.Log4NetLog.IsWarnEnabled"> - <summary> - Gets a value indicating whether this instance is warn enabled. - </summary> - <value> - <c>true</c> if this instance is warn enabled; otherwise, <c>false</c>. - </value> - </member> - <member name="T:SuperSocket.SocketBase.Logging.Log4NetLogFactory"> - <summary> - Log4NetLogFactory - </summary> - </member> - <member name="T:SuperSocket.SocketBase.Logging.LogFactoryBase"> - <summary> - LogFactory Base class - </summary> - </member> - <member name="M:SuperSocket.SocketBase.Logging.LogFactoryBase.#ctor(System.String)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.Logging.LogFactoryBase"/> class. - </summary> - <param name="configFile">The config file.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.LogFactoryBase.GetLog(System.String)"> - <summary> - Gets the log by name. - </summary> - <param name="name">The name.</param> - <returns></returns> - </member> - <member name="P:SuperSocket.SocketBase.Logging.LogFactoryBase.ConfigFile"> - <summary> - Gets the config file file path. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Logging.LogFactoryBase.IsSharedConfig"> - Gets a value indicating whether the server instance is running in isolation mode and the multiple server instances share the same logging configuration. - </member> - <member name="M:SuperSocket.SocketBase.Logging.Log4NetLogFactory.#ctor"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.Logging.Log4NetLogFactory"/> class. - </summary> - </member> - <member name="M:SuperSocket.SocketBase.Logging.Log4NetLogFactory.#ctor(System.String)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.Logging.Log4NetLogFactory"/> class. - </summary> - <param name="log4netConfig">The log4net config.</param> - </member> - <member name="M:SuperSocket.SocketBase.Logging.Log4NetLogFactory.GetLog(System.String)"> - <summary> - Gets the log by name. - </summary> - <param name="name">The name.</param> - <returns></returns> - </member> - <member name="T:SuperSocket.SocketBase.NodeSummary"> - <summary> - GlobalPerformanceData class - </summary> - </member> - <member name="P:SuperSocket.SocketBase.NodeSummary.CpuUsage"> - <summary> - Gets or sets the cpu usage. - </summary> - <value> - The cpu usage. - </value> - </member> - <member name="P:SuperSocket.SocketBase.NodeSummary.WorkingSet"> - <summary> - Gets or sets the working set. - </summary> - <value> - The working set. - </value> - </member> - <member name="P:SuperSocket.SocketBase.NodeSummary.TotalThreadCount"> - <summary> - Gets or sets the total thread count. - </summary> - <value> - The total thread count. - </value> - </member> - <member name="P:SuperSocket.SocketBase.NodeSummary.AvailableWorkingThreads"> - <summary> - Gets or sets the available working threads. - </summary> - <value> - The available working threads. - </value> - </member> - <member name="P:SuperSocket.SocketBase.NodeSummary.AvailableCompletionPortThreads"> - <summary> - Gets or sets the available completion port threads. - </summary> - <value> - The available completion port threads. - </value> - </member> - <member name="P:SuperSocket.SocketBase.NodeSummary.MaxWorkingThreads"> - <summary> - Gets or sets the max working threads. - </summary> - <value> - The max working threads. - </value> - </member> - <member name="P:SuperSocket.SocketBase.NodeSummary.MaxCompletionPortThreads"> - <summary> - Gets or sets the max completion port threads. - </summary> - <value> - The max completion port threads. - </value> - </member> - <member name="T:SuperSocket.SocketBase.Protocol.CommandLineReceiveFilterFactory"> - <summary> - CommandLine RequestFilter Factory - </summary> - </member> - <member name="T:SuperSocket.SocketBase.Protocol.TerminatorReceiveFilterFactory"> - <summary> - Terminator ReceiveFilter Factory - </summary> - </member> - <member name="T:SuperSocket.SocketBase.Protocol.IReceiveFilterFactory`1"> - <summary> - Receive filter factory interface - </summary> - <typeparam name="TRequestInfo">The type of the request info.</typeparam> - </member> - <member name="T:SuperSocket.SocketBase.Protocol.IReceiveFilterFactory"> - <summary> - Receive filter factory interface - </summary> - </member> - <member name="M:SuperSocket.SocketBase.Protocol.IReceiveFilterFactory`1.CreateFilter(SuperSocket.SocketBase.IAppServer,SuperSocket.SocketBase.IAppSession,System.Net.IPEndPoint)"> - <summary> - Creates the Receive filter. - </summary> - <param name="appServer">The app server.</param> - <param name="appSession">The app session.</param> - <param name="remoteEndPoint">The remote end point.</param> - <returns> - the new created request filer assosiated with this socketSession - </returns> - </member> - <member name="M:SuperSocket.SocketBase.Protocol.TerminatorReceiveFilterFactory.#ctor(System.String)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.Protocol.TerminatorReceiveFilterFactory"/> class. - </summary> - <param name="terminator">The terminator.</param> - </member> - <member name="M:SuperSocket.SocketBase.Protocol.TerminatorReceiveFilterFactory.#ctor(System.String,System.Text.Encoding)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.Protocol.TerminatorReceiveFilterFactory"/> class. - </summary> - <param name="terminator">The terminator.</param> - <param name="encoding">The encoding.</param> - </member> - <member name="M:SuperSocket.SocketBase.Protocol.TerminatorReceiveFilterFactory.#ctor(System.String,System.Text.Encoding,SuperSocket.SocketBase.Protocol.IRequestInfoParser{SuperSocket.SocketBase.Protocol.StringRequestInfo})"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.Protocol.TerminatorReceiveFilterFactory"/> class. - </summary> - <param name="terminator">The terminator.</param> - <param name="encoding">The encoding.</param> - <param name="requestInfoParser">The line parser.</param> - </member> - <member name="M:SuperSocket.SocketBase.Protocol.TerminatorReceiveFilterFactory.CreateFilter(SuperSocket.SocketBase.IAppServer,SuperSocket.SocketBase.IAppSession,System.Net.IPEndPoint)"> - <summary> - Creates the Receive filter. - </summary> - <param name="appServer">The app server.</param> - <param name="appSession">The app session.</param> - <param name="remoteEndPoint">The remote end point.</param> - <returns> - the new created request filer assosiated with this socketSession - </returns> - </member> - <member name="M:SuperSocket.SocketBase.Protocol.CommandLineReceiveFilterFactory.#ctor"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.Protocol.CommandLineReceiveFilterFactory"/> class. - </summary> - </member> - <member name="M:SuperSocket.SocketBase.Protocol.CommandLineReceiveFilterFactory.#ctor(System.Text.Encoding)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.Protocol.CommandLineReceiveFilterFactory"/> class. - </summary> - <param name="encoding">The encoding.</param> - </member> - <member name="M:SuperSocket.SocketBase.Protocol.CommandLineReceiveFilterFactory.#ctor(System.Text.Encoding,SuperSocket.SocketBase.Protocol.IRequestInfoParser{SuperSocket.SocketBase.Protocol.StringRequestInfo})"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.Protocol.CommandLineReceiveFilterFactory"/> class. - </summary> - <param name="encoding">The encoding.</param> - <param name="requestInfoParser">The request info parser.</param> - </member> - <member name="T:SuperSocket.SocketBase.Protocol.DefaultReceiveFilterFactory`2"> - <summary> - DefaultreceiveFilterFactory - </summary> - <typeparam name="TReceiveFilter">The type of the Receive filter.</typeparam> - <typeparam name="TRequestInfo">The type of the request info.</typeparam> - </member> - <member name="M:SuperSocket.SocketBase.Protocol.DefaultReceiveFilterFactory`2.CreateFilter(SuperSocket.SocketBase.IAppServer,SuperSocket.SocketBase.IAppSession,System.Net.IPEndPoint)"> - <summary> - Creates the Receive filter. - </summary> - <param name="appServer">The app server.</param> - <param name="appSession">The app session.</param> - <param name="remoteEndPoint">The remote end point.</param> - <returns> - the new created request filer assosiated with this socketSession - </returns> - </member> - <member name="T:SuperSocket.SocketBase.Protocol.FilterState"> - <summary> - Filter state enum - </summary> - </member> - <member name="F:SuperSocket.SocketBase.Protocol.FilterState.Normal"> - <summary> - Normal state - </summary> - </member> - <member name="F:SuperSocket.SocketBase.Protocol.FilterState.Error"> - <summary> - Error state - </summary> - </member> - <member name="T:SuperSocket.SocketBase.Protocol.IOffsetAdapter"> - <summary> - The interface for a Receive filter to adapt receiving buffer offset - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Protocol.IOffsetAdapter.OffsetDelta"> - <summary> - Gets the offset delta. - </summary> - </member> - <member name="T:SuperSocket.SocketBase.Protocol.IReceiveFilter`1"> - <summary> - Receive filter interface - </summary> - <typeparam name="TRequestInfo">The type of the request info.</typeparam> - </member> - <member name="M:SuperSocket.SocketBase.Protocol.IReceiveFilter`1.Filter(System.Byte[],System.Int32,System.Int32,System.Boolean,System.Int32@)"> - <summary> - Filters received data of the specific session into request info. - </summary> - <param name="readBuffer">The read buffer.</param> - <param name="offset">The offset of the current received data in this read buffer.</param> - <param name="length">The length of the current received data.</param> - <param name="toBeCopied">if set to <c>true</c> [to be copied].</param> - <param name="rest">The rest, the length of the data which hasn't been parsed.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.Protocol.IReceiveFilter`1.Reset"> - <summary> - Resets this instance to initial state. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Protocol.IReceiveFilter`1.LeftBufferSize"> - <summary> - Gets the size of the rest buffer. - </summary> - <value> - The size of the rest buffer. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Protocol.IReceiveFilter`1.NextReceiveFilter"> - <summary> - Gets the next Receive filter. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Protocol.IReceiveFilter`1.State"> - <summary> - Gets the filter state. - </summary> - <value> - The filter state. - </value> - </member> - <member name="T:SuperSocket.SocketBase.Protocol.IReceiveFilterInitializer"> - <summary> - Provide the initializing interface for ReceiveFilter - </summary> - </member> - <member name="M:SuperSocket.SocketBase.Protocol.IReceiveFilterInitializer.Initialize(SuperSocket.SocketBase.IAppServer,SuperSocket.SocketBase.IAppSession)"> - <summary> - Initializes the ReceiveFilter with the specified appServer and appSession - </summary> - <param name="appServer">The app server.</param> - <param name="session">The session.</param> - </member> - <member name="T:SuperSocket.SocketBase.Protocol.ReceiveFilterBase`1"> - <summary> - Receive filter base class - </summary> - <typeparam name="TRequestInfo">The type of the request info.</typeparam> - </member> - <member name="M:SuperSocket.SocketBase.Protocol.ReceiveFilterBase`1.#ctor"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.Protocol.ReceiveFilterBase`1"/> class. - </summary> - </member> - <member name="M:SuperSocket.SocketBase.Protocol.ReceiveFilterBase`1.#ctor(SuperSocket.SocketBase.Protocol.ReceiveFilterBase{`0})"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.Protocol.ReceiveFilterBase`1"/> class. - </summary> - <param name="previousRequestFilter">The previous Receive filter.</param> - </member> - <member name="M:SuperSocket.SocketBase.Protocol.ReceiveFilterBase`1.Initialize(SuperSocket.SocketBase.Protocol.ReceiveFilterBase{`0})"> - <summary> - Initializes the specified previous Receive filter. - </summary> - <param name="previousRequestFilter">The previous Receive filter.</param> - </member> - <member name="M:SuperSocket.SocketBase.Protocol.ReceiveFilterBase`1.Filter(System.Byte[],System.Int32,System.Int32,System.Boolean,System.Int32@)"> - <summary> - Filters received data of the specific session into request info. - </summary> - <param name="readBuffer">The read buffer.</param> - <param name="offset">The offset of the current received data in this read buffer.</param> - <param name="length">The length of the current received data.</param> - <param name="toBeCopied">if set to <c>true</c> [to be copied].</param> - <param name="rest">The rest, the length of the data which hasn't been parsed.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.Protocol.ReceiveFilterBase`1.GetLeftBuffer"> - <summary> - Gets the rest buffer. - </summary> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.Protocol.ReceiveFilterBase`1.AddArraySegment(System.Byte[],System.Int32,System.Int32,System.Boolean)"> - <summary> - Adds the array segment. - </summary> - <param name="buffer">The buffer.</param> - <param name="offset">The offset.</param> - <param name="length">The length.</param> - <param name="toBeCopied">if set to <c>true</c> [to be copied].</param> - </member> - <member name="M:SuperSocket.SocketBase.Protocol.ReceiveFilterBase`1.ClearBufferSegments"> - <summary> - Clears the buffer segments. - </summary> - </member> - <member name="M:SuperSocket.SocketBase.Protocol.ReceiveFilterBase`1.Reset"> - <summary> - Resets this instance to initial state. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Protocol.ReceiveFilterBase`1.BufferSegments"> - <summary> - Gets the buffer segments which can help you parse your request info conviniently. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Protocol.ReceiveFilterBase`1.LeftBufferSize"> - <summary> - Gets the size of the rest buffer. - </summary> - <value> - The size of the rest buffer. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Protocol.ReceiveFilterBase`1.NextReceiveFilter"> - <summary> - Gets or sets the next Receive filter. - </summary> - <value> - The next Receive filter. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Protocol.ReceiveFilterBase`1.State"> - <summary> - Gets the filter state. - </summary> - <value> - The state. - </value> - </member> - <member name="T:SuperSocket.SocketBase.Protocol.TerminatorReceiveFilter`1"> - <summary> - Terminator Receive filter - </summary> - <typeparam name="TRequestInfo">The type of the request info.</typeparam> - </member> - <member name="F:SuperSocket.SocketBase.Protocol.TerminatorReceiveFilter`1.NullRequestInfo"> - <summary> - Null RequestInfo - </summary> - </member> - <member name="M:SuperSocket.SocketBase.Protocol.TerminatorReceiveFilter`1.#ctor(System.Byte[])"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.Protocol.TerminatorReceiveFilter`1"/> class. - </summary> - <param name="terminator">The terminator.</param> - </member> - <member name="M:SuperSocket.SocketBase.Protocol.TerminatorReceiveFilter`1.Filter(System.Byte[],System.Int32,System.Int32,System.Boolean,System.Int32@)"> - <summary> - Filters received data of the specific session into request info. - </summary> - <param name="readBuffer">The read buffer.</param> - <param name="offset">The offset of the current received data in this read buffer.</param> - <param name="length">The length of the current received data.</param> - <param name="toBeCopied">if set to <c>true</c> [to be copied].</param> - <param name="rest">The rest, the length of the data which hasn't been parsed.</param> - <returns>return the parsed TRequestInfo</returns> - </member> - <member name="M:SuperSocket.SocketBase.Protocol.TerminatorReceiveFilter`1.Reset"> - <summary> - Resets this instance. - </summary> - </member> - <member name="M:SuperSocket.SocketBase.Protocol.TerminatorReceiveFilter`1.ProcessMatchedRequest(System.Byte[],System.Int32,System.Int32)"> - <summary> - Resolves the specified data to TRequestInfo. - </summary> - <param name="data">The data.</param> - <param name="offset">The offset.</param> - <param name="length">The length.</param> - <returns></returns> - </member> - <member name="P:SuperSocket.SocketBase.Protocol.TerminatorReceiveFilter`1.Session"> - <summary> - Gets the session assosiated with the Receive filter. - </summary> - </member> - <member name="T:SuperSocket.SocketBase.Protocol.TerminatorReceiveFilter"> - <summary> - TerminatorRequestFilter - </summary> - </member> - <member name="M:SuperSocket.SocketBase.Protocol.TerminatorReceiveFilter.#ctor(System.Byte[],System.Text.Encoding)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.Protocol.TerminatorReceiveFilter"/> class. - </summary> - <param name="terminator">The terminator.</param> - <param name="encoding">The encoding.</param> - </member> - <member name="M:SuperSocket.SocketBase.Protocol.TerminatorReceiveFilter.#ctor(System.Byte[],System.Text.Encoding,SuperSocket.SocketBase.Protocol.IRequestInfoParser{SuperSocket.SocketBase.Protocol.StringRequestInfo})"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.Protocol.TerminatorReceiveFilter"/> class. - </summary> - <param name="terminator">The terminator.</param> - <param name="encoding">The encoding.</param> - <param name="requestParser">The request parser.</param> - </member> - <member name="M:SuperSocket.SocketBase.Protocol.TerminatorReceiveFilter.ProcessMatchedRequest(System.Byte[],System.Int32,System.Int32)"> - <summary> - Resolves the specified data to StringRequestInfo. - </summary> - <param name="data">The data.</param> - <param name="offset">The offset.</param> - <param name="length">The length.</param> - <returns></returns> - </member> - <member name="T:SuperSocket.SocketBase.Provider.ExportFactory"> - <summary> - Export Factory - </summary> - </member> - <member name="M:SuperSocket.SocketBase.Provider.ExportFactory.#ctor"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.Provider.ExportFactory"/> class. - </summary> - </member> - <member name="M:SuperSocket.SocketBase.Provider.ExportFactory.#ctor(System.Object)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.Provider.ExportFactory"/> class. - </summary> - <param name="instance">The instance.</param> - </member> - <member name="M:SuperSocket.SocketBase.Provider.ExportFactory.#ctor(System.String)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.Provider.ExportFactory"/> class. - </summary> - <param name="typeName">Name of the type.</param> - </member> - <member name="M:SuperSocket.SocketBase.Provider.ExportFactory.EnsureInstance"> - <summary> - Ensures the instance's existance. - </summary> - </member> - <member name="M:SuperSocket.SocketBase.Provider.ExportFactory.CreateExport``1"> - <summary> - Creates the export type instance. - </summary> - <typeparam name="T"></typeparam> - <returns></returns> - </member> - <member name="P:SuperSocket.SocketBase.Provider.ExportFactory.TypeName"> - <summary> - Gets or sets the type. - </summary> - <value> - The type. - </value> - </member> - <member name="T:SuperSocket.SocketBase.Provider.ProviderFactoryInfo"> - <summary> - Provider factory infomation - </summary> - </member> - <member name="M:SuperSocket.SocketBase.Provider.ProviderFactoryInfo.#ctor"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.Provider.ProviderFactoryInfo"/> class. - </summary> - </member> - <member name="M:SuperSocket.SocketBase.Provider.ProviderFactoryInfo.#ctor(SuperSocket.SocketBase.Provider.ProviderKey,System.String,System.Object)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.Provider.ProviderFactoryInfo"/> class. - </summary> - <param name="key">The key.</param> - <param name="name">The name.</param> - <param name="instance">The instance.</param> - </member> - <member name="M:SuperSocket.SocketBase.Provider.ProviderFactoryInfo.#ctor(SuperSocket.SocketBase.Provider.ProviderKey,System.String,System.String)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.Provider.ProviderFactoryInfo"/> class. - </summary> - <param name="key">The key.</param> - <param name="name">The name.</param> - <param name="typeName">Name of the type.</param> - </member> - <member name="M:SuperSocket.SocketBase.Provider.ProviderFactoryInfo.#ctor(SuperSocket.SocketBase.Provider.ProviderKey,System.String,System.Type)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.Provider.ProviderFactoryInfo"/> class. - </summary> - <param name="key">The key.</param> - <param name="name">The name.</param> - <param name="type">The type.</param> - </member> - <member name="P:SuperSocket.SocketBase.Provider.ProviderFactoryInfo.Key"> - <summary> - Gets the key. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Provider.ProviderFactoryInfo.Name"> - <summary> - Gets or sets the name. - </summary> - <value> - The name. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Provider.ProviderFactoryInfo.ExportFactory"> - <summary> - Gets or sets the export factory. - </summary> - <value> - The export factory. - </value> - </member> - <member name="T:SuperSocket.SocketBase.Provider.ProviderKey"> - <summary> - ProviderKey - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Provider.ProviderKey.Name"> - <summary> - Gets or sets the name. - </summary> - <value> - The name. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Provider.ProviderKey.Type"> - <summary> - Gets or sets the type. - </summary> - <value> - The type. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Provider.ProviderKey.ServerType"> - <summary> - Gets the service. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Provider.ProviderKey.SocketServerFactory"> - <summary> - Gets the socket server factory. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Provider.ProviderKey.ConnectionFilter"> - <summary> - Gets the connection filter. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Provider.ProviderKey.LogFactory"> - <summary> - Gets the log factory. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Provider.ProviderKey.ReceiveFilterFactory"> - <summary> - Gets the Receive filter factory. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Provider.ProviderKey.CommandLoader"> - <summary> - Gets the command loader. - </summary> - </member> - <member name="T:SuperSocket.SocketBase.RequestHandler`2"> - <summary> - Request handler - </summary> - <typeparam name="TAppSession">The type of the app session.</typeparam> - <typeparam name="TRequestInfo">The type of the request info.</typeparam> - <param name="session">The session.</param> - <param name="requestInfo">The request info.</param> - </member> - <member name="T:SuperSocket.SocketBase.Config.IListenerConfig"> - <summary> - The listener configuration interface - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.IListenerConfig.Ip"> - <summary> - Gets the ip of listener - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.IListenerConfig.Port"> - <summary> - Gets the port of listener - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.IListenerConfig.Backlog"> - <summary> - Gets the backlog. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.IListenerConfig.Security"> - <summary> - Gets the security option, None/Default/Tls/Ssl/... - </summary> - </member> - <member name="T:SuperSocket.SocketBase.Config.ListenerConfig"> - <summary> - Listener configuration model - </summary> - </member> - <member name="M:SuperSocket.SocketBase.Config.ListenerConfig.#ctor"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.Config.ListenerConfig"/> class. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.ListenerConfig.Ip"> - <summary> - Gets the ip of listener - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.ListenerConfig.Port"> - <summary> - Gets the port of listener - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.ListenerConfig.Backlog"> - <summary> - Gets the backlog. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.ListenerConfig.Security"> - <summary> - Gets/sets the security option, None/Default/Tls/Ssl/... - </summary> - </member> - <member name="T:SuperSocket.SocketBase.ListenerInfo"> - <summary> - Listener inforamtion - </summary> - </member> - <member name="P:SuperSocket.SocketBase.ListenerInfo.EndPoint"> - <summary> - Gets or sets the listen endpoint. - </summary> - <value> - The end point. - </value> - </member> - <member name="P:SuperSocket.SocketBase.ListenerInfo.BackLog"> - <summary> - Gets or sets the listen backlog. - </summary> - <value> - The back log. - </value> - </member> - <member name="P:SuperSocket.SocketBase.ListenerInfo.Security"> - <summary> - Gets or sets the security protocol. - </summary> - <value> - The security. - </value> - </member> - <member name="T:SuperSocket.SocketBase.Protocol.BinaryRequestInfo"> - <summary> - Binary type request information - </summary> - </member> - <member name="T:SuperSocket.SocketBase.Protocol.RequestInfo`1"> - <summary> - RequestInfo basic class - </summary> - <typeparam name="TRequestBody">The type of the request body.</typeparam> - </member> - <member name="T:SuperSocket.SocketBase.Protocol.IRequestInfo`1"> - <summary> - Request information interface - </summary> - <typeparam name="TRequestBody">The type of the request body.</typeparam> - </member> - <member name="T:SuperSocket.SocketBase.Protocol.IRequestInfo"> - <summary> - Request information interface - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Protocol.IRequestInfo.Key"> - <summary> - Gets the key of this request. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Protocol.IRequestInfo`1.Body"> - <summary> - Gets the body of this request. - </summary> - </member> - <member name="M:SuperSocket.SocketBase.Protocol.RequestInfo`1.#ctor"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.Protocol.RequestInfo`1"/> class. - </summary> - </member> - <member name="M:SuperSocket.SocketBase.Protocol.RequestInfo`1.#ctor(System.String,`0)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.Protocol.RequestInfo`1"/> class. - </summary> - <param name="key">The key.</param> - <param name="body">The body.</param> - </member> - <member name="M:SuperSocket.SocketBase.Protocol.RequestInfo`1.Initialize(System.String,`0)"> - <summary> - Initializes the specified key. - </summary> - <param name="key">The key.</param> - <param name="body">The body.</param> - </member> - <member name="P:SuperSocket.SocketBase.Protocol.RequestInfo`1.Key"> - <summary> - Gets the key of this request. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Protocol.RequestInfo`1.Body"> - <summary> - Gets the body. - </summary> - </member> - <member name="M:SuperSocket.SocketBase.Protocol.BinaryRequestInfo.#ctor(System.String,System.Byte[])"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.Protocol.BinaryRequestInfo"/> class. - </summary> - <param name="key">The key.</param> - <param name="body">The body.</param> - </member> - <member name="T:SuperSocket.SocketBase.Command.CommandBase`2"> - <summary> - Command base class - </summary> - <typeparam name="TAppSession">The type of the app session.</typeparam> - <typeparam name="TRequestInfo">The type of the request info.</typeparam> - </member> - <member name="T:SuperSocket.SocketBase.Command.ICommand`2"> - <summary> - Command basic interface - </summary> - <typeparam name="TAppSession">The type of the app session.</typeparam> - <typeparam name="TRequestInfo">The type of the request info.</typeparam> - </member> - <member name="T:SuperSocket.SocketBase.Command.ICommand"> - <summary> - Command basic interface - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Command.ICommand.Name"> - <summary> - Gets the name. - </summary> - </member> - <member name="M:SuperSocket.SocketBase.Command.ICommand`2.ExecuteCommand(`0,`1)"> - <summary> - Executes the command. - </summary> - <param name="session">The session.</param> - <param name="requestInfo">The request info.</param> - </member> - <member name="M:SuperSocket.SocketBase.Command.CommandBase`2.ExecuteCommand(`0,`1)"> - <summary> - Executes the command. - </summary> - <param name="session">The session.</param> - <param name="requestInfo">The request info.</param> - </member> - <member name="M:SuperSocket.SocketBase.Command.CommandBase`2.ToString"> - <summary> - Returns a <see cref="T:System.String"/> that represents this instance. - </summary> - <returns> - A <see cref="T:System.String"/> that represents this instance. - </returns> - </member> - <member name="P:SuperSocket.SocketBase.Command.CommandBase`2.Name"> - <summary> - Gets the name. - </summary> - </member> - <member name="T:SuperSocket.SocketBase.Command.CommandUpdateAction"> - <summary> - Command update action enum - </summary> - </member> - <member name="F:SuperSocket.SocketBase.Command.CommandUpdateAction.Add"> - <summary> - Add command - </summary> - </member> - <member name="F:SuperSocket.SocketBase.Command.CommandUpdateAction.Remove"> - <summary> - Remove command - </summary> - </member> - <member name="F:SuperSocket.SocketBase.Command.CommandUpdateAction.Update"> - <summary> - Update command - </summary> - </member> - <member name="T:SuperSocket.SocketBase.Command.CommandUpdateInfo`1"> - <summary> - Command update information - </summary> - <typeparam name="T"></typeparam> - </member> - <member name="P:SuperSocket.SocketBase.Command.CommandUpdateInfo`1.UpdateAction"> - <summary> - Gets or sets the update action. - </summary> - <value> - The update action. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Command.CommandUpdateInfo`1.Command"> - <summary> - Gets or sets the target command. - </summary> - <value> - The command. - </value> - </member> - <member name="T:SuperSocket.SocketBase.Command.MockupCommand`2"> - <summary> - Mockup command - </summary> - <typeparam name="TAppSession">The type of the app session.</typeparam> - <typeparam name="TRequestInfo">The type of the request info.</typeparam> - </member> - <member name="M:SuperSocket.SocketBase.Command.MockupCommand`2.#ctor(System.String)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.Command.MockupCommand`2"/> class. - </summary> - <param name="name">The name.</param> - </member> - <member name="M:SuperSocket.SocketBase.Command.MockupCommand`2.ExecuteCommand(`0,`1)"> - <summary> - Executes the command. - </summary> - <param name="session">The session.</param> - <param name="requestInfo">The request info.</param> - </member> - <member name="P:SuperSocket.SocketBase.Command.MockupCommand`2.Name"> - <summary> - Gets the name. - </summary> - </member> - <member name="T:SuperSocket.SocketBase.Protocol.IRequestInfo`2"> - <summary> - Request information interface - </summary> - <typeparam name="TRequestHeader">The type of the request header.</typeparam> - <typeparam name="TRequestBody">The type of the request body.</typeparam> - </member> - <member name="P:SuperSocket.SocketBase.Protocol.IRequestInfo`2.Header"> - <summary> - Gets the header of the request. - </summary> - </member> - <member name="T:SuperSocket.SocketBase.Protocol.RequestInfo`2"> - <summary> - RequestInfo with header - </summary> - <typeparam name="TRequestHeader">The type of the request header.</typeparam> - <typeparam name="TRequestBody">The type of the request body.</typeparam> - </member> - <member name="M:SuperSocket.SocketBase.Protocol.RequestInfo`2.#ctor"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.Protocol.RequestInfo`2"/> class. - </summary> - </member> - <member name="M:SuperSocket.SocketBase.Protocol.RequestInfo`2.#ctor(System.String,`0,`1)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.Protocol.RequestInfo`2"/> class. - </summary> - <param name="key">The key.</param> - <param name="header">The header.</param> - <param name="body">The body.</param> - </member> - <member name="M:SuperSocket.SocketBase.Protocol.RequestInfo`2.Initialize(System.String,`0,`1)"> - <summary> - Initializes the specified key. - </summary> - <param name="key">The key.</param> - <param name="header">The header.</param> - <param name="body">The body.</param> - </member> - <member name="P:SuperSocket.SocketBase.Protocol.RequestInfo`2.Header"> - <summary> - Gets the header. - </summary> - </member> - <member name="T:SuperSocket.SocketBase.Protocol.StringRequestInfo"> - <summary> - String type request information - </summary> - </member> - <member name="M:SuperSocket.SocketBase.Protocol.StringRequestInfo.#ctor(System.String,System.String,System.String[])"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.Protocol.StringRequestInfo"/> class. - </summary> - <param name="key">The key.</param> - <param name="body">The body.</param> - <param name="parameters">The parameters.</param> - </member> - <member name="M:SuperSocket.SocketBase.Protocol.StringRequestInfo.GetFirstParam"> - <summary> - Gets the first param. - </summary> - <returns></returns> - </member> - <member name="P:SuperSocket.SocketBase.Protocol.StringRequestInfo.Parameters"> - <summary> - Gets the parameters. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Protocol.StringRequestInfo.Item(System.Int32)"> - <summary> - Gets the <see cref="T:System.String"/> at the specified index. - </summary> - </member> - <member name="T:SuperSocket.SocketBase.Command.ReflectCommandLoader"> - <summary> - A command loader which loads commands from assembly by reflection - </summary> - </member> - <member name="M:SuperSocket.SocketBase.Command.ReflectCommandLoader.Initialize``1(SuperSocket.SocketBase.Config.IRootConfig,SuperSocket.SocketBase.IAppServer)"> - <summary> - Initializes the command loader - </summary> - <typeparam name="TCommand">The type of the command.</typeparam> - <param name="rootConfig">The root config.</param> - <param name="appServer">The app server.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.Command.ReflectCommandLoader.TryLoadCommands(System.Collections.Generic.IEnumerable{SuperSocket.SocketBase.Command.ICommand}@)"> - <summary> - Tries to load commands. - </summary> - <param name="commands">The commands.</param> - <returns></returns> - </member> - <member name="T:SuperSocket.SocketBase.Protocol.UdpRequestInfo"> - <summary> - UdpRequestInfo, it is designed for passing in business session ID to udp request info - </summary> - </member> - <member name="M:SuperSocket.SocketBase.Protocol.UdpRequestInfo.#ctor(System.String,System.String)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.Protocol.UdpRequestInfo"/> class. - </summary> - <param name="key">The key.</param> - <param name="sessionID">The session ID.</param> - </member> - <member name="P:SuperSocket.SocketBase.Protocol.UdpRequestInfo.Key"> - <summary> - Gets the key of this request. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Protocol.UdpRequestInfo.SessionID"> - <summary> - Gets the session ID. - </summary> - </member> - <member name="T:SuperSocket.SocketBase.Config.CertificateConfig"> - <summary> - Certificate config model class - </summary> - </member> - <member name="T:SuperSocket.SocketBase.Config.ICertificateConfig"> - <summary> - Certificate configuration interface - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.ICertificateConfig.FilePath"> - <summary> - Gets the file path. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.ICertificateConfig.Password"> - <summary> - Gets the password. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.ICertificateConfig.StoreName"> - <summary> - Gets the the store where certificate locates. - </summary> - <value> - The name of the store. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Config.ICertificateConfig.Thumbprint"> - <summary> - Gets the thumbprint. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.CertificateConfig.FilePath"> - <summary> - Gets/sets the file path. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.CertificateConfig.Password"> - <summary> - Gets/sets the password. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.CertificateConfig.StoreName"> - <summary> - Gets/sets the the store where certificate locates. - </summary> - <value> - The name of the store. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Config.CertificateConfig.Thumbprint"> - <summary> - Gets/sets the thumbprint. - </summary> - </member> - <member name="T:SuperSocket.SocketBase.Config.IServerConfig"> - <summary> - Server instance configuation interface - </summary> - </member> - <member name="M:SuperSocket.SocketBase.Config.IServerConfig.GetChildConfig``1(System.String)"> - <summary> - Gets the child config. - </summary> - <typeparam name="TConfig">The type of the config.</typeparam> - <param name="childConfigName">Name of the child config.</param> - <returns></returns> - </member> - <member name="P:SuperSocket.SocketBase.Config.IServerConfig.ServerTypeName"> - <summary> - Gets the name of the server type this appServer want to use. - </summary> - <value> - The name of the server type. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Config.IServerConfig.ServerType"> - <summary> - Gets the type definition of the appserver. - </summary> - <value> - The type of the server. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Config.IServerConfig.ReceiveFilterFactory"> - <summary> - Gets the Receive filter factory. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.IServerConfig.Ip"> - <summary> - Gets the ip. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.IServerConfig.Port"> - <summary> - Gets the port. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.IServerConfig.Options"> - <summary> - Gets the options. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.IServerConfig.OptionElements"> - <summary> - Gets the option elements. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.IServerConfig.Disabled"> - <summary> - Gets a value indicating whether this <see cref="T:SuperSocket.SocketBase.Config.IServerConfig"/> is disabled. - </summary> - <value> - <c>true</c> if disabled; otherwise, <c>false</c>. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Config.IServerConfig.Name"> - <summary> - Gets the name. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.IServerConfig.Mode"> - <summary> - Gets the mode. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.IServerConfig.SendTimeOut"> - <summary> - Gets the send time out. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.IServerConfig.MaxConnectionNumber"> - <summary> - Gets the max connection number. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.IServerConfig.ReceiveBufferSize"> - <summary> - Gets the size of the receive buffer. - </summary> - <value> - The size of the receive buffer. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Config.IServerConfig.SendBufferSize"> - <summary> - Gets the size of the send buffer. - </summary> - <value> - The size of the send buffer. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Config.IServerConfig.SyncSend"> - <summary> - Gets a value indicating whether sending is in synchronous mode. - </summary> - <value> - <c>true</c> if [sync send]; otherwise, <c>false</c>. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Config.IServerConfig.LogCommand"> - <summary> - Gets a value indicating whether log command in log file. - </summary> - <value><c>true</c> if log command; otherwise, <c>false</c>.</value> - </member> - <member name="P:SuperSocket.SocketBase.Config.IServerConfig.ClearIdleSession"> - <summary> - Gets a value indicating whether clear idle session. - </summary> - <value><c>true</c> if clear idle session; otherwise, <c>false</c>.</value> - </member> - <member name="P:SuperSocket.SocketBase.Config.IServerConfig.ClearIdleSessionInterval"> - <summary> - Gets the clear idle session interval, in seconds. - </summary> - <value>The clear idle session interval.</value> - </member> - <member name="P:SuperSocket.SocketBase.Config.IServerConfig.IdleSessionTimeOut"> - <summary> - Gets the idle session timeout time length, in seconds. - </summary> - <value>The idle session time out.</value> - </member> - <member name="P:SuperSocket.SocketBase.Config.IServerConfig.Certificate"> - <summary> - Gets X509Certificate configuration. - </summary> - <value>X509Certificate configuration.</value> - </member> - <member name="P:SuperSocket.SocketBase.Config.IServerConfig.Security"> - <summary> - Gets the security protocol, X509 certificate. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.IServerConfig.MaxRequestLength"> - <summary> - Gets the length of the max request. - </summary> - <value> - The length of the max request. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Config.IServerConfig.DisableSessionSnapshot"> - <summary> - Gets a value indicating whether [disable session snapshot]. - </summary> - <value> - <c>true</c> if [disable session snapshot]; otherwise, <c>false</c>. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Config.IServerConfig.SessionSnapshotInterval"> - <summary> - Gets the interval to taking snapshot for all live sessions. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.IServerConfig.ConnectionFilter"> - <summary> - Gets the connection filters used by this server instance. - </summary> - <value> - The connection filter's name list, seperated by comma - </value> - </member> - <member name="P:SuperSocket.SocketBase.Config.IServerConfig.CommandLoader"> - <summary> - Gets the command loader, multiple values should be separated by comma. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.IServerConfig.KeepAliveTime"> - <summary> - Gets the start keep alive time, in seconds - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.IServerConfig.KeepAliveInterval"> - <summary> - Gets the keep alive interval, in seconds. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.IServerConfig.ListenBacklog"> - <summary> - Gets the backlog size of socket listening. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.IServerConfig.StartupOrder"> - <summary> - Gets the startup order of the server instance. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.IServerConfig.Listeners"> - <summary> - Gets the listeners' configuration. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.IServerConfig.LogFactory"> - <summary> - Gets the log factory name. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.IServerConfig.SendingQueueSize"> - <summary> - Gets the size of the sending queue. - </summary> - <value> - The size of the sending queue. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Config.IServerConfig.LogBasicSessionActivity"> - <summary> - Gets a value indicating whether [log basic session activity like connected and disconnected]. - </summary> - <value> - <c>true</c> if [log basic session activity]; otherwise, <c>false</c>. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Config.IServerConfig.LogAllSocketException"> - <summary> - Gets a value indicating whether [log all socket exception]. - </summary> - <value> - <c>true</c> if [log all socket exception]; otherwise, <c>false</c>. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Config.IServerConfig.CommandAssemblies"> - <summary> - Gets the command assemblies configuration. - </summary> - <value> - The command assemblies. - </value> - </member> - <member name="T:SuperSocket.SocketBase.Config.ServerConfig"> - <summary> - Server configruation model - </summary> - </member> - <member name="F:SuperSocket.SocketBase.Config.ServerConfig.DefaultReceiveBufferSize"> - <summary> - Default ReceiveBufferSize - </summary> - </member> - <member name="F:SuperSocket.SocketBase.Config.ServerConfig.DefaultMaxConnectionNumber"> - <summary> - Default MaxConnectionNumber - </summary> - </member> - <member name="F:SuperSocket.SocketBase.Config.ServerConfig.DefaultSendingQueueSize"> - <summary> - Default sending queue size - </summary> - </member> - <member name="F:SuperSocket.SocketBase.Config.ServerConfig.DefaultMaxRequestLength"> - <summary> - Default MaxRequestLength - </summary> - </member> - <member name="M:SuperSocket.SocketBase.Config.ServerConfig.#ctor(SuperSocket.SocketBase.Config.IServerConfig)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.Config.ServerConfig"/> class. - </summary> - <param name="serverConfig">The server config.</param> - </member> - <member name="M:SuperSocket.SocketBase.Config.ServerConfig.#ctor"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.Config.ServerConfig"/> class. - </summary> - </member> - <member name="M:SuperSocket.SocketBase.Config.ServerConfig.GetChildConfig``1(System.String)"> - <summary> - Gets the child config. - </summary> - <typeparam name="TConfig">The type of the config.</typeparam> - <param name="childConfigName">Name of the child config.</param> - <returns></returns> - </member> - <member name="P:SuperSocket.SocketBase.Config.ServerConfig.ServerTypeName"> - <summary> - Gets/sets the name of the server type of this appServer want to use. - </summary> - <value> - The name of the server type. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Config.ServerConfig.ServerType"> - <summary> - Gets/sets the type definition of the appserver. - </summary> - <value> - The type of the server. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Config.ServerConfig.ReceiveFilterFactory"> - <summary> - Gets/sets the Receive filter factory. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.ServerConfig.Ip"> - <summary> - Gets/sets the ip. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.ServerConfig.Port"> - <summary> - Gets/sets the port. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.ServerConfig.Options"> - <summary> - Gets/sets the options. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.ServerConfig.OptionElements"> - <summary> - Gets the option elements. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.ServerConfig.Disabled"> - <summary> - Gets/sets a value indicating whether this <see cref="T:SuperSocket.SocketBase.Config.IServerConfig"/> is disabled. - </summary> - <value> - <c>true</c> if disabled; otherwise, <c>false</c>. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Config.ServerConfig.Name"> - <summary> - Gets the name. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.ServerConfig.Mode"> - <summary> - Gets/sets the mode. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.ServerConfig.SendTimeOut"> - <summary> - Gets/sets the send time out. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.ServerConfig.MaxConnectionNumber"> - <summary> - Gets the max connection number. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.ServerConfig.ReceiveBufferSize"> - <summary> - Gets the size of the receive buffer. - </summary> - <value> - The size of the receive buffer. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Config.ServerConfig.SendBufferSize"> - <summary> - Gets the size of the send buffer. - </summary> - <value> - The size of the send buffer. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Config.ServerConfig.SyncSend"> - <summary> - Gets a value indicating whether sending is in synchronous mode. - </summary> - <value> - <c>true</c> if [sync send]; otherwise, <c>false</c>. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Config.ServerConfig.LogCommand"> - <summary> - Gets/sets a value indicating whether log command in log file. - </summary> - <value> - <c>true</c> if log command; otherwise, <c>false</c>. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Config.ServerConfig.ClearIdleSession"> - <summary> - Gets/sets a value indicating whether clear idle session. - </summary> - <value> - <c>true</c> if clear idle session; otherwise, <c>false</c>. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Config.ServerConfig.ClearIdleSessionInterval"> - <summary> - Gets/sets the clear idle session interval, in seconds. - </summary> - <value> - The clear idle session interval. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Config.ServerConfig.IdleSessionTimeOut"> - <summary> - Gets/sets the idle session timeout time length, in seconds. - </summary> - <value> - The idle session time out. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Config.ServerConfig.Certificate"> - <summary> - Gets/sets X509Certificate configuration. - </summary> - <value> - X509Certificate configuration. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Config.ServerConfig.Security"> - <summary> - Gets/sets the security protocol, X509 certificate. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.ServerConfig.MaxRequestLength"> - <summary> - Gets/sets the length of the max request. - </summary> - <value> - The length of the max request. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Config.ServerConfig.DisableSessionSnapshot"> - <summary> - Gets/sets a value indicating whether [disable session snapshot]. - </summary> - <value> - <c>true</c> if [disable session snapshot]; otherwise, <c>false</c>. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Config.ServerConfig.SessionSnapshotInterval"> - <summary> - Gets/sets the interval to taking snapshot for all live sessions. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.ServerConfig.ConnectionFilter"> - <summary> - Gets/sets the connection filters used by this server instance. - </summary> - <value> - The connection filter's name list, seperated by comma - </value> - </member> - <member name="P:SuperSocket.SocketBase.Config.ServerConfig.CommandLoader"> - <summary> - Gets the command loader, multiple values should be separated by comma. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.ServerConfig.KeepAliveTime"> - <summary> - Gets/sets the start keep alive time, in seconds - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.ServerConfig.KeepAliveInterval"> - <summary> - Gets/sets the keep alive interval, in seconds. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.ServerConfig.ListenBacklog"> - <summary> - Gets the backlog size of socket listening. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.ServerConfig.StartupOrder"> - <summary> - Gets/sets the startup order of the server instance. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.ServerConfig.Listeners"> - <summary> - Gets and sets the listeners' configuration. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.ServerConfig.LogFactory"> - <summary> - Gets/sets the log factory name. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.Config.ServerConfig.SendingQueueSize"> - <summary> - Gets/sets the size of the sending queue. - </summary> - <value> - The size of the sending queue. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Config.ServerConfig.LogBasicSessionActivity"> - <summary> - Gets a value indicating whether [log basic session activity like connected and disconnected]. - </summary> - <value> - <c>true</c> if [log basic session activity]; otherwise, <c>false</c>. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Config.ServerConfig.LogAllSocketException"> - <summary> - Gets/sets a value indicating whether [log all socket exception]. - </summary> - <value> - <c>true</c> if [log all socket exception]; otherwise, <c>false</c>. - </value> - </member> - <member name="P:SuperSocket.SocketBase.Config.ServerConfig.CommandAssemblies"> - <summary> - Gets the command assemblies configuration. - </summary> - <value> - The command assemblies. - </value> - </member> - <member name="T:SuperSocket.SocketBase.IAppServer"> - <summary> - The interface for AppServer - </summary> - </member> - <member name="M:SuperSocket.SocketBase.IAppServer.CreateAppSession(SuperSocket.SocketBase.ISocketSession)"> - <summary> - Creates the app session. - </summary> - <param name="socketSession">The socket session.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.IAppServer.GetAppSessionByID(System.String)"> - <summary> - Gets the app session by ID. - </summary> - <param name="sessionID">The session ID.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.IAppServer.ResetSessionSecurity(SuperSocket.SocketBase.IAppSession,System.Security.Authentication.SslProtocols)"> - <summary> - Resets the session's security protocol. - </summary> - <param name="session">The session.</param> - <param name="security">The security protocol.</param> - </member> - <member name="P:SuperSocket.SocketBase.IAppServer.StartedTime"> - <summary> - Gets the started time. - </summary> - <value> - The started time. - </value> - </member> - <member name="P:SuperSocket.SocketBase.IAppServer.Listeners"> - <summary> - Gets or sets the listeners. - </summary> - <value> - The listeners. - </value> - </member> - <member name="P:SuperSocket.SocketBase.IAppServer.ReceiveFilterFactory"> - <summary> - Gets the Receive filter factory. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.IAppServer.Config"> - <summary> - Gets the server's config. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.IAppServer.Certificate"> - <summary> - Gets the certificate of current server. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.IAppServer.BasicSecurity"> - <summary> - Gets the transfer layer security protocol. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.IAppServer.LogFactory"> - <summary> - Gets the log factory. - </summary> - </member> - <member name="T:SuperSocket.SocketBase.IRawDataProcessor`1"> - <summary> - The raw data processor - </summary> - <typeparam name="TAppSession">The type of the app session.</typeparam> - </member> - <member name="E:SuperSocket.SocketBase.IRawDataProcessor`1.RawDataReceived"> - <summary> - Gets or sets the raw binary data received event handler. - TAppSession: session - byte[]: receive buffer - int: receive buffer offset - int: receive lenght - bool: whether process the received data further - </summary> - </member> - <member name="T:SuperSocket.SocketBase.IAppServer`1"> - <summary> - The interface for AppServer - </summary> - <typeparam name="TAppSession">The type of the app session.</typeparam> - </member> - <member name="M:SuperSocket.SocketBase.IAppServer`1.GetSessions(System.Func{`0,System.Boolean})"> - <summary> - Gets the matched sessions from sessions snapshot. - </summary> - <param name="critera">The prediction critera.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.IAppServer`1.GetAllSessions"> - <summary> - Gets all sessions in sessions snapshot. - </summary> - <returns></returns> - </member> - <member name="E:SuperSocket.SocketBase.IAppServer`1.NewSessionConnected"> - <summary> - Gets/sets the new session connected event handler. - </summary> - </member> - <member name="E:SuperSocket.SocketBase.IAppServer`1.SessionClosed"> - <summary> - Gets/sets the session closed event handler. - </summary> - </member> - <member name="T:SuperSocket.SocketBase.IAppServer`2"> - <summary> - The interface for AppServer - </summary> - <typeparam name="TAppSession">The type of the app session.</typeparam> - <typeparam name="TRequestInfo">The type of the request info.</typeparam> - </member> - <member name="E:SuperSocket.SocketBase.IAppServer`2.NewRequestReceived"> - <summary> - Occurs when [request comming]. - </summary> - </member> - <member name="T:SuperSocket.SocketBase.IRequestHandler`1"> - <summary> - The interface for handler of session request - </summary> - <typeparam name="TRequestInfo">The type of the request info.</typeparam> - </member> - <member name="M:SuperSocket.SocketBase.IRequestHandler`1.ExecuteCommand(SuperSocket.SocketBase.IAppSession,`0)"> - <summary> - Executes the command. - </summary> - <param name="session">The session.</param> - <param name="requestInfo">The request info.</param> - </member> - <member name="T:SuperSocket.SocketBase.ISocketServerAccessor"> - <summary> - SocketServer Accessor interface - </summary> - </member> - <member name="P:SuperSocket.SocketBase.ISocketServerAccessor.SocketServer"> - <summary> - Gets the socket server. - </summary> - <value> - The socket server. - </value> - </member> - <member name="T:SuperSocket.SocketBase.IAppSession"> - <summary> - The basic interface for appSession - </summary> - </member> - <member name="T:SuperSocket.SocketBase.ISessionBase"> - <summary> - The basic session interface - </summary> - </member> - <member name="P:SuperSocket.SocketBase.ISessionBase.SessionID"> - <summary> - Gets the session ID. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.ISessionBase.RemoteEndPoint"> - <summary> - Gets the remote endpoint. - </summary> - </member> - <member name="M:SuperSocket.SocketBase.IAppSession.Close"> - <summary> - Closes this session. - </summary> - </member> - <member name="M:SuperSocket.SocketBase.IAppSession.Close(SuperSocket.SocketBase.CloseReason)"> - <summary> - Closes the session by the specified reason. - </summary> - <param name="reason">The close reason.</param> - </member> - <member name="M:SuperSocket.SocketBase.IAppSession.ProcessRequest(System.Byte[],System.Int32,System.Int32,System.Boolean)"> - <summary> - Processes the request. - </summary> - <param name="readBuffer">The read buffer.</param> - <param name="offset">The offset.</param> - <param name="length">The length.</param> - <param name="toBeCopied">if set to <c>true</c> [to be copied].</param> - <returns>return offset delta of next receiving buffer</returns> - </member> - <member name="M:SuperSocket.SocketBase.IAppSession.StartSession"> - <summary> - Starts the session. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.IAppSession.AppServer"> - <summary> - Gets the app server. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.IAppSession.SocketSession"> - <summary> - Gets the socket session of the AppSession. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.IAppSession.Items"> - <summary> - Gets the items. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.IAppSession.Config"> - <summary> - Gets the config of the server. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.IAppSession.LocalEndPoint"> - <summary> - Gets the local listening endpoint. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.IAppSession.LastActiveTime"> - <summary> - Gets or sets the last active time of the session. - </summary> - <value> - The last active time. - </value> - </member> - <member name="P:SuperSocket.SocketBase.IAppSession.StartTime"> - <summary> - Gets the start time of the session. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.IAppSession.Connected"> - <summary> - Gets a value indicating whether this <see cref="T:SuperSocket.SocketBase.IAppSession"/> is connected. - </summary> - <value> - <c>true</c> if connected; otherwise, <c>false</c>. - </value> - </member> - <member name="P:SuperSocket.SocketBase.IAppSession.Charset"> - <summary> - Gets or sets the charset which is used for transfering text message. - </summary> - <value>The charset.</value> - </member> - <member name="P:SuperSocket.SocketBase.IAppSession.PrevCommand"> - <summary> - Gets or sets the previous command. - </summary> - <value> - The prev command. - </value> - </member> - <member name="P:SuperSocket.SocketBase.IAppSession.CurrentCommand"> - <summary> - Gets or sets the current executing command. - </summary> - <value> - The current command. - </value> - </member> - <member name="P:SuperSocket.SocketBase.IAppSession.Logger"> - <summary> - Gets the logger assosiated with this session. - </summary> - </member> - <member name="T:SuperSocket.SocketBase.IAppSession`2"> - <summary> - The interface for appSession - </summary> - <typeparam name="TAppSession">The type of the app session.</typeparam> - <typeparam name="TRequestInfo">The type of the request info.</typeparam> - </member> - <member name="M:SuperSocket.SocketBase.IAppSession`2.Initialize(SuperSocket.SocketBase.IAppServer{`0,`1},SuperSocket.SocketBase.ISocketSession)"> - <summary> - Initializes the specified session. - </summary> - <param name="server">The server.</param> - <param name="socketSession">The socket session.</param> - </member> - <member name="T:SuperSocket.SocketBase.IConnectionFilter"> - <summary> - The basic interface of connection filter - </summary> - </member> - <member name="M:SuperSocket.SocketBase.IConnectionFilter.Initialize(System.String,SuperSocket.SocketBase.IAppServer)"> - <summary> - Initializes the connection filter - </summary> - <param name="name">The name.</param> - <param name="appServer">The app server.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.IConnectionFilter.AllowConnect(System.Net.IPEndPoint)"> - <summary> - Whether allows the connect according the remote endpoint - </summary> - <param name="remoteAddress">The remote address.</param> - <returns></returns> - </member> - <member name="P:SuperSocket.SocketBase.IConnectionFilter.Name"> - <summary> - Gets the name of the filter. - </summary> - </member> - <member name="T:SuperSocket.SocketBase.ISocketServer"> - <summary> - It is the basic interface of SocketServer, - SocketServer is the abstract server who really listen the comming sockets directly. - </summary> - </member> - <member name="M:SuperSocket.SocketBase.ISocketServer.Start"> - <summary> - Starts this instance. - </summary> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.ISocketServer.ResetSessionSecurity(SuperSocket.SocketBase.IAppSession,System.Security.Authentication.SslProtocols)"> - <summary> - Resets the session's security protocol. - </summary> - <param name="session">The session.</param> - <param name="security">The security protocol.</param> - </member> - <member name="M:SuperSocket.SocketBase.ISocketServer.Stop"> - <summary> - Stops this instance. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.ISocketServer.IsRunning"> - <summary> - Gets a value indicating whether this instance is running. - </summary> - <value> - <c>true</c> if this instance is running; otherwise, <c>false</c>. - </value> - </member> - <member name="P:SuperSocket.SocketBase.ISocketServer.SendingQueuePool"> - <summary> - Gets the information of the sending queue pool. - </summary> - <value> - The sending queue pool. - </value> - </member> - <member name="T:SuperSocket.SocketBase.ISocketServerFactory"> - <summary> - The interface for socket server factory - </summary> - </member> - <member name="M:SuperSocket.SocketBase.ISocketServerFactory.CreateSocketServer``1(SuperSocket.SocketBase.IAppServer,SuperSocket.SocketBase.ListenerInfo[],SuperSocket.SocketBase.Config.IServerConfig)"> - <summary> - Creates the socket server instance. - </summary> - <typeparam name="TRequestInfo">The type of the request info.</typeparam> - <param name="appServer">The app server.</param> - <param name="listeners">The listeners.</param> - <param name="config">The config.</param> - <returns></returns> - </member> - <member name="T:SuperSocket.SocketBase.CloseReason"> - <summary> - CloseReason enum - </summary> - </member> - <member name="F:SuperSocket.SocketBase.CloseReason.Unknown"> - <summary> - The socket is closed for unknown reason - </summary> - </member> - <member name="F:SuperSocket.SocketBase.CloseReason.ServerShutdown"> - <summary> - Close for server shutdown - </summary> - </member> - <member name="F:SuperSocket.SocketBase.CloseReason.ClientClosing"> - <summary> - The client close the socket - </summary> - </member> - <member name="F:SuperSocket.SocketBase.CloseReason.ServerClosing"> - <summary> - The server side close the socket - </summary> - </member> - <member name="F:SuperSocket.SocketBase.CloseReason.ApplicationError"> - <summary> - Application error - </summary> - </member> - <member name="F:SuperSocket.SocketBase.CloseReason.SocketError"> - <summary> - The socket is closed for a socket error - </summary> - </member> - <member name="F:SuperSocket.SocketBase.CloseReason.TimeOut"> - <summary> - The socket is closed by server for timeout - </summary> - </member> - <member name="F:SuperSocket.SocketBase.CloseReason.ProtocolError"> - <summary> - Protocol error - </summary> - </member> - <member name="F:SuperSocket.SocketBase.CloseReason.InternalError"> - <summary> - SuperSocket internal error - </summary> - </member> - <member name="T:SuperSocket.SocketBase.ISocketSession"> - <summary> - The interface for socket session - </summary> - </member> - <member name="M:SuperSocket.SocketBase.ISocketSession.Initialize(SuperSocket.SocketBase.IAppSession)"> - <summary> - Initializes the specified app session. - </summary> - <param name="appSession">The app session.</param> - </member> - <member name="M:SuperSocket.SocketBase.ISocketSession.Start"> - <summary> - Starts this instance. - </summary> - </member> - <member name="M:SuperSocket.SocketBase.ISocketSession.Close(SuperSocket.SocketBase.CloseReason)"> - <summary> - Closes the socket session for the specified reason. - </summary> - <param name="reason">The reason.</param> - </member> - <member name="M:SuperSocket.SocketBase.ISocketSession.TrySend(System.Collections.Generic.IList{System.ArraySegment{System.Byte}})"> - <summary> - Tries to send array segment. - </summary> - <param name="segments">The segments.</param> - </member> - <member name="M:SuperSocket.SocketBase.ISocketSession.TrySend(System.ArraySegment{System.Byte})"> - <summary> - Tries to send array segment. - </summary> - <param name="segment">The segment.</param> - </member> - <member name="M:SuperSocket.SocketBase.ISocketSession.ApplySecureProtocol"> - <summary> - Applies the secure protocol. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.ISocketSession.Client"> - <summary> - Gets the client socket. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.ISocketSession.LocalEndPoint"> - <summary> - Gets the local listening endpoint. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.ISocketSession.SecureProtocol"> - <summary> - Gets or sets the secure protocol. - </summary> - <value> - The secure protocol. - </value> - </member> - <member name="P:SuperSocket.SocketBase.ISocketSession.Closed"> - <summary> - Occurs when [closed]. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.ISocketSession.AppSession"> - <summary> - Gets the app session assosiated with this socket session. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.ISocketSession.OrigReceiveOffset"> - <summary> - Gets the original receive buffer offset. - </summary> - <value> - The original receive buffer offset. - </value> - </member> - <member name="T:SuperSocket.SocketBase.LoggerExtension"> - <summary> - Logger extension class - </summary> - </member> - <member name="M:SuperSocket.SocketBase.LoggerExtension.Error(SuperSocket.SocketBase.Logging.ILog,SuperSocket.SocketBase.ISessionBase,System.String,System.Exception)"> - <summary> - Logs the error - </summary> - <param name="logger">The logger.</param> - <param name="session">The session.</param> - <param name="title">The title.</param> - <param name="e">The e.</param> - </member> - <member name="M:SuperSocket.SocketBase.LoggerExtension.Error(SuperSocket.SocketBase.Logging.ILog,SuperSocket.SocketBase.ISessionBase,System.String)"> - <summary> - Logs the error - </summary> - <param name="logger">The logger.</param> - <param name="session">The session.</param> - <param name="message">The message.</param> - </member> - <member name="M:SuperSocket.SocketBase.LoggerExtension.Info(SuperSocket.SocketBase.Logging.ILog,SuperSocket.SocketBase.ISessionBase,System.String)"> - <summary> - Logs the information - </summary> - <param name="logger">The logger.</param> - <param name="session">The session.</param> - <param name="message">The message.</param> - </member> - <member name="M:SuperSocket.SocketBase.LoggerExtension.Debug(SuperSocket.SocketBase.Logging.ILog,SuperSocket.SocketBase.ISessionBase,System.String)"> - <summary> - Logs the debug message - </summary> - <param name="logger">The logger.</param> - <param name="session">The session.</param> - <param name="message">The message.</param> - </member> - <member name="M:SuperSocket.SocketBase.LoggerExtension.LogPerf(SuperSocket.SocketBase.IAppServer,System.String)"> - <summary> - Logs the performance message - </summary> - <param name="appServer">The app server.</param> - <param name="message">The message.</param> - </member> - <member name="T:SuperSocket.SocketBase.Protocol.BasicRequestInfoParser"> - <summary> - Basic request info parser, which parse request info by separating - </summary> - </member> - <member name="T:SuperSocket.SocketBase.Protocol.IRequestInfoParser`1"> - <summary> - The interface for request info parser - </summary> - </member> - <member name="M:SuperSocket.SocketBase.Protocol.IRequestInfoParser`1.ParseRequestInfo(System.String)"> - <summary> - Parses the request info from the source string. - </summary> - <param name="source">The source.</param> - <returns></returns> - </member> - <member name="F:SuperSocket.SocketBase.Protocol.BasicRequestInfoParser.DefaultInstance"> - <summary> - The default singlegton instance - </summary> - </member> - <member name="M:SuperSocket.SocketBase.Protocol.BasicRequestInfoParser.#ctor"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.Protocol.BasicRequestInfoParser"/> class. - </summary> - </member> - <member name="M:SuperSocket.SocketBase.Protocol.BasicRequestInfoParser.#ctor(System.String,System.String)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.Protocol.BasicRequestInfoParser"/> class. - </summary> - <param name="spliter">The spliter between command name and command parameters.</param> - <param name="parameterSpliter">The parameter spliter.</param> - </member> - <member name="M:SuperSocket.SocketBase.Protocol.BasicRequestInfoParser.ParseRequestInfo(System.String)"> - <summary> - Parses the request info. - </summary> - <param name="source">The source.</param> - <returns></returns> - </member> - <member name="T:SuperSocket.SocketBase.ServerState"> - <summary> - Server's state enum class - </summary> - </member> - <member name="F:SuperSocket.SocketBase.ServerState.NotInitialized"> - <summary> - Not initialized - </summary> - </member> - <member name="F:SuperSocket.SocketBase.ServerState.Initializing"> - <summary> - In initializing - </summary> - </member> - <member name="F:SuperSocket.SocketBase.ServerState.NotStarted"> - <summary> - Has been initialized, but not started - </summary> - </member> - <member name="F:SuperSocket.SocketBase.ServerState.Starting"> - <summary> - In starting - </summary> - </member> - <member name="F:SuperSocket.SocketBase.ServerState.Running"> - <summary> - In running - </summary> - </member> - <member name="F:SuperSocket.SocketBase.ServerState.Stopping"> - <summary> - In stopping - </summary> - </member> - <member name="T:SuperSocket.SocketBase.ServerSummary"> - <summary> - Server State - </summary> - </member> - <member name="P:SuperSocket.SocketBase.ServerSummary.Name"> - <summary> - Gets or sets the name. - </summary> - <value> - The name. - </value> - </member> - <member name="P:SuperSocket.SocketBase.ServerSummary.CollectedTime"> - <summary> - Gets or sets the collected time. - </summary> - <value> - The collected time. - </value> - </member> - <member name="P:SuperSocket.SocketBase.ServerSummary.StartedTime"> - <summary> - Gets or sets the started time. - </summary> - <value> - The started time. - </value> - </member> - <member name="P:SuperSocket.SocketBase.ServerSummary.IsRunning"> - <summary> - Gets or sets a value indicating whether this instance is running. - </summary> - <value> - <c>true</c> if this instance is running; otherwise, <c>false</c>. - </value> - </member> - <member name="P:SuperSocket.SocketBase.ServerSummary.TotalConnections"> - <summary> - Gets or sets the total count of the connections. - </summary> - <value> - The total count of the connections. - </value> - </member> - <member name="P:SuperSocket.SocketBase.ServerSummary.MaxConnectionNumber"> - <summary> - Gets or sets the maximum allowed connection number. - </summary> - <value> - The max connection number. - </value> - </member> - <member name="P:SuperSocket.SocketBase.ServerSummary.TotalHandledRequests"> - <summary> - Gets or sets the total handled requests count. - </summary> - <value> - The total handled requests count. - </value> - </member> - <member name="P:SuperSocket.SocketBase.ServerSummary.RequestHandlingSpeed"> - <summary> - Gets or sets the request handling speed, per second. - </summary> - <value> - The request handling speed. - </value> - </member> - <member name="P:SuperSocket.SocketBase.ServerSummary.Listeners"> - <summary> - Gets or sets the listeners. - </summary> - <value> - The listeners. - </value> - </member> - <member name="P:SuperSocket.SocketBase.ServerSummary.AvialableSendingQueueItems"> - <summary> - Gets or sets the avialable sending queue items. - </summary> - <value> - The avialable sending queue items. - </value> - </member> - <member name="P:SuperSocket.SocketBase.ServerSummary.TotalSendingQueueItems"> - <summary> - Gets or sets the total sending queue items. - </summary> - <value> - The total sending queue items. - </value> - </member> - <member name="T:SuperSocket.SocketBase.SessionHandler`1"> - <summary> - Used for session level event handler - </summary> - <typeparam name="TAppSession">the type of the target session</typeparam> - <param name="session">the target session</param> - </member> - <member name="T:SuperSocket.SocketBase.SessionHandler`2"> - <summary> - Used for session level event handler - </summary> - <typeparam name="TAppSession">the type of the target session</typeparam> - <typeparam name="TParam">the target session</typeparam> - <param name="session">the target session</param> - <param name="value">the event parameter</param> - </member> - <member name="T:SuperSocket.SocketBase.SocketMode"> - <summary> - Socket server running mode - </summary> - </member> - <member name="F:SuperSocket.SocketBase.SocketMode.Tcp"> - <summary> - Tcp mode - </summary> - </member> - <member name="F:SuperSocket.SocketBase.SocketMode.Udp"> - <summary> - Udp mode - </summary> - </member> - <member name="T:SuperSocket.SocketBase.AppServer"> - <summary> - AppServer class - </summary> - </member> - <member name="T:SuperSocket.SocketBase.AppServer`1"> - <summary> - AppServer class - </summary> - <typeparam name="TAppSession">The type of the app session.</typeparam> - </member> - <member name="T:SuperSocket.SocketBase.AppServer`2"> - <summary> - AppServer basic class - </summary> - <typeparam name="TAppSession">The type of the app session.</typeparam> - <typeparam name="TRequestInfo">The type of the request info.</typeparam> - </member> - <member name="T:SuperSocket.SocketBase.AppServerBase`2"> - <summary> - AppServer base class - </summary> - <typeparam name="TAppSession">The type of the app session.</typeparam> - <typeparam name="TRequestInfo">The type of the request info.</typeparam> - </member> - <member name="F:SuperSocket.SocketBase.AppServerBase`2.NullAppSession"> - <summary> - Null appSession instance - </summary> - </member> - <member name="F:SuperSocket.SocketBase.AppServerBase`2.m_StateCode"> - <summary> - the current state's code - </summary> - </member> - <member name="M:SuperSocket.SocketBase.AppServerBase`2.#ctor"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.AppServerBase`2"/> class. - </summary> - </member> - <member name="M:SuperSocket.SocketBase.AppServerBase`2.#ctor(SuperSocket.SocketBase.Protocol.IReceiveFilterFactory{`1})"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.AppServerBase`2"/> class. - </summary> - <param name="receiveFilterFactory">The Receive filter factory.</param> - </member> - <member name="M:SuperSocket.SocketBase.AppServerBase`2.GetCommandFilterAttributes(System.Type)"> - <summary> - Gets the filter attributes. - </summary> - <param name="type">The type.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.AppServerBase`2.SetupCommands(System.Collections.Generic.Dictionary{System.String,SuperSocket.SocketBase.Command.ICommand{`0,`1}})"> - <summary> - Setups the command into command dictionary - </summary> - <param name="discoveredCommands">The discovered commands.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.AppServerBase`2.Setup(SuperSocket.SocketBase.Config.IRootConfig,SuperSocket.SocketBase.Config.IServerConfig)"> - <summary> - Setups the specified root config. - </summary> - <param name="rootConfig">The root config.</param> - <param name="config">The config.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.AppServerBase`2.Setup(System.Int32)"> - <summary> - Setups with the specified port. - </summary> - <param name="port">The port.</param> - <returns>return setup result</returns> - </member> - <member name="M:SuperSocket.SocketBase.AppServerBase`2.Setup(SuperSocket.SocketBase.Config.IServerConfig,SuperSocket.SocketBase.ISocketServerFactory,SuperSocket.SocketBase.Protocol.IReceiveFilterFactory{`1},SuperSocket.SocketBase.Logging.ILogFactory,System.Collections.Generic.IEnumerable{SuperSocket.SocketBase.IConnectionFilter},System.Collections.Generic.IEnumerable{SuperSocket.SocketBase.Command.ICommandLoader})"> - <summary> - Setups with the specified config. - </summary> - <param name="config">The server config.</param> - <param name="socketServerFactory">The socket server factory.</param> - <param name="receiveFilterFactory">The receive filter factory.</param> - <param name="logFactory">The log factory.</param> - <param name="connectionFilters">The connection filters.</param> - <param name="commandLoaders">The command loaders.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.AppServerBase`2.Setup(SuperSocket.SocketBase.Config.IRootConfig,SuperSocket.SocketBase.Config.IServerConfig,SuperSocket.SocketBase.ISocketServerFactory,SuperSocket.SocketBase.Protocol.IReceiveFilterFactory{`1},SuperSocket.SocketBase.Logging.ILogFactory,System.Collections.Generic.IEnumerable{SuperSocket.SocketBase.IConnectionFilter},System.Collections.Generic.IEnumerable{SuperSocket.SocketBase.Command.ICommandLoader})"> - <summary> - Setups the specified root config, this method used for programming setup - </summary> - <param name="rootConfig">The root config.</param> - <param name="config">The server config.</param> - <param name="socketServerFactory">The socket server factory.</param> - <param name="receiveFilterFactory">The Receive filter factory.</param> - <param name="logFactory">The log factory.</param> - <param name="connectionFilters">The connection filters.</param> - <param name="commandLoaders">The command loaders.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.AppServerBase`2.Setup(System.String,System.Int32,SuperSocket.SocketBase.ISocketServerFactory,SuperSocket.SocketBase.Protocol.IReceiveFilterFactory{`1},SuperSocket.SocketBase.Logging.ILogFactory,System.Collections.Generic.IEnumerable{SuperSocket.SocketBase.IConnectionFilter},System.Collections.Generic.IEnumerable{SuperSocket.SocketBase.Command.ICommandLoader})"> - <summary> - Setups with the specified ip and port. - </summary> - <param name="ip">The ip.</param> - <param name="port">The port.</param> - <param name="socketServerFactory">The socket server factory.</param> - <param name="receiveFilterFactory">The Receive filter factory.</param> - <param name="logFactory">The log factory.</param> - <param name="connectionFilters">The connection filters.</param> - <param name="commandLoaders">The command loaders.</param> - <returns>return setup result</returns> - </member> - <member name="M:SuperSocket.SocketBase.AppServerBase`2.SuperSocket#SocketBase#IWorkItem#Setup(SuperSocket.SocketBase.IBootstrap,SuperSocket.SocketBase.Config.IServerConfig,SuperSocket.SocketBase.Provider.ProviderFactoryInfo[])"> - <summary> - Setups the specified root config. - </summary> - <param name="bootstrap">The bootstrap.</param> - <param name="config">The socket server instance config.</param> - <param name="factories">The factories.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.AppServerBase`2.CreateLogger(System.String)"> - <summary> - Creates the logger for the AppServer. - </summary> - <param name="loggerName">Name of the logger.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.AppServerBase`2.SetupSecurity(SuperSocket.SocketBase.Config.IServerConfig)"> - <summary> - Setups the security option of socket communications. - </summary> - <param name="config">The config of the server instance.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.AppServerBase`2.GetCertificate(SuperSocket.SocketBase.Config.ICertificateConfig)"> - <summary> - Gets the certificate from server configuguration. - </summary> - <param name="certificate">The certificate config.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.AppServerBase`2.SetupSocketServer"> - <summary> - Setups the socket server.instance - </summary> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.AppServerBase`2.SetupListeners(SuperSocket.SocketBase.Config.IServerConfig)"> - <summary> - Setups the listeners base on server configuration - </summary> - <param name="config">The config.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.AppServerBase`2.Start"> - <summary> - Starts this server instance. - </summary> - <returns> - return true if start successfull, else false - </returns> - </member> - <member name="M:SuperSocket.SocketBase.AppServerBase`2.OnStartup"> - <summary> - Called when [startup]. - </summary> - </member> - <member name="M:SuperSocket.SocketBase.AppServerBase`2.OnStopped"> - <summary> - Called when [stopped]. - </summary> - </member> - <member name="M:SuperSocket.SocketBase.AppServerBase`2.Stop"> - <summary> - Stops this server instance. - </summary> - </member> - <member name="M:SuperSocket.SocketBase.AppServerBase`2.GetCommandByName(System.String)"> - <summary> - Gets command by command name. - </summary> - <param name="commandName">Name of the command.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.AppServerBase`2.OnRawDataReceived(SuperSocket.SocketBase.IAppSession,System.Byte[],System.Int32,System.Int32)"> - <summary> - Called when [raw data received]. - </summary> - <param name="session">The session.</param> - <param name="buffer">The buffer.</param> - <param name="offset">The offset.</param> - <param name="length">The length.</param> - </member> - <member name="M:SuperSocket.SocketBase.AppServerBase`2.ExecuteCommand(`0,`1)"> - <summary> - Executes the command. - </summary> - <param name="session">The session.</param> - <param name="requestInfo">The request info.</param> - </member> - <member name="M:SuperSocket.SocketBase.AppServerBase`2.ExecuteCommand(SuperSocket.SocketBase.IAppSession,`1)"> - <summary> - Executes the command for the session. - </summary> - <param name="session">The session.</param> - <param name="requestInfo">The request info.</param> - </member> - <member name="M:SuperSocket.SocketBase.AppServerBase`2.SuperSocket#SocketBase#IRequestHandler{TRequestInfo}#ExecuteCommand(SuperSocket.SocketBase.IAppSession,`1)"> - <summary> - Executes the command. - </summary> - <param name="session">The session.</param> - <param name="requestInfo">The request info.</param> - </member> - <member name="M:SuperSocket.SocketBase.AppServerBase`2.ExecuteConnectionFilters(System.Net.IPEndPoint)"> - <summary> - Executes the connection filters. - </summary> - <param name="remoteAddress">The remote address.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.AppServerBase`2.SuperSocket#SocketBase#IAppServer#CreateAppSession(SuperSocket.SocketBase.ISocketSession)"> - <summary> - Creates the app session. - </summary> - <param name="socketSession">The socket session.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.AppServerBase`2.RegisterSession(System.String,`0)"> - <summary> - Registers the session into session container. - </summary> - <param name="sessionID">The session ID.</param> - <param name="appSession">The app session.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.AppServerBase`2.OnNewSessionConnected(`0)"> - <summary> - Called when [new session connected]. - </summary> - <param name="session">The session.</param> - </member> - <member name="M:SuperSocket.SocketBase.AppServerBase`2.ResetSessionSecurity(SuperSocket.SocketBase.IAppSession,System.Security.Authentication.SslProtocols)"> - <summary> - Resets the session's security protocol. - </summary> - <param name="session">The session.</param> - <param name="security">The security protocol.</param> - </member> - <member name="M:SuperSocket.SocketBase.AppServerBase`2.OnSocketSessionClosed(SuperSocket.SocketBase.ISocketSession,SuperSocket.SocketBase.CloseReason)"> - <summary> - Called when [socket session closed]. - </summary> - <param name="session">The socket session.</param> - <param name="reason">The reason.</param> - </member> - <member name="M:SuperSocket.SocketBase.AppServerBase`2.OnSessionClosed(`0,SuperSocket.SocketBase.CloseReason)"> - <summary> - Called when [session closed]. - </summary> - <param name="session">The appSession.</param> - <param name="reason">The reason.</param> - </member> - <member name="M:SuperSocket.SocketBase.AppServerBase`2.GetAppSessionByID(System.String)"> - <summary> - Gets the app session by ID. - </summary> - <param name="sessionID">The session ID.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.AppServerBase`2.SuperSocket#SocketBase#IAppServer#GetAppSessionByID(System.String)"> - <summary> - Gets the app session by ID. - </summary> - <param name="sessionID"></param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.AppServerBase`2.GetSessions(System.Func{`0,System.Boolean})"> - <summary> - Gets the matched sessions from sessions snapshot. - </summary> - <param name="critera">The prediction critera.</param> - </member> - <member name="M:SuperSocket.SocketBase.AppServerBase`2.GetAllSessions"> - <summary> - Gets all sessions in sessions snapshot. - </summary> - </member> - <member name="M:SuperSocket.SocketBase.AppServerBase`2.UpdateServerSummary(SuperSocket.SocketBase.ServerSummary)"> - <summary> - Updates the summary of the server. - </summary> - <param name="serverSummary">The server summary.</param> - </member> - <member name="M:SuperSocket.SocketBase.AppServerBase`2.OnServerSummaryCollected(SuperSocket.SocketBase.NodeSummary,SuperSocket.SocketBase.ServerSummary)"> - <summary> - Called when [summary data collected], you can override this method to get collected performance data - </summary> - <param name="nodeSummary">The node summary.</param> - <param name="serverSummary">The server summary.</param> - </member> - <member name="M:SuperSocket.SocketBase.AppServerBase`2.Dispose"> - <summary> - Releases unmanaged and - optionally - managed resources - </summary> - </member> - <member name="P:SuperSocket.SocketBase.AppServerBase`2.Config"> - <summary> - Gets the server's config. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.AppServerBase`2.State"> - <summary> - Gets the current state of the work item. - </summary> - <value> - The state. - </value> - </member> - <member name="P:SuperSocket.SocketBase.AppServerBase`2.Certificate"> - <summary> - Gets the certificate of current server. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.AppServerBase`2.ReceiveFilterFactory"> - <summary> - Gets or sets the receive filter factory. - </summary> - <value> - The receive filter factory. - </value> - </member> - <member name="P:SuperSocket.SocketBase.AppServerBase`2.SuperSocket#SocketBase#IAppServer#ReceiveFilterFactory"> - <summary> - Gets the Receive filter factory. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.AppServerBase`2.BasicSecurity"> - <summary> - Gets the basic transfer layer security protocol. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.AppServerBase`2.RootConfig"> - <summary> - Gets the root config. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.AppServerBase`2.Logger"> - <summary> - Gets the logger assosiated with this object. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.AppServerBase`2.Bootstrap"> - <summary> - Gets the bootstrap of this appServer instance. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.AppServerBase`2.TotalHandledRequests"> - <summary> - Gets the total handled requests number. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.AppServerBase`2.Listeners"> - <summary> - Gets or sets the listeners inforamtion. - </summary> - <value> - The listeners. - </value> - </member> - <member name="P:SuperSocket.SocketBase.AppServerBase`2.StartedTime"> - <summary> - Gets the started time of this server instance. - </summary> - <value> - The started time. - </value> - </member> - <member name="P:SuperSocket.SocketBase.AppServerBase`2.LogFactory"> - <summary> - Gets or sets the log factory. - </summary> - <value> - The log factory. - </value> - </member> - <member name="P:SuperSocket.SocketBase.AppServerBase`2.Name"> - <summary> - Gets the name of the server instance. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.AppServerBase`2.SuperSocket#SocketBase#ISocketServerAccessor#SocketServer"> - <summary> - Gets the socket server. - </summary> - <value> - The socket server. - </value> - </member> - <member name="E:SuperSocket.SocketBase.AppServerBase`2.SuperSocket#SocketBase#IRawDataProcessor{TAppSession}#RawDataReceived"> - <summary> - Gets or sets the raw binary data received event handler. - TAppSession: session - byte[]: receive buffer - int: receive buffer offset - int: receive lenght - bool: whether process the received data further - </summary> - </member> - <member name="E:SuperSocket.SocketBase.AppServerBase`2.NewRequestReceived"> - <summary> - Occurs when a full request item received. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.AppServerBase`2.ConnectionFilters"> - <summary> - Gets or sets the server's connection filter - </summary> - <value> - The server's connection filters - </value> - </member> - <member name="E:SuperSocket.SocketBase.AppServerBase`2.NewSessionConnected"> - <summary> - The action which will be executed after a new session connect - </summary> - </member> - <member name="E:SuperSocket.SocketBase.AppServerBase`2.SessionClosed"> - <summary> - Gets/sets the session closed event handler. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.AppServerBase`2.SessionCount"> - <summary> - Gets the total session count. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.AppServerBase`2.ServerSummaryType"> - <summary> - Gets the type of the server state. The type must inherit from ServerState - </summary> - <value> - The type of the server state. - </value> - </member> - <member name="P:SuperSocket.SocketBase.AppServerBase`2.Summary"> - <summary> - Gets the state of the server. - </summary> - <value> - The state data of the server. - </value> - </member> - <member name="M:SuperSocket.SocketBase.AppServer`2.#ctor"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.AppServer`2"/> class. - </summary> - </member> - <member name="M:SuperSocket.SocketBase.AppServer`2.#ctor(SuperSocket.SocketBase.Protocol.IReceiveFilterFactory{`1})"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.AppServer`2"/> class. - </summary> - <param name="protocol">The protocol.</param> - </member> - <member name="M:SuperSocket.SocketBase.AppServer`2.Start"> - <summary> - Starts this AppServer instance. - </summary> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.AppServer`2.RegisterSession(System.String,`0)"> - <summary> - Registers the session into the session container. - </summary> - <param name="sessionID">The session ID.</param> - <param name="appSession">The app session.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.AppServer`2.GetAppSessionByID(System.String)"> - <summary> - Gets the app session by ID. - </summary> - <param name="sessionID">The session ID.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.AppServer`2.OnSessionClosed(`0,SuperSocket.SocketBase.CloseReason)"> - <summary> - Called when [socket session closed]. - </summary> - <param name="session">The session.</param> - <param name="reason">The reason.</param> - </member> - <member name="M:SuperSocket.SocketBase.AppServer`2.ClearIdleSession(System.Object)"> - <summary> - Clears the idle session. - </summary> - <param name="state">The state.</param> - </member> - <member name="M:SuperSocket.SocketBase.AppServer`2.GetSessions(System.Func{`0,System.Boolean})"> - <summary> - Gets the matched sessions from sessions snapshot. - </summary> - <param name="critera">The prediction critera.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.AppServer`2.GetAllSessions"> - <summary> - Gets all sessions in sessions snapshot. - </summary> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.AppServer`2.Stop"> - <summary> - Stops this instance. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.AppServer`2.SessionCount"> - <summary> - Gets the total session count. - </summary> - </member> - <member name="M:SuperSocket.SocketBase.AppServer`1.#ctor"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.AppServer`1"/> class. - </summary> - </member> - <member name="M:SuperSocket.SocketBase.AppServer`1.#ctor(SuperSocket.SocketBase.Protocol.IReceiveFilterFactory{SuperSocket.SocketBase.Protocol.StringRequestInfo})"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.AppServer`1"/> class. - </summary> - <param name="receiveFilterFactory">The Receive filter factory.</param> - </member> - <member name="M:SuperSocket.SocketBase.AppServer.#ctor"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.AppServer"/> class. - </summary> - </member> - <member name="M:SuperSocket.SocketBase.AppServer.#ctor(SuperSocket.SocketBase.Protocol.IReceiveFilterFactory{SuperSocket.SocketBase.Protocol.StringRequestInfo})"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.AppServer"/> class. - </summary> - <param name="receiveFilterFactory">The Receive filter factory.</param> - </member> - <member name="T:SuperSocket.SocketBase.AppSession`2"> - <summary> - AppSession base class - </summary> - <typeparam name="TAppSession">The type of the app session.</typeparam> - <typeparam name="TRequestInfo">The type of the request info.</typeparam> - </member> - <member name="M:SuperSocket.SocketBase.AppSession`2.#ctor"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.AppSession`2"/> class. - </summary> - </member> - <member name="M:SuperSocket.SocketBase.AppSession`2.Initialize(SuperSocket.SocketBase.IAppServer{`0,`1},SuperSocket.SocketBase.ISocketSession)"> - <summary> - Initializes the specified app session by AppServer and SocketSession. - </summary> - <param name="appServer">The app server.</param> - <param name="socketSession">The socket session.</param> - </member> - <member name="M:SuperSocket.SocketBase.AppSession`2.SuperSocket#SocketBase#IAppSession#StartSession"> - <summary> - Starts the session. - </summary> - </member> - <member name="M:SuperSocket.SocketBase.AppSession`2.OnInit"> - <summary> - Called when [init]. - </summary> - </member> - <member name="M:SuperSocket.SocketBase.AppSession`2.OnSessionStarted"> - <summary> - Called when [session started]. - </summary> - </member> - <member name="M:SuperSocket.SocketBase.AppSession`2.OnSessionClosed(SuperSocket.SocketBase.CloseReason)"> - <summary> - Called when [session closed]. - </summary> - <param name="reason">The reason.</param> - </member> - <member name="M:SuperSocket.SocketBase.AppSession`2.HandleException(System.Exception)"> - <summary> - Handles the exceptional error, it only handles application error. - </summary> - <param name="e">The exception.</param> - </member> - <member name="M:SuperSocket.SocketBase.AppSession`2.HandleUnknownRequest(`1)"> - <summary> - Handles the unknown request. - </summary> - <param name="requestInfo">The request info.</param> - </member> - <member name="M:SuperSocket.SocketBase.AppSession`2.Close(SuperSocket.SocketBase.CloseReason)"> - <summary> - Closes the session by the specified reason. - </summary> - <param name="reason">The close reason.</param> - </member> - <member name="M:SuperSocket.SocketBase.AppSession`2.Close"> - <summary> - Closes this session. - </summary> - </member> - <member name="M:SuperSocket.SocketBase.AppSession`2.TrySend(System.String)"> - <summary> - Try to send the message to client. - </summary> - <param name="message">The message which will be sent.</param> - <returns>Indicate whether the message was pushed into the sending queue</returns> - </member> - <member name="M:SuperSocket.SocketBase.AppSession`2.Send(System.String)"> - <summary> - Sends the message to client. - </summary> - <param name="message">The message which will be sent.</param> - </member> - <member name="M:SuperSocket.SocketBase.AppSession`2.SendResponse(System.String)"> - <summary> - Sends the response. - </summary> - <param name="message">The message which will be sent.</param> - <returns>Indicate whether the message was pushed into the sending queue</returns> - </member> - <member name="M:SuperSocket.SocketBase.AppSession`2.TrySend(System.Byte[],System.Int32,System.Int32)"> - <summary> - Try to send the data to client. - </summary> - <param name="data">The data which will be sent.</param> - <param name="offset">The offset.</param> - <param name="length">The length.</param> - <returns>Indicate whether the message was pushed into the sending queue</returns> - </member> - <member name="M:SuperSocket.SocketBase.AppSession`2.Send(System.Byte[],System.Int32,System.Int32)"> - <summary> - Sends the data to client. - </summary> - <param name="data">The data which will be sent.</param> - <param name="offset">The offset.</param> - <param name="length">The length.</param> - </member> - <member name="M:SuperSocket.SocketBase.AppSession`2.SendResponse(System.Byte[],System.Int32,System.Int32)"> - <summary> - Sends the response. - </summary> - <param name="data">The data which will be sent.</param> - <param name="offset">The offset.</param> - <param name="length">The length.</param> - </member> - <member name="M:SuperSocket.SocketBase.AppSession`2.TrySend(System.ArraySegment{System.Byte})"> - <summary> - Try to send the data segment to client. - </summary> - <param name="segment">The segment which will be sent.</param> - <returns>Indicate whether the message was pushed into the sending queue</returns> - </member> - <member name="M:SuperSocket.SocketBase.AppSession`2.Send(System.ArraySegment{System.Byte})"> - <summary> - Sends the data segment to client. - </summary> - <param name="segment">The segment which will be sent.</param> - </member> - <member name="M:SuperSocket.SocketBase.AppSession`2.SendResponse(System.ArraySegment{System.Byte})"> - <summary> - Sends the response. - </summary> - <param name="segment">The segment which will be sent.</param> - <returns>Indicate whether the message was pushed into the sending queue; if it returns false, the sending queue may be full or the socket is not connected</returns> - </member> - <member name="M:SuperSocket.SocketBase.AppSession`2.TrySend(System.Collections.Generic.IList{System.ArraySegment{System.Byte}})"> - <summary> - Try to send the data segments to clinet. - </summary> - <param name="segments">The segments.</param> - <returns>Indicate whether the message was pushed into the sending queue; if it returns false, the sending queue may be full or the socket is not connected</returns> - </member> - <member name="M:SuperSocket.SocketBase.AppSession`2.Send(System.Collections.Generic.IList{System.ArraySegment{System.Byte}})"> - <summary> - Sends the data segments to clinet. - </summary> - <param name="segments">The segments.</param> - </member> - <member name="M:SuperSocket.SocketBase.AppSession`2.SendResponse(System.Collections.Generic.IList{System.ArraySegment{System.Byte}})"> - <summary> - Sends the response. - </summary> - <param name="segments">The segments.</param> - <returns>Indicate whether the message was pushed into the sending queue</returns> - </member> - <member name="M:SuperSocket.SocketBase.AppSession`2.Send(System.String,System.Object[])"> - <summary> - Sends the response. - </summary> - <param name="message">The message which will be sent.</param> - <param name="paramValues">The parameter values.</param> - </member> - <member name="M:SuperSocket.SocketBase.AppSession`2.SendResponse(System.String,System.Object[])"> - <summary> - Sends the response. - </summary> - <param name="message">The message which will be sent.</param> - <param name="paramValues">The parameter values.</param> - <returns>Indicate whether the message was pushed into the sending queue</returns> - </member> - <member name="M:SuperSocket.SocketBase.AppSession`2.SetNextReceiveFilter(SuperSocket.SocketBase.Protocol.IReceiveFilter{`1})"> - <summary> - Sets the next Receive filter which will be used when next data block received - </summary> - <param name="nextReceiveFilter">The next receive filter.</param> - </member> - <member name="M:SuperSocket.SocketBase.AppSession`2.FilterRequest(System.Byte[],System.Int32,System.Int32,System.Boolean,System.Int32@,System.Int32@)"> - <summary> - Filters the request. - </summary> - <param name="readBuffer">The read buffer.</param> - <param name="offset">The offset.</param> - <param name="length">The length.</param> - <param name="toBeCopied">if set to <c>true</c> [to be copied].</param> - <param name="rest">The rest, the size of the data which has not been processed</param> - <param name="offsetDelta">return offset delta of next receiving buffer.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.AppSession`2.SuperSocket#SocketBase#IAppSession#ProcessRequest(System.Byte[],System.Int32,System.Int32,System.Boolean)"> - <summary> - Processes the request data. - </summary> - <param name="readBuffer">The read buffer.</param> - <param name="offset">The offset.</param> - <param name="length">The length.</param> - <param name="toBeCopied">if set to <c>true</c> [to be copied].</param> - <returns> - return offset delta of next receiving buffer - </returns> - </member> - <member name="P:SuperSocket.SocketBase.AppSession`2.AppServer"> - <summary> - Gets the app server instance assosiated with the session. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.AppSession`2.SuperSocket#SocketBase#IAppSession#AppServer"> - <summary> - Gets the app server instance assosiated with the session. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.AppSession`2.Charset"> - <summary> - Gets or sets the charset which is used for transfering text message. - </summary> - <value> - The charset. - </value> - </member> - <member name="P:SuperSocket.SocketBase.AppSession`2.Items"> - <summary> - Gets the items dictionary, only support 10 items maximum - </summary> - </member> - <member name="P:SuperSocket.SocketBase.AppSession`2.Connected"> - <summary> - Gets a value indicating whether this <see cref="T:SuperSocket.SocketBase.IAppSession"/> is connected. - </summary> - <value> - <c>true</c> if connected; otherwise, <c>false</c>. - </value> - </member> - <member name="P:SuperSocket.SocketBase.AppSession`2.PrevCommand"> - <summary> - Gets or sets the previous command. - </summary> - <value> - The prev command. - </value> - </member> - <member name="P:SuperSocket.SocketBase.AppSession`2.CurrentCommand"> - <summary> - Gets or sets the current executing command. - </summary> - <value> - The current command. - </value> - </member> - <member name="P:SuperSocket.SocketBase.AppSession`2.SecureProtocol"> - <summary> - Gets or sets the secure protocol of transportation layer. - </summary> - <value> - The secure protocol. - </value> - </member> - <member name="P:SuperSocket.SocketBase.AppSession`2.LocalEndPoint"> - <summary> - Gets the local listening endpoint. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.AppSession`2.RemoteEndPoint"> - <summary> - Gets the remote endpoint of client. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.AppSession`2.Logger"> - <summary> - Gets the logger. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.AppSession`2.LastActiveTime"> - <summary> - Gets or sets the last active time of the session. - </summary> - <value> - The last active time. - </value> - </member> - <member name="P:SuperSocket.SocketBase.AppSession`2.StartTime"> - <summary> - Gets the start time of the session. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.AppSession`2.SessionID"> - <summary> - Gets the session ID. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.AppSession`2.SocketSession"> - <summary> - Gets the socket session of the AppSession. - </summary> - </member> - <member name="P:SuperSocket.SocketBase.AppSession`2.Config"> - <summary> - Gets the config of the server. - </summary> - </member> - <member name="T:SuperSocket.SocketBase.AppSession`1"> - <summary> - AppServer basic class for whose request infoe type is StringRequestInfo - </summary> - <typeparam name="TAppSession">The type of the app session.</typeparam> - </member> - <member name="M:SuperSocket.SocketBase.AppSession`1.#ctor"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.AppSession`1"/> class. - </summary> - </member> - <member name="M:SuperSocket.SocketBase.AppSession`1.#ctor(System.Boolean)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketBase.AppSession`1"/> class. - </summary> - <param name="appendNewLineForResponse">if set to <c>true</c> [append new line for response].</param> - </member> - <member name="M:SuperSocket.SocketBase.AppSession`1.HandleUnknownRequest(SuperSocket.SocketBase.Protocol.StringRequestInfo)"> - <summary> - Handles the unknown request. - </summary> - <param name="requestInfo">The request info.</param> - </member> - <member name="M:SuperSocket.SocketBase.AppSession`1.ProcessSendingMessage(System.String)"> - <summary> - Processes the sending message. - </summary> - <param name="rawMessage">The raw message.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.AppSession`1.Send(System.String)"> - <summary> - Sends the specified message. - </summary> - <param name="message">The message.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketBase.AppSession`1.SendResponse(System.String)"> - <summary> - Sends the response. - </summary> - <param name="message">The message.</param> - <returns>Indicate whether the message was pushed into the sending queue</returns> - </member> - <member name="M:SuperSocket.SocketBase.AppSession`1.Send(System.String,System.Object[])"> - <summary> - Sends the response. - </summary> - <param name="message">The message.</param> - <param name="paramValues">The param values.</param> - <returns>Indicate whether the message was pushed into the sending queue</returns> - </member> - <member name="M:SuperSocket.SocketBase.AppSession`1.SendResponse(System.String,System.Object[])"> - <summary> - Sends the response. - </summary> - <param name="message">The message.</param> - <param name="paramValues">The param values.</param> - <returns>Indicate whether the message was pushed into the sending queue</returns> - </member> - <member name="T:SuperSocket.SocketBase.AppSession"> - <summary> - AppServer basic class for whose request infoe type is StringRequestInfo - </summary> - </member> - <member name="T:SuperSocket.SocketBase.Command.StringCommandBase`1"> - <summary> - A command type for whose request info type is StringRequestInfo - </summary> - <typeparam name="TAppSession">The type of the app session.</typeparam> - </member> - <member name="T:SuperSocket.SocketBase.Command.StringCommandBase"> - <summary> - A command type for whose request info type is StringRequestInfo - </summary> - </member> - </members> -</doc> diff --git a/cb-tools/SuperWebSocket/SuperSocket.SocketEngine.XML b/cb-tools/SuperWebSocket/SuperSocket.SocketEngine.XML deleted file mode 100644 index 95df68de..00000000 --- a/cb-tools/SuperWebSocket/SuperSocket.SocketEngine.XML +++ /dev/null @@ -1,1045 +0,0 @@ -<?xml version="1.0"?> -<doc> - <assembly> - <name>SuperSocket.SocketEngine</name> - </assembly> - <members> - <member name="T:SuperSocket.SocketEngine.AppDomainAppServer"> - <summary> - AppDomainAppServer - </summary> - </member> - <member name="M:SuperSocket.SocketEngine.AppDomainAppServer.#ctor(System.String)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketEngine.AppDomainAppServer"/> class. - </summary> - <param name="serviceTypeName">Name of the service type.</param> - </member> - <member name="M:SuperSocket.SocketEngine.AppDomainAppServer.#ctor(System.Type)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketEngine.AppDomainAppServer"/> class. - </summary> - <param name="serviceType">Type of the service.</param> - </member> - <member name="M:SuperSocket.SocketEngine.AppDomainAppServer.Setup(SuperSocket.SocketBase.IBootstrap,SuperSocket.SocketBase.Config.IServerConfig,SuperSocket.SocketBase.Provider.ProviderFactoryInfo[])"> - <summary> - Setups the specified root config. - </summary> - <param name="bootstrap">The bootstrap.</param> - <param name="config">The socket server instance config.</param> - <param name="factories">The factories.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketEngine.AppDomainAppServer.Start"> - <summary> - Starts this server instance. - </summary> - <returns> - return true if start successfull, else false - </returns> - </member> - <member name="M:SuperSocket.SocketEngine.AppDomainAppServer.Stop"> - <summary> - Stops this server instance. - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.AppDomainAppServer.Name"> - <summary> - Gets the name of the server instance. - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.AppDomainAppServer.State"> - <summary> - Gets the current state of the work item. - </summary> - <value> - The state. - </value> - </member> - <member name="P:SuperSocket.SocketEngine.AppDomainAppServer.SessionCount"> - <summary> - Gets the total session count. - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.AppDomainAppServer.Summary"> - <summary> - Gets the state data of the server. - </summary> - <value> - The state of the server. - </value> - </member> - <member name="T:SuperSocket.SocketEngine.AppDomainBootstrap"> - <summary> - AppDomainBootstrap - </summary> - </member> - <member name="M:SuperSocket.SocketEngine.AppDomainBootstrap.#ctor(SuperSocket.SocketBase.Config.IConfigurationSource)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketEngine.AppDomainBootstrap"/> class. - </summary> - </member> - <member name="M:SuperSocket.SocketEngine.AppDomainBootstrap.Initialize"> - <summary> - Initializes the bootstrap with the configuration - </summary> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketEngine.AppDomainBootstrap.Initialize(System.Func{SuperSocket.SocketBase.Config.IServerConfig,SuperSocket.SocketBase.Config.IServerConfig})"> - <summary> - Initializes the bootstrap with the configuration and config resolver. - </summary> - <param name="serverConfigResolver">The server config resolver.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketEngine.AppDomainBootstrap.Initialize(SuperSocket.SocketBase.Logging.ILogFactory)"> - <summary> - Initializes the bootstrap with the configuration and config resolver. - </summary> - <param name="logFactory">The log factory.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketEngine.AppDomainBootstrap.Initialize(System.Collections.Generic.IDictionary{System.String,System.Net.IPEndPoint})"> - <summary> - Initializes the bootstrap with a listen endpoint replacement dictionary - </summary> - <param name="listenEndPointReplacement">The listen end point replacement.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketEngine.AppDomainBootstrap.Initialize(System.Func{SuperSocket.SocketBase.Config.IServerConfig,SuperSocket.SocketBase.Config.IServerConfig},SuperSocket.SocketBase.Logging.ILogFactory)"> - <summary> - Initializes the bootstrap with the configuration - </summary> - <param name="serverConfigResolver">The server config resolver.</param> - <param name="logFactory">The log factory.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketEngine.AppDomainBootstrap.Start"> - <summary> - Starts this bootstrap. - </summary> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketEngine.AppDomainBootstrap.Stop"> - <summary> - Stops this bootstrap. - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.AppDomainBootstrap.AppServers"> - <summary> - Gets all the app servers running in this bootstrap - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.AppDomainBootstrap.Config"> - <summary> - Gets the config. - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.AppDomainBootstrap.StartupConfigFile"> - <summary> - Gets the startup config file. - </summary> - </member> - <member name="M:SuperSocket.SocketEngine.WorkItemFactoryInfoLoader.ValidateProviderType(System.String)"> - <summary> - Validates the type of the provider, needn't validate in default mode, because it will be validate later when initializing. - </summary> - <param name="typeName">Name of the type.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketEngine.WorkItemFactoryInfoLoader.Dispose"> - <summary> - Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - </summary> - </member> - <member name="T:SuperSocket.SocketEngine.DefaultBootstrap"> - <summary> - SuperSocket default bootstrap - </summary> - </member> - <member name="F:SuperSocket.SocketEngine.DefaultBootstrap.m_Initialized"> - <summary> - Indicates whether the bootstrap is initialized - </summary> - </member> - <member name="F:SuperSocket.SocketEngine.DefaultBootstrap.m_Config"> - <summary> - Global configuration - </summary> - </member> - <member name="F:SuperSocket.SocketEngine.DefaultBootstrap.m_GlobalLog"> - <summary> - Global log - </summary> - </member> - <member name="M:SuperSocket.SocketEngine.DefaultBootstrap.#ctor(System.Collections.Generic.IEnumerable{SuperSocket.SocketBase.IWorkItem})"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketEngine.DefaultBootstrap"/> class. - </summary> - <param name="appServers">The app servers.</param> - </member> - <member name="M:SuperSocket.SocketEngine.DefaultBootstrap.#ctor(SuperSocket.SocketBase.Config.IRootConfig,System.Collections.Generic.IEnumerable{SuperSocket.SocketBase.IWorkItem})"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketEngine.DefaultBootstrap"/> class. - </summary> - <param name="rootConfig">The root config.</param> - <param name="appServers">The app servers.</param> - </member> - <member name="M:SuperSocket.SocketEngine.DefaultBootstrap.#ctor(SuperSocket.SocketBase.Config.IRootConfig,System.Collections.Generic.IEnumerable{SuperSocket.SocketBase.IWorkItem},SuperSocket.SocketBase.Logging.ILogFactory)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketEngine.DefaultBootstrap"/> class. - </summary> - <param name="rootConfig">The root config.</param> - <param name="appServers">The app servers.</param> - <param name="logFactory">The log factory.</param> - </member> - <member name="M:SuperSocket.SocketEngine.DefaultBootstrap.#ctor(SuperSocket.SocketBase.Config.IConfigurationSource)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketEngine.DefaultBootstrap"/> class. - </summary> - <param name="config">The config.</param> - </member> - <member name="M:SuperSocket.SocketEngine.DefaultBootstrap.#ctor(SuperSocket.SocketBase.Config.IConfigurationSource,System.String)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketEngine.DefaultBootstrap"/> class. - </summary> - <param name="config">The config.</param> - <param name="startupConfigFile">The startup config file.</param> - </member> - <member name="M:SuperSocket.SocketEngine.DefaultBootstrap.CreateWorkItemInstance(System.String)"> - <summary> - Creates the work item instance. - </summary> - <param name="serviceTypeName">Name of the service type.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketEngine.DefaultBootstrap.GetWorkItemFactoryInfoLoader(SuperSocket.SocketBase.Config.IConfigurationSource,SuperSocket.SocketBase.Logging.ILogFactory)"> - <summary> - Gets the work item factory info loader. - </summary> - <param name="config">The config.</param> - <param name="logFactory">The log factory.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketEngine.DefaultBootstrap.Initialize(System.Collections.Generic.IDictionary{System.String,System.Net.IPEndPoint})"> - <summary> - Initializes the bootstrap with a listen endpoint replacement dictionary - </summary> - <param name="listenEndPointReplacement">The listen end point replacement.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketEngine.DefaultBootstrap.Initialize(System.Func{SuperSocket.SocketBase.Config.IServerConfig,SuperSocket.SocketBase.Config.IServerConfig},SuperSocket.SocketBase.Logging.ILogFactory)"> - <summary> - Initializes the bootstrap with the configuration, config resolver and log factory. - </summary> - <param name="serverConfigResolver">The server config resolver.</param> - <param name="logFactory">The log factory.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketEngine.DefaultBootstrap.Initialize(System.Func{SuperSocket.SocketBase.Config.IServerConfig,SuperSocket.SocketBase.Config.IServerConfig})"> - <summary> - Initializes the bootstrap with the configuration and config resolver. - </summary> - <param name="serverConfigResolver">The server config resolver.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketEngine.DefaultBootstrap.Initialize(SuperSocket.SocketBase.Logging.ILogFactory)"> - <summary> - Initializes the bootstrap with the configuration - </summary> - <param name="logFactory">The log factory.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketEngine.DefaultBootstrap.Initialize"> - <summary> - Initializes the bootstrap with the configuration - </summary> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketEngine.DefaultBootstrap.Start"> - <summary> - Starts this bootstrap. - </summary> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketEngine.DefaultBootstrap.Stop"> - <summary> - Stops this bootstrap. - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.DefaultBootstrap.LogFactory"> - <summary> - Gets the log factory. - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.DefaultBootstrap.AppServers"> - <summary> - Gets all the app servers running in this bootstrap - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.DefaultBootstrap.Config"> - <summary> - Gets the config. - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.DefaultBootstrap.StartupConfigFile"> - <summary> - Gets the startup config file. - </summary> - </member> - <member name="T:SuperSocket.SocketEngine.AssemblyImport"> - <summary> - AssemblyImport, used for importing assembly to the current AppDomain - </summary> - </member> - <member name="M:SuperSocket.SocketEngine.AssemblyImport.#ctor(System.String)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketEngine.AssemblyImport"/> class. - </summary> - </member> - <member name="T:SuperSocket.SocketEngine.BootstrapFactory"> - <summary> - Bootstrap Factory - </summary> - </member> - <member name="M:SuperSocket.SocketEngine.BootstrapFactory.CreateBootstrap(SuperSocket.SocketBase.Config.IConfigurationSource)"> - <summary> - Creates the bootstrap. - </summary> - <param name="config">The config.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketEngine.BootstrapFactory.CreateBootstrap"> - <summary> - Creates the bootstrap from app configuration's socketServer section. - </summary> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketEngine.BootstrapFactory.CreateBootstrap(System.String)"> - <summary> - Creates the bootstrap. - </summary> - <param name="configSectionName">Name of the config section.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketEngine.BootstrapFactory.CreateBootstrapFromConfigFile(System.String)"> - <summary> - Creates the bootstrap from configuration file. - </summary> - <param name="configFile">The configuration file.</param> - <returns></returns> - </member> - <member name="T:SuperSocket.SocketEngine.Configuration.CommandAssembly"> - <summary> - Command assembly configuration element - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.CommandAssembly.Assembly"> - <summary> - Gets the assembly name. - </summary> - <value> - The assembly. - </value> - </member> - <member name="T:SuperSocket.SocketEngine.Configuration.CommandAssemblyCollection"> - <summary> - Command assembly configuation collection - </summary> - </member> - <member name="T:SuperSocket.SocketEngine.SocketSession"> - <summary> - Socket Session, all application session should base on this class - </summary> - </member> - <member name="M:SuperSocket.SocketEngine.SocketSession.LogError(System.Exception,System.String,System.String,System.Int32)"> - <summary> - Logs the error, skip the ignored exception - </summary> - <param name="exception">The exception.</param> - <param name="caller">The caller.</param> - <param name="callerFilePath">The caller file path.</param> - <param name="callerLineNumber">The caller line number.</param> - </member> - <member name="M:SuperSocket.SocketEngine.SocketSession.LogError(System.String,System.Exception,System.String,System.String,System.Int32)"> - <summary> - Logs the error, skip the ignored exception - </summary> - <param name="message">The message.</param> - <param name="exception">The exception.</param> - <param name="caller">The caller.</param> - <param name="callerFilePath">The caller file path.</param> - <param name="callerLineNumber">The caller line number.</param> - </member> - <member name="M:SuperSocket.SocketEngine.SocketSession.LogError(System.Int32,System.String,System.String,System.Int32)"> - <summary> - Logs the socket error, skip the ignored error - </summary> - <param name="socketErrorCode">The socket error code.</param> - <param name="caller">The caller.</param> - <param name="callerFilePath">The caller file path.</param> - <param name="callerLineNumber">The caller line number.</param> - </member> - <member name="M:SuperSocket.SocketEngine.SocketSession.Start"> - <summary> - Starts this session. - </summary> - </member> - <member name="M:SuperSocket.SocketEngine.SocketSession.StartSession"> - <summary> - Says the welcome information when a client connectted. - </summary> - </member> - <member name="M:SuperSocket.SocketEngine.SocketSession.OnClosed(SuperSocket.SocketBase.CloseReason)"> - <summary> - Called when [close]. - </summary> - </member> - <member name="M:SuperSocket.SocketEngine.SocketSession.TrySend(System.Collections.Generic.IList{System.ArraySegment{System.Byte}})"> - <summary> - Tries to send array segment. - </summary> - <param name="segments">The segments.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketEngine.SocketSession.TrySend(System.ArraySegment{System.Byte})"> - <summary> - Tries to send array segment. - </summary> - <param name="segment">The segment.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketEngine.SocketSession.SendAsync(SuperSocket.Common.SendingQueue)"> - <summary> - Sends in async mode. - </summary> - <param name="queue">The queue.</param> - </member> - <member name="M:SuperSocket.SocketEngine.SocketSession.SendSync(SuperSocket.Common.SendingQueue)"> - <summary> - Sends in sync mode. - </summary> - <param name="queue">The queue.</param> - </member> - <member name="P:SuperSocket.SocketEngine.SocketSession.SessionID"> - <summary> - Gets or sets the session ID. - </summary> - <value>The session ID.</value> - </member> - <member name="P:SuperSocket.SocketEngine.SocketSession.Config"> - <summary> - Gets or sets the config. - </summary> - <value> - The config. - </value> - </member> - <member name="P:SuperSocket.SocketEngine.SocketSession.Closed"> - <summary> - Occurs when [closed]. - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.SocketSession.Client"> - <summary> - Gets or sets the client. - </summary> - <value>The client.</value> - </member> - <member name="P:SuperSocket.SocketEngine.SocketSession.LocalEndPoint"> - <summary> - Gets the local end point. - </summary> - <value>The local end point.</value> - </member> - <member name="P:SuperSocket.SocketEngine.SocketSession.RemoteEndPoint"> - <summary> - Gets the remote end point. - </summary> - <value>The remote end point.</value> - </member> - <member name="P:SuperSocket.SocketEngine.SocketSession.SecureProtocol"> - <summary> - Gets or sets the secure protocol. - </summary> - <value>The secure protocol.</value> - </member> - <member name="M:SuperSocket.SocketEngine.MarshalAppServer.#ctor(System.String)"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketEngine.AppDomainAppServer"/> class. - </summary> - <param name="serviceTypeName">Name of the service type.</param> - </member> - <member name="M:SuperSocket.SocketEngine.MarshalAppServer.Setup(SuperSocket.SocketBase.IBootstrap,SuperSocket.SocketBase.Config.IServerConfig,SuperSocket.SocketBase.Provider.ProviderFactoryInfo[])"> - <summary> - Setups the specified root config. - </summary> - <param name="bootstrap">The bootstrap.</param> - <param name="config">The socket server instance config.</param> - <param name="factories">The providers.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketEngine.MarshalAppServer.Start"> - <summary> - Starts this server instance. - </summary> - <returns> - return true if start successfull, else false - </returns> - </member> - <member name="M:SuperSocket.SocketEngine.MarshalAppServer.Stop"> - <summary> - Stops this server instance. - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.MarshalAppServer.Name"> - <summary> - Gets the name of the server instance. - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.MarshalAppServer.State"> - <summary> - Gets the current state of the work item. - </summary> - <value> - The state. - </value> - </member> - <member name="P:SuperSocket.SocketEngine.MarshalAppServer.SessionCount"> - <summary> - Gets the total session count. - </summary> - </member> - <member name="T:SuperSocket.SocketEngine.ISocketListener"> - <summary> - The interface for socket listener - </summary> - </member> - <member name="M:SuperSocket.SocketEngine.ISocketListener.Start(SuperSocket.SocketBase.Config.IServerConfig)"> - <summary> - Starts to listen - </summary> - <param name="config">The server config.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketEngine.ISocketListener.Stop"> - <summary> - Stops listening - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.ISocketListener.Info"> - <summary> - Gets the info of listener - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.ISocketListener.EndPoint"> - <summary> - Gets the end point the listener is working on - </summary> - </member> - <member name="E:SuperSocket.SocketEngine.ISocketListener.NewClientAccepted"> - <summary> - Occurs when new client accepted. - </summary> - </member> - <member name="E:SuperSocket.SocketEngine.ISocketListener.Error"> - <summary> - Occurs when error got. - </summary> - </member> - <member name="E:SuperSocket.SocketEngine.ISocketListener.Stopped"> - <summary> - Occurs when [stopped]. - </summary> - </member> - <member name="M:SuperSocket.SocketEngine.SocketListenerBase.Start(SuperSocket.SocketBase.Config.IServerConfig)"> - <summary> - Starts to listen - </summary> - <param name="config">The server config.</param> - <returns></returns> - </member> - <member name="E:SuperSocket.SocketEngine.SocketListenerBase.Stopped"> - <summary> - Occurs when [stopped]. - </summary> - </member> - <member name="T:SuperSocket.SocketEngine.TcpAsyncSocketListener"> - <summary> - Tcp socket listener in async mode - </summary> - </member> - <member name="M:SuperSocket.SocketEngine.TcpAsyncSocketListener.Start(SuperSocket.SocketBase.Config.IServerConfig)"> - <summary> - Starts to listen - </summary> - <param name="config">The server config.</param> - <returns></returns> - </member> - <member name="P:SuperSocket.SocketEngine.SocketServerBase.SendingQueuePool"> - <summary> - Gets the sending queue manager. - </summary> - <value> - The sending queue manager. - </value> - </member> - <member name="M:SuperSocket.SocketEngine.AsyncStreamSocketSession.Start"> - <summary> - Starts this session communication. - </summary> - </member> - <member name="T:SuperSocket.SocketEngine.Configuration.CertificateConfig"> - <summary> - Certificate configuration - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.CertificateConfig.FilePath"> - <summary> - Gets the certificate file path. - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.CertificateConfig.Password"> - <summary> - Gets the password. - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.CertificateConfig.StoreName"> - <summary> - Gets the the store where certificate locates. - </summary> - <value> - The name of the store. - </value> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.CertificateConfig.Thumbprint"> - <summary> - Gets the thumbprint. - </summary> - </member> - <member name="T:SuperSocket.SocketEngine.Configuration.Listener"> - <summary> - Listener configuration - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.Listener.Ip"> - <summary> - Gets the ip of listener - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.Listener.Port"> - <summary> - Gets the port of listener - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.Listener.Backlog"> - <summary> - Gets the backlog. - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.Listener.Security"> - <summary> - Gets the security option, None/Default/Tls/Ssl/... - </summary> - </member> - <member name="T:SuperSocket.SocketEngine.Configuration.ListenerConfigCollection"> - <summary> - Listener configuration collection - </summary> - </member> - <member name="T:SuperSocket.SocketEngine.Configuration.Server"> - <summary> - Server configuration - </summary> - </member> - <member name="M:SuperSocket.SocketEngine.Configuration.Server.GetChildConfig``1(System.String)"> - <summary> - Gets the child config. - </summary> - <typeparam name="TConfig">The type of the config.</typeparam> - <param name="childConfigName">Name of the child config.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketEngine.Configuration.Server.OnDeserializeUnrecognizedAttribute(System.String,System.String)"> - <summary> - Gets a value indicating whether an unknown attribute is encountered during deserialization. - To keep compatible with old configuration - </summary> - <param name="name">The name of the unrecognized attribute.</param> - <param name="value">The value of the unrecognized attribute.</param> - <returns> - true when an unknown attribute is encountered while deserializing; otherwise, false. - </returns> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.Server.ServerTypeName"> - <summary> - Gets the name of the server type this appServer want to use. - </summary> - <value> - The name of the server type. - </value> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.Server.ServerType"> - <summary> - Gets the type definition of the appserver. - </summary> - <value> - The type of the server. - </value> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.Server.ReceiveFilterFactory"> - <summary> - Gets the Receive filter factory. - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.Server.Ip"> - <summary> - Gets the ip. - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.Server.Port"> - <summary> - Gets the port. - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.Server.Mode"> - <summary> - Gets the mode. - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.Server.Disabled"> - <summary> - Gets a value indicating whether this <see cref="T:SuperSocket.SocketBase.Config.IServerConfig"/> is disabled. - </summary> - <value> - <c>true</c> if disabled; otherwise, <c>false</c>. - </value> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.Server.SendTimeOut"> - <summary> - Gets the send time out. - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.Server.MaxConnectionNumber"> - <summary> - Gets the max connection number. - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.Server.ReceiveBufferSize"> - <summary> - Gets the size of the receive buffer. - </summary> - <value> - The size of the receive buffer. - </value> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.Server.SendBufferSize"> - <summary> - Gets the size of the send buffer. - </summary> - <value> - The size of the send buffer. - </value> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.Server.SyncSend"> - <summary> - Gets a value indicating whether sending is in synchronous mode. - </summary> - <value> - <c>true</c> if [sync send]; otherwise, <c>false</c>. - </value> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.Server.LogCommand"> - <summary> - Gets a value indicating whether log command in log file. - </summary> - <value><c>true</c> if log command; otherwise, <c>false</c>.</value> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.Server.LogBasicSessionActivity"> - <summary> - Gets a value indicating whether [log basic session activity like connected and disconnected]. - </summary> - <value> - <c>true</c> if [log basic session activity]; otherwise, <c>false</c>. - </value> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.Server.LogAllSocketException"> - <summary> - Gets a value indicating whether [log all socket exception]. - </summary> - <value> - <c>true</c> if [log all socket exception]; otherwise, <c>false</c>. - </value> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.Server.ClearIdleSession"> - <summary> - Gets a value indicating whether clear idle session. - </summary> - <value><c>true</c> if clear idle session; otherwise, <c>false</c>.</value> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.Server.ClearIdleSessionInterval"> - <summary> - Gets the clear idle session interval, in seconds. - </summary> - <value>The clear idle session interval.</value> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.Server.IdleSessionTimeOut"> - <summary> - Gets the idle session timeout time length, in seconds. - </summary> - <value>The idle session time out.</value> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.Server.CertificateConfig"> - <summary> - Gets the certificate config. - </summary> - <value>The certificate config.</value> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.Server.Certificate"> - <summary> - Gets X509Certificate configuration. - </summary> - <value> - X509Certificate configuration. - </value> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.Server.Security"> - <summary> - Gets the security protocol, X509 certificate. - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.Server.MaxRequestLength"> - <summary> - Gets the max allowed length of request. - </summary> - <value> - The max allowed length of request. - </value> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.Server.DisableSessionSnapshot"> - <summary> - Gets a value indicating whether [disable session snapshot] - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.Server.SessionSnapshotInterval"> - <summary> - Gets the interval to taking snapshot for all live sessions. - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.Server.ConnectionFilter"> - <summary> - Gets the connection filters used by this server instance. - </summary> - <value> - The connection filters's name list, seperated by comma - </value> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.Server.CommandLoader"> - <summary> - Gets the command loader, multiple values should be separated by comma. - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.Server.KeepAliveTime"> - <summary> - Gets the start keep alive time, in seconds - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.Server.KeepAliveInterval"> - <summary> - Gets the keep alive interval, in seconds. - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.Server.ListenBacklog"> - <summary> - Gets the backlog size of socket listening. - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.Server.StartupOrder"> - <summary> - Gets the startup order of the server instance. - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.Server.SendingQueueSize"> - <summary> - Gets/sets the size of the sending queue. - </summary> - <value> - The size of the sending queue. - </value> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.Server.LogFactory"> - <summary> - Gets the logfactory name of the server instance. - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.Server.Listeners"> - <summary> - Gets the listeners' configuration. - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.Server.SuperSocket#SocketBase#Config#IServerConfig#Listeners"> - <summary> - Gets the listeners' configuration. - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.Server.CommandAssemblies"> - <summary> - Gets the command assemblies configuration. - </summary> - <value> - The command assemblies. - </value> - </member> - <member name="T:SuperSocket.SocketEngine.Configuration.ServerCollection"> - <summary> - Server configuration collection - </summary> - </member> - <member name="T:SuperSocket.SocketEngine.Configuration.SocketServiceConfig"> - <summary> - SuperSocket's root configuration node - </summary> - </member> - <member name="M:SuperSocket.SocketEngine.Configuration.SocketServiceConfig.OnDeserializeUnrecognizedElement(System.String,System.Xml.XmlReader)"> - <summary> - Gets a value indicating whether an unknown element is encountered during deserialization. - To keep compatible with old configuration - </summary> - <param name="elementName">The name of the unknown subelement.</param> - <param name="reader">The <see cref="T:System.Xml.XmlReader"/> being used for deserialization.</param> - <returns> - true when an unknown element is encountered while deserializing; otherwise, false. - </returns> - <exception cref="T:System.Configuration.ConfigurationErrorsException">The element identified by <paramref name="elementName"/> is locked.- or -One or more of the element's attributes is locked.- or -<paramref name="elementName"/> is unrecognized, or the element has an unrecognized attribute.- or -The element has a Boolean attribute with an invalid value.- or -An attempt was made to deserialize a property more than once.- or -An attempt was made to deserialize a property that is not a valid member of the element.- or -The element cannot contain a CDATA or text element.</exception> - </member> - <member name="M:SuperSocket.SocketEngine.Configuration.SocketServiceConfig.GetChildConfig``1(System.String)"> - <summary> - Gets the child config. - </summary> - <typeparam name="TConfig">The type of the config.</typeparam> - <param name="childConfigName">Name of the child config.</param> - <returns></returns> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.SocketServiceConfig.Servers"> - <summary> - Gets all the server configurations - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.SocketServiceConfig.ServerTypes"> - <summary> - Gets the service configurations - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.SocketServiceConfig.ConnectionFilters"> - <summary> - Gets all the connection filter configurations. - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.SocketServiceConfig.LogFactories"> - <summary> - Gets the defined log factory types. - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.SocketServiceConfig.ReceiveFilterFactories"> - <summary> - Gets the logfactory name of the bootstrap. - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.SocketServiceConfig.CommandLoaders"> - <summary> - Gets the command loaders definition. - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.SocketServiceConfig.MaxWorkingThreads"> - <summary> - Gets the max working threads. - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.SocketServiceConfig.MinWorkingThreads"> - <summary> - Gets the min working threads. - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.SocketServiceConfig.MaxCompletionPortThreads"> - <summary> - Gets the max completion port threads. - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.SocketServiceConfig.MinCompletionPortThreads"> - <summary> - Gets the min completion port threads. - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.SocketServiceConfig.PerformanceDataCollectInterval"> - <summary> - Gets the performance data collect interval, in seconds. - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.SocketServiceConfig.DisablePerformanceDataCollector"> - <summary> - Gets a value indicating whether [disable performance data collector]. - </summary> - <value> - <c>true</c> if [disable performance data collector]; otherwise, <c>false</c>. - </value> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.SocketServiceConfig.Isolation"> - <summary> - Gets the isolation mode. - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.SocketServiceConfig.LogFactory"> - <summary> - Gets the logfactory name of the bootstrap. - </summary> - </member> - <member name="P:SuperSocket.SocketEngine.Configuration.SocketServiceConfig.OptionElements"> - <summary> - Gets the option elements. - </summary> - </member> - <member name="T:SuperSocket.SocketEngine.SocketServerFactory"> - <summary> - Default socket server factory - </summary> - </member> - <member name="M:SuperSocket.SocketEngine.SocketServerFactory.CreateSocketServer``1(SuperSocket.SocketBase.IAppServer,SuperSocket.SocketBase.ListenerInfo[],SuperSocket.SocketBase.Config.IServerConfig)"> - <summary> - Creates the socket server. - </summary> - <typeparam name="TRequestInfo">The type of the request info.</typeparam> - <param name="appServer">The app server.</param> - <param name="listeners">The listeners.</param> - <param name="config">The config.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketEngine.UdpSocketListener.Start(SuperSocket.SocketBase.Config.IServerConfig)"> - <summary> - Starts to listen - </summary> - <param name="config">The server config.</param> - <returns></returns> - </member> - <member name="M:SuperSocket.SocketEngine.UdpSocketServer`1.#ctor(SuperSocket.SocketBase.IAppServer,SuperSocket.SocketBase.ListenerInfo[])"> - <summary> - Initializes a new instance of the <see cref="T:SuperSocket.SocketEngine.UdpSocketServer`1"/> class. - </summary> - <param name="appServer">The app server.</param> - <param name="listeners">The listeners.</param> - </member> - <member name="M:SuperSocket.SocketEngine.UdpSocketServer`1.OnNewClientAccepted(SuperSocket.SocketEngine.ISocketListener,System.Net.Sockets.Socket,System.Object)"> - <summary> - Called when [new client accepted]. - </summary> - <param name="listener">The listener.</param> - <param name="client">The client.</param> - <param name="state">The state.</param> - </member> - <member name="M:SuperSocket.SocketEngine.UdpSocketSession.UpdateRemoteEndPoint(System.Net.IPEndPoint)"> - <summary> - Updates the remote end point of the client. - </summary> - <param name="remoteEndPoint">The remote end point.</param> - </member> - </members> -</doc> diff --git a/cb-tools/SuperWebSocket/SuperSocket.SocketEngine.dll b/cb-tools/SuperWebSocket/SuperSocket.SocketEngine.dll deleted file mode 100644 index ded732da201bcbbdffe8ca6870143a58bf92ff81..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 71680 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 Br;`8x diff --git a/cb-tools/SuperWebSocket/SuperSocket.SocketEngine.pdb b/cb-tools/SuperWebSocket/SuperSocket.SocketEngine.pdb deleted file mode 100644 index 619ef6be4680971fb018f0d229ff01a2ac796002..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 165376 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 diff --git a/cb-tools/SuperWebSocket/SuperSocket.SocketService.exe b/cb-tools/SuperWebSocket/SuperSocket.SocketService.exe deleted file mode 100644 index b3a7c7d767580ddaa3786a61a5c874e6eb80bac8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11264 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 diff --git a/cb-tools/SuperWebSocket/SuperSocket.SocketService.exe.config b/cb-tools/SuperWebSocket/SuperSocket.SocketService.exe.config deleted file mode 100644 index 4ce9e122..00000000 --- a/cb-tools/SuperWebSocket/SuperSocket.SocketService.exe.config +++ /dev/null @@ -1,24 +0,0 @@ -<?xml version="1.0"?> -<configuration> - <configSections> - <section name="superSocket" type="SuperSocket.SocketEngine.Configuration.SocketServiceConfig, SuperSocket.SocketEngine"/> - </configSections> - <appSettings> - <add key="ServiceName" value="SupperSocketService"/> - </appSettings> - <superSocket> - <servers> - <server name="YourServerInstanceName" serverTypeName="YourService" ip="Any" port="2020" maxConnectionNumber="100"> - </server> - </servers> - <serverTypes> - <add name="YourService" type="YourServiceServerType, YourServiceAssembly"/> - </serverTypes> - </superSocket> - <startup> - <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/> - </startup> - <runtime> - <gcServer enabled="true" /> - </runtime> -</configuration> diff --git a/cb-tools/SuperWebSocket/SuperSocket.SocketService.pdb b/cb-tools/SuperWebSocket/SuperSocket.SocketService.pdb deleted file mode 100644 index 30066049a99675f1971a7cb3bcfdbb2dd7ac9a19..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24064 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 diff --git a/cb-tools/SuperWebSocket/SuperWebSocket.XML b/cb-tools/SuperWebSocket/SuperWebSocket.XML deleted file mode 100644 index cdd8817c..00000000 --- a/cb-tools/SuperWebSocket/SuperWebSocket.XML +++ /dev/null @@ -1,1762 +0,0 @@ -<?xml version="1.0"?> -<doc> - <assembly> - <name>SuperWebSocket</name> - </assembly> - <members> - <member name="T:SuperWebSocket.Command.Binary`1"> - <summary> - The command handling binary data - </summary> - <typeparam name="TWebSocketSession">The type of the web socket session.</typeparam> - </member> - <member name="T:SuperWebSocket.Command.FragmentCommand`1"> - <summary> - FragmentCommand - </summary> - <typeparam name="TWebSocketSession">The type of the web socket session.</typeparam> - </member> - <member name="M:SuperWebSocket.Command.FragmentCommand`1.#ctor"> - <summary> - Initializes a new instance of the <see cref="T:SuperWebSocket.Command.FragmentCommand`1"/> class. - </summary> - </member> - <member name="M:SuperWebSocket.Command.FragmentCommand`1.CheckFrame(SuperWebSocket.Protocol.WebSocketDataFrame)"> - <summary> - Checks the frame. - </summary> - <param name="frame">The frame.</param> - <returns></returns> - </member> - <member name="M:SuperWebSocket.Command.FragmentCommand`1.CheckControlFrame(SuperWebSocket.Protocol.WebSocketDataFrame)"> - <summary> - Checks the control frame. - </summary> - <param name="frame">The frame.</param> - <returns></returns> - </member> - <member name="M:SuperWebSocket.Command.FragmentCommand`1.GetWebSocketData(System.Collections.Generic.IList{SuperWebSocket.Protocol.WebSocketDataFrame})"> - <summary> - Gets data from websocket frames. - </summary> - <param name="frames">The frames.</param> - <returns></returns> - </member> - <member name="M:SuperWebSocket.Command.FragmentCommand`1.GetWebSocketText(System.Collections.Generic.IList{SuperWebSocket.Protocol.WebSocketDataFrame})"> - <summary> - Gets text string from websocket frames. - </summary> - <param name="frames">The frames.</param> - <returns></returns> - </member> - <member name="M:SuperWebSocket.Command.FragmentCommand`1.GetWebSocketData(SuperWebSocket.Protocol.WebSocketDataFrame)"> - <summary> - Gets data from a websocket frame. - </summary> - <param name="frame">The frame.</param> - <returns></returns> - </member> - <member name="M:SuperWebSocket.Command.FragmentCommand`1.GetWebSocketText(SuperWebSocket.Protocol.WebSocketDataFrame)"> - <summary> - Gets text string from a websocket frame. - </summary> - <param name="frame">The frame.</param> - <returns></returns> - </member> - <member name="P:SuperWebSocket.Command.FragmentCommand`1.Utf8Encoding"> - <summary> - Gets the UTF8 encoding which has been set ExceptionFallback. - </summary> - </member> - <member name="M:SuperWebSocket.Command.Binary`1.ExecuteCommand(`0,SuperWebSocket.Protocol.IWebSocketFragment)"> - <summary> - Executes the command. - </summary> - <param name="session">The session.</param> - <param name="requestInfo">The request info.</param> - </member> - <member name="P:SuperWebSocket.Command.Binary`1.Name"> - <summary> - Gets the name. - </summary> - </member> - <member name="T:SuperWebSocket.Command.Close`1"> - <summary> - The command handling close fragment - </summary> - <typeparam name="TWebSocketSession">The type of the web socket session.</typeparam> - </member> - <member name="M:SuperWebSocket.Command.Close`1.ExecuteCommand(`0,SuperWebSocket.Protocol.IWebSocketFragment)"> - <summary> - Executes the command. - </summary> - <param name="session">The session.</param> - <param name="requestInfo">The request info.</param> - </member> - <member name="P:SuperWebSocket.Command.Close`1.Name"> - <summary> - Gets the name. - </summary> - </member> - <member name="T:SuperWebSocket.Command.Continuation`1"> - <summary> - The command handling continuation fragment - </summary> - <typeparam name="TWebSocketSession">The type of the web socket session.</typeparam> - </member> - <member name="M:SuperWebSocket.Command.Continuation`1.ExecuteCommand(`0,SuperWebSocket.Protocol.IWebSocketFragment)"> - <summary> - Executes the command. - </summary> - <param name="session">The session.</param> - <param name="requestInfo">The request info.</param> - </member> - <member name="P:SuperWebSocket.Command.Continuation`1.Name"> - <summary> - Gets the name. - </summary> - </member> - <member name="T:SuperWebSocket.Command.HandShake`1"> - <summary> - The command handle handshake request - </summary> - <typeparam name="TWebSocketSession">The type of the web socket session.</typeparam> - </member> - <member name="M:SuperWebSocket.Command.HandShake`1.ExecuteCommand(`0,SuperWebSocket.Protocol.IWebSocketFragment)"> - <summary> - Executes the command. - </summary> - <param name="session">The session.</param> - <param name="requestInfo">The request info.</param> - </member> - <member name="P:SuperWebSocket.Command.HandShake`1.Name"> - <summary> - Gets the name. - </summary> - </member> - <member name="T:SuperWebSocket.Command.Ping`1"> - <summary> - The command handling Ping - </summary> - <typeparam name="TWebSocketSession">The type of the web socket session.</typeparam> - </member> - <member name="M:SuperWebSocket.Command.Ping`1.ExecuteCommand(`0,SuperWebSocket.Protocol.IWebSocketFragment)"> - <summary> - Executes the command. - </summary> - <param name="session">The session.</param> - <param name="requestInfo">The request info.</param> - </member> - <member name="P:SuperWebSocket.Command.Ping`1.Name"> - <summary> - Gets the name. - </summary> - </member> - <member name="T:SuperWebSocket.Command.Plain`1"> - <summary> - The command to handling text message in plain text of hybi00 - </summary> - <typeparam name="TWebSocketSession">The type of the web socket session.</typeparam> - </member> - <member name="M:SuperWebSocket.Command.Plain`1.ExecuteCommand(`0,SuperWebSocket.Protocol.IWebSocketFragment)"> - <summary> - Executes the command. - </summary> - <param name="session">The session.</param> - <param name="requestInfo">The request info.</param> - </member> - <member name="P:SuperWebSocket.Command.Plain`1.Name"> - <summary> - Gets the name. - </summary> - </member> - <member name="T:SuperWebSocket.Command.Pong`1"> - <summary> - The command handling Pong - </summary> - <typeparam name="TWebSocketSession">The type of the web socket session.</typeparam> - </member> - <member name="M:SuperWebSocket.Command.Pong`1.ExecuteCommand(`0,SuperWebSocket.Protocol.IWebSocketFragment)"> - <summary> - Executes the command. - </summary> - <param name="session">The session.</param> - <param name="requestInfo">The request info.</param> - </member> - <member name="P:SuperWebSocket.Command.Pong`1.Name"> - <summary> - Gets the name. - </summary> - </member> - <member name="T:SuperWebSocket.Command.Text`1"> - <summary> - The command handling Text fragment - </summary> - <typeparam name="TWebSocketSession">The type of the web socket session.</typeparam> - </member> - <member name="M:SuperWebSocket.Command.Text`1.ExecuteCommand(`0,SuperWebSocket.Protocol.IWebSocketFragment)"> - <summary> - Executes the command. - </summary> - <param name="session">The session.</param> - <param name="requestInfo">The request info.</param> - </member> - <member name="P:SuperWebSocket.Command.Text`1.Name"> - <summary> - Gets the name. - </summary> - </member> - <member name="T:SuperWebSocket.Config.CommandConfig"> - <summary> - Command configuration - </summary> - </member> - <member name="M:SuperWebSocket.Config.CommandConfig.OnDeserializeUnrecognizedAttribute(System.String,System.String)"> - <summary> - Gets a value indicating whether an unknown attribute is encountered during deserialization. - </summary> - <param name="name">The name of the unrecognized attribute.</param> - <param name="value">The value of the unrecognized attribute.</param> - <returns> - true when an unknown attribute is encountered while deserializing; otherwise, false. - </returns> - </member> - <member name="P:SuperWebSocket.Config.CommandConfig.Options"> - <summary> - Gets the options. - </summary> - </member> - <member name="T:SuperWebSocket.Config.CommandConfigCollection"> - <summary> - Command configuration collection - </summary> - </member> - <member name="M:SuperWebSocket.Config.CommandConfigCollection.CreateNewElement"> - <summary> - When overridden in a derived class, creates a new <see cref="T:System.Configuration.ConfigurationElement"/>. - </summary> - <returns> - A new <see cref="T:System.Configuration.ConfigurationElement"/>. - </returns> - </member> - <member name="M:SuperWebSocket.Config.CommandConfigCollection.GetElementKey(System.Configuration.ConfigurationElement)"> - <summary> - Gets the element key for a specified configuration element when overridden in a derived class. - </summary> - <param name="element">The <see cref="T:System.Configuration.ConfigurationElement"/> to return the key for.</param> - <returns> - An <see cref="T:System.Object"/> that acts as the key for the specified <see cref="T:System.Configuration.ConfigurationElement"/>. - </returns> - </member> - <member name="M:SuperWebSocket.Config.CommandConfigCollection.GetEnumerator"> - <summary> - Gets the enumerator. - </summary> - <returns></returns> - </member> - <member name="P:SuperWebSocket.Config.CommandConfigCollection.Item(System.Int32)"> - <summary> - Gets or sets a property, attribute, or child element of this configuration element. - </summary> - <returns>The specified property, attribute, or child element</returns> - </member> - <member name="T:SuperWebSocket.Config.SubProtocolConfig"> - <summary> - SubProtocol configuration - </summary> - </member> - <member name="M:SuperWebSocket.Config.SubProtocolConfig.#ctor"> - <summary> - Initializes a new instance of the <see cref="T:SuperWebSocket.Config.SubProtocolConfig"/> class. - </summary> - </member> - <member name="P:SuperWebSocket.Config.SubProtocolConfig.Type"> - <summary> - Gets the type. - </summary> - </member> - <member name="P:SuperWebSocket.Config.SubProtocolConfig.Commands"> - <summary> - Gets the commands. - </summary> - </member> - <member name="T:SuperWebSocket.Config.SubProtocolConfigCollection"> - <summary> - SubProtocol configuation collection - </summary> - </member> - <member name="M:SuperWebSocket.Config.SubProtocolConfigCollection.CreateNewElement"> - <summary> - When overridden in a derived class, creates a new <see cref="T:System.Configuration.ConfigurationElement"/>. - </summary> - <returns> - A new <see cref="T:System.Configuration.ConfigurationElement"/>. - </returns> - </member> - <member name="M:SuperWebSocket.Config.SubProtocolConfigCollection.GetElementKey(System.Configuration.ConfigurationElement)"> - <summary> - Gets the element key for a specified configuration element when overridden in a derived class. - </summary> - <param name="element">The <see cref="T:System.Configuration.ConfigurationElement"/> to return the key for.</param> - <returns> - An <see cref="T:System.Object"/> that acts as the key for the specified <see cref="T:System.Configuration.ConfigurationElement"/>. - </returns> - </member> - <member name="M:SuperWebSocket.Config.SubProtocolConfigCollection.GetEnumerator"> - <summary> - Gets the enumerator. - </summary> - <returns></returns> - </member> - <member name="P:SuperWebSocket.Config.SubProtocolConfigCollection.CollectionType"> - <summary> - Gets the type of the <see cref="T:System.Configuration.ConfigurationElementCollection"/>. - </summary> - <returns>The <see cref="T:System.Configuration.ConfigurationElementCollectionType"/> of this collection.</returns> - </member> - <member name="P:SuperWebSocket.Config.SubProtocolConfigCollection.ElementName"> - <summary> - Gets the name used to identify this collection of elements in the configuration file when overridden in a derived class. - </summary> - <returns>The name of the collection; otherwise, an empty string. The default is an empty string.</returns> - </member> - <member name="T:SuperWebSocket.Extensions"> - <summary> - Extension class - </summary> - </member> - <member name="M:SuperWebSocket.Extensions.AppendFormatWithCrCf(System.Text.StringBuilder,System.String,System.Object)"> - <summary> - Appends in the format with CrCf as suffix. - </summary> - <param name="builder">The builder.</param> - <param name="format">The format.</param> - <param name="arg">The arg.</param> - </member> - <member name="M:SuperWebSocket.Extensions.AppendFormatWithCrCf(System.Text.StringBuilder,System.String,System.Object[])"> - <summary> - Appends in the format with CrCf as suffix. - </summary> - <param name="builder">The builder.</param> - <param name="format">The format.</param> - <param name="args">The args.</param> - </member> - <member name="M:SuperWebSocket.Extensions.AppendWithCrCf(System.Text.StringBuilder,System.String)"> - <summary> - Appends with CrCf as suffix. - </summary> - <param name="builder">The builder.</param> - <param name="content">The content.</param> - </member> - <member name="M:SuperWebSocket.Extensions.AppendWithCrCf(System.Text.StringBuilder)"> - <summary> - Appends with CrCf as suffix. - </summary> - <param name="builder">The builder.</param> - </member> - <member name="T:SuperWebSocket.IBinaryDataConverter"> - <summary> - The converter interface for converting binary data to text message - </summary> - </member> - <member name="M:SuperWebSocket.IBinaryDataConverter.ToString(System.Byte[],System.Int32,System.Int32)"> - <summary> - Returns a <see cref="T:System.String"/> that represents this instance. - </summary> - <param name="data">The data.</param> - <param name="offset">The offset.</param> - <param name="length">The length.</param> - <returns> - A <see cref="T:System.String"/> that represents this instance. - </returns> - </member> - <member name="T:SuperWebSocket.JsonWebSocketSession"> - <summary> - Json websocket session - </summary> - </member> - <member name="T:SuperWebSocket.JsonWebSocketSession`1"> - <summary> - Json websocket session - </summary> - <typeparam name="TWebSocketSession">The type of the web socket session.</typeparam> - </member> - <member name="T:SuperWebSocket.WebSocketSession`1"> - <summary> - WebSocket AppSession class - </summary> - <typeparam name="TWebSocketSession">The type of the web socket session.</typeparam> - </member> - <member name="T:SuperWebSocket.IWebSocketSession"> - <summary> - WebSocketSession basic interface - </summary> - </member> - <member name="M:SuperWebSocket.IWebSocketSession.SendRawData(System.Byte[],System.Int32,System.Int32)"> - <summary> - Sends the raw binary response. - </summary> - <param name="data">The data.</param> - <param name="offset">The offset.</param> - <param name="length">The length.</param> - </member> - <member name="M:SuperWebSocket.IWebSocketSession.GetAvailableSubProtocol(System.String)"> - <summary> - Gets the available sub protocol. - </summary> - <param name="protocol">The protocol.</param> - <returns></returns> - </member> - <member name="P:SuperWebSocket.IWebSocketSession.Method"> - <summary> - Gets or sets the method. - </summary> - <value> - The method. - </value> - </member> - <member name="P:SuperWebSocket.IWebSocketSession.Host"> - <summary> - Gets the host. - </summary> - </member> - <member name="P:SuperWebSocket.IWebSocketSession.Path"> - <summary> - Gets or sets the path. - </summary> - <value> - The path. - </value> - </member> - <member name="P:SuperWebSocket.IWebSocketSession.HttpVersion"> - <summary> - Gets or sets the HTTP version. - </summary> - <value> - The HTTP version. - </value> - </member> - <member name="P:SuperWebSocket.IWebSocketSession.SecWebSocketVersion"> - <summary> - Gets the sec web socket version. - </summary> - </member> - <member name="P:SuperWebSocket.IWebSocketSession.Origin"> - <summary> - Gets the origin. - </summary> - </member> - <member name="P:SuperWebSocket.IWebSocketSession.UriScheme"> - <summary> - Gets the URI scheme. - </summary> - </member> - <member name="P:SuperWebSocket.IWebSocketSession.Handshaked"> - <summary> - Gets a value indicating whether this <see cref="T:SuperWebSocket.IWebSocketSession"/> is handshaked. - </summary> - <value> - <c>true</c> if handshaked; otherwise, <c>false</c>. - </value> - </member> - <member name="P:SuperWebSocket.IWebSocketSession.AppServer"> - <summary> - Gets the app server. - </summary> - </member> - <member name="P:SuperWebSocket.IWebSocketSession.ProtocolProcessor"> - <summary> - Gets or sets the protocol processor. - </summary> - <value> - The protocol processor. - </value> - </member> - <member name="M:SuperWebSocket.WebSocketSession`1.OnInit"> - <summary> - Called when [init]. - </summary> - </member> - <member name="M:SuperWebSocket.WebSocketSession`1.SetCookie"> - <summary> - Sets the cookie. - </summary> - </member> - <member name="M:SuperWebSocket.WebSocketSession`1.Send(System.String)"> - <summary> - Sends the response. - </summary> - <param name="message">The message.</param> - </member> - <member name="M:SuperWebSocket.WebSocketSession`1.Send(System.Byte[],System.Int32,System.Int32)"> - <summary> - Sends the response. - </summary> - <param name="data">The data.</param> - <param name="offset">The offset.</param> - <param name="length">The length.</param> - </member> - <member name="M:SuperWebSocket.WebSocketSession`1.Send(System.ArraySegment{System.Byte})"> - <summary> - Sends the response. - </summary> - <param name="segment">The segment.</param> - </member> - <member name="M:SuperWebSocket.WebSocketSession`1.SuperWebSocket#IWebSocketSession#SendRawData(System.Byte[],System.Int32,System.Int32)"> - <summary> - Sends the raw binary data. - </summary> - <param name="data">The data.</param> - <param name="offset">The offset.</param> - <param name="length">The length.</param> - </member> - <member name="M:SuperWebSocket.WebSocketSession`1.SendResponse(System.String)"> - <summary> - Sends the response. - </summary> - <param name="message">The message.</param> - <returns></returns> - </member> - <member name="M:SuperWebSocket.WebSocketSession`1.SendResponse(System.Byte[],System.Int32,System.Int32)"> - <summary> - Sends the response. - </summary> - <param name="data">The data.</param> - <param name="offset">The offset.</param> - <param name="length">The length.</param> - <returns></returns> - </member> - <member name="M:SuperWebSocket.WebSocketSession`1.SendResponse(System.ArraySegment{System.Byte})"> - <summary> - Sends the response. - </summary> - <param name="segment">The segment.</param> - <returns></returns> - </member> - <member name="M:SuperWebSocket.WebSocketSession`1.CloseWithHandshake(System.String)"> - <summary> - Closes the with handshake. - </summary> - <param name="reasonText">The reason text.</param> - </member> - <member name="M:SuperWebSocket.WebSocketSession`1.CloseWithHandshake(System.Int32,System.String)"> - <summary> - Closes the with handshake. - </summary> - <param name="statusCode">The status code.</param> - <param name="reasonText">The reason text.</param> - </member> - <member name="M:SuperWebSocket.WebSocketSession`1.SendCloseHandshakeResponse(System.Int32)"> - <summary> - Sends the close handshake response. - </summary> - <param name="statusCode">The status code.</param> - </member> - <member name="M:SuperWebSocket.WebSocketSession`1.Close(SuperSocket.SocketBase.CloseReason)"> - <summary> - Closes the specified reason. - </summary> - <param name="reason">The reason.</param> - </member> - <member name="M:SuperWebSocket.WebSocketSession`1.HandleUnknownCommand(SuperWebSocket.SubProtocol.SubRequestInfo)"> - <summary> - Handles the unknown command. - </summary> - <param name="requestInfo">The request info.</param> - </member> - <member name="M:SuperWebSocket.WebSocketSession`1.HandleUnknownRequest(SuperWebSocket.Protocol.IWebSocketFragment)"> - <summary> - Handles the unknown request. - </summary> - <param name="requestInfo">The request info.</param> - </member> - <member name="P:SuperWebSocket.WebSocketSession`1.Method"> - <summary> - Gets or sets the method. - </summary> - <value> - The method. - </value> - </member> - <member name="P:SuperWebSocket.WebSocketSession`1.Path"> - <summary> - Gets or sets the path. - </summary> - <value> - The path. - </value> - </member> - <member name="P:SuperWebSocket.WebSocketSession`1.HttpVersion"> - <summary> - Gets or sets the HTTP version. - </summary> - <value> - The HTTP version. - </value> - </member> - <member name="P:SuperWebSocket.WebSocketSession`1.Host"> - <summary> - Gets the host. - </summary> - </member> - <member name="P:SuperWebSocket.WebSocketSession`1.Origin"> - <summary> - Gets the origin. - </summary> - </member> - <member name="P:SuperWebSocket.WebSocketSession`1.Upgrade"> - <summary> - Gets the upgrade. - </summary> - </member> - <member name="P:SuperWebSocket.WebSocketSession`1.Connection"> - <summary> - Gets the connection. - </summary> - </member> - <member name="P:SuperWebSocket.WebSocketSession`1.SecWebSocketVersion"> - <summary> - Gets the sec web socket version. - </summary> - </member> - <member name="P:SuperWebSocket.WebSocketSession`1.SecWebSocketProtocol"> - <summary> - Gets the sec web socket protocol. - </summary> - </member> - <member name="P:SuperWebSocket.WebSocketSession`1.CurrentToken"> - <summary> - Gets the current token. - </summary> - </member> - <member name="P:SuperWebSocket.WebSocketSession`1.AppServer"> - <summary> - Gets the app server. - </summary> - </member> - <member name="P:SuperWebSocket.WebSocketSession`1.UriScheme"> - <summary> - Gets the URI scheme, ws or wss - </summary> - </member> - <member name="P:SuperWebSocket.WebSocketSession`1.SubProtocol"> - <summary> - Gets the sub protocol. - </summary> - </member> - <member name="P:SuperWebSocket.WebSocketSession`1.Handshaked"> - <summary> - Gets a value indicating whether this <see cref="T:SuperWebSocket.IWebSocketSession"/> is handshaked. - </summary> - <value> - <c>true</c> if handshaked; otherwise, <c>false</c>. - </value> - </member> - <member name="P:SuperWebSocket.WebSocketSession`1.InClosing"> - <summary> - Gets a value indicating whether the session [in closing]. - </summary> - <value> - <c>true</c> if [in closing]; otherwise, <c>false</c>. - </value> - </member> - <member name="P:SuperWebSocket.WebSocketSession`1.Cookies"> - <summary> - Gets the cookies. - </summary> - </member> - <member name="P:SuperWebSocket.WebSocketSession`1.ProtocolProcessor"> - <summary> - Gets or sets the protocol processor. - </summary> - <value> - The protocol processor. - </value> - </member> - <member name="M:SuperWebSocket.JsonWebSocketSession`1.SendJsonMessage(System.String,System.Object)"> - <summary> - Sends the json message. - </summary> - <param name="name">The name.</param> - <param name="content">The content.</param> - </member> - <member name="T:SuperWebSocket.Protocol.CloseStatusCodeHybi10"> - <summary> - Close status code for Hybi10 - </summary> - </member> - <member name="T:SuperWebSocket.Protocol.ICloseStatusCode"> - <summary> - Close status code interface - </summary> - </member> - <member name="P:SuperWebSocket.Protocol.ICloseStatusCode.ExtensionNotMatch"> - <summary> - Gets the code for extension not match. - </summary> - </member> - <member name="P:SuperWebSocket.Protocol.ICloseStatusCode.GoingAway"> - <summary> - Gets the code for going away. - </summary> - </member> - <member name="P:SuperWebSocket.Protocol.ICloseStatusCode.InvalidUTF8"> - <summary> - Gets the code for invalid UT f8. - </summary> - </member> - <member name="P:SuperWebSocket.Protocol.ICloseStatusCode.NormalClosure"> - <summary> - Gets the code for normal closure. - </summary> - </member> - <member name="P:SuperWebSocket.Protocol.ICloseStatusCode.NotAcceptableData"> - <summary> - Gets the code for not acceptable data. - </summary> - </member> - <member name="P:SuperWebSocket.Protocol.ICloseStatusCode.ProtocolError"> - <summary> - Gets the code for protocol error. - </summary> - </member> - <member name="P:SuperWebSocket.Protocol.ICloseStatusCode.TLSHandshakeFailure"> - <summary> - Gets the code for TLS handshake failure. - </summary> - </member> - <member name="P:SuperWebSocket.Protocol.ICloseStatusCode.TooLargeFrame"> - <summary> - Gets the code for too large frame. - </summary> - </member> - <member name="P:SuperWebSocket.Protocol.ICloseStatusCode.UnexpectedCondition"> - <summary> - Gets the code for unexpected condition. - </summary> - </member> - <member name="P:SuperWebSocket.Protocol.ICloseStatusCode.ViolatePolicy"> - <summary> - Gets the code for violate policy. - </summary> - </member> - <member name="P:SuperWebSocket.Protocol.ICloseStatusCode.NoStatusCode"> - <summary> - Gets the code for no status code. - </summary> - </member> - <member name="M:SuperWebSocket.Protocol.CloseStatusCodeHybi10.#ctor"> - <summary> - Initializes a new instance of the <see cref="T:SuperWebSocket.Protocol.CloseStatusCodeHybi10"/> class. - </summary> - </member> - <member name="P:SuperWebSocket.Protocol.CloseStatusCodeHybi10.NormalClosure"> - <summary> - Gets the code for normal closure. - </summary> - </member> - <member name="P:SuperWebSocket.Protocol.CloseStatusCodeHybi10.GoingAway"> - <summary> - Gets the code for going away. - </summary> - </member> - <member name="P:SuperWebSocket.Protocol.CloseStatusCodeHybi10.ProtocolError"> - <summary> - Gets the code for protocol error. - </summary> - </member> - <member name="P:SuperWebSocket.Protocol.CloseStatusCodeHybi10.NotAcceptableData"> - <summary> - Gets the code for not acceptable data. - </summary> - </member> - <member name="P:SuperWebSocket.Protocol.CloseStatusCodeHybi10.TooLargeFrame"> - <summary> - Gets the code for too large frame. - </summary> - </member> - <member name="P:SuperWebSocket.Protocol.CloseStatusCodeHybi10.InvalidUTF8"> - <summary> - Gets the code for invalid UT f8. - </summary> - </member> - <member name="P:SuperWebSocket.Protocol.CloseStatusCodeHybi10.ViolatePolicy"> - <summary> - Gets the code for violate policy. - </summary> - </member> - <member name="P:SuperWebSocket.Protocol.CloseStatusCodeHybi10.ExtensionNotMatch"> - <summary> - Gets the code for extension not match. - </summary> - </member> - <member name="P:SuperWebSocket.Protocol.CloseStatusCodeHybi10.UnexpectedCondition"> - <summary> - Gets the code for unexpected condition. - </summary> - </member> - <member name="P:SuperWebSocket.Protocol.CloseStatusCodeHybi10.TLSHandshakeFailure"> - <summary> - Gets the code for TLS handshake failure. - </summary> - </member> - <member name="P:SuperWebSocket.Protocol.CloseStatusCodeHybi10.NoStatusCode"> - <summary> - Gets the code for no status code. - </summary> - </member> - <member name="T:SuperWebSocket.Protocol.CloseStatusCodeRfc6455"> - <summary> - Close status code for rfc6455 - </summary> - </member> - <member name="M:SuperWebSocket.Protocol.CloseStatusCodeRfc6455.#ctor"> - <summary> - Initializes a new instance of the <see cref="T:SuperWebSocket.Protocol.CloseStatusCodeRfc6455"/> class. - </summary> - </member> - <member name="P:SuperWebSocket.Protocol.CloseStatusCodeRfc6455.NormalClosure"> - <summary> - Gets the code for normal closure. - </summary> - </member> - <member name="P:SuperWebSocket.Protocol.CloseStatusCodeRfc6455.GoingAway"> - <summary> - Gets the code for going away. - </summary> - </member> - <member name="P:SuperWebSocket.Protocol.CloseStatusCodeRfc6455.ProtocolError"> - <summary> - Gets the code for protocol error. - </summary> - </member> - <member name="P:SuperWebSocket.Protocol.CloseStatusCodeRfc6455.NotAcceptableData"> - <summary> - Gets the code for not acceptable data. - </summary> - </member> - <member name="P:SuperWebSocket.Protocol.CloseStatusCodeRfc6455.TooLargeFrame"> - <summary> - Gets the code for too large frame. - </summary> - </member> - <member name="P:SuperWebSocket.Protocol.CloseStatusCodeRfc6455.InvalidUTF8"> - <summary> - Gets the code for invalid UT f8. - </summary> - </member> - <member name="P:SuperWebSocket.Protocol.CloseStatusCodeRfc6455.ViolatePolicy"> - <summary> - Gets the code for violate policy. - </summary> - </member> - <member name="P:SuperWebSocket.Protocol.CloseStatusCodeRfc6455.ExtensionNotMatch"> - <summary> - Gets the code for extension not match. - </summary> - </member> - <member name="P:SuperWebSocket.Protocol.CloseStatusCodeRfc6455.UnexpectedCondition"> - <summary> - Gets the code for unexpected condition. - </summary> - </member> - <member name="P:SuperWebSocket.Protocol.CloseStatusCodeRfc6455.TLSHandshakeFailure"> - <summary> - Gets the code for TLS handshake failure. - </summary> - </member> - <member name="P:SuperWebSocket.Protocol.CloseStatusCodeRfc6455.NoStatusCode"> - <summary> - Gets the code for no status code. - </summary> - </member> - <member name="T:SuperWebSocket.Protocol.DraftHybi00Processor"> - <summary> - http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00 - </summary> - </member> - <member name="T:SuperWebSocket.Protocol.IProtocolProcessor"> - <summary> - Protocol processor interface - </summary> - </member> - <member name="M:SuperWebSocket.Protocol.IProtocolProcessor.Handshake(SuperWebSocket.IWebSocketSession,SuperWebSocket.Protocol.WebSocketReceiveFilterBase,SuperSocket.SocketBase.Protocol.IReceiveFilter{SuperWebSocket.Protocol.IWebSocketFragment}@)"> - <summary> - Handshakes the specified session. - </summary> - <param name="session">The session.</param> - <param name="previousFilter">The previous filter.</param> - <param name="dataFrameReader">The data frame reader.</param> - <returns></returns> - </member> - <member name="M:SuperWebSocket.Protocol.IProtocolProcessor.SendMessage(SuperWebSocket.IWebSocketSession,System.String)"> - <summary> - Sends the message. - </summary> - <param name="session">The session.</param> - <param name="message">The message.</param> - </member> - <member name="M:SuperWebSocket.Protocol.IProtocolProcessor.SendData(SuperWebSocket.IWebSocketSession,System.Byte[],System.Int32,System.Int32)"> - <summary> - Sends the data. - </summary> - <param name="session">The session.</param> - <param name="data">The data.</param> - <param name="offset">The offset.</param> - <param name="length">The length.</param> - </member> - <member name="M:SuperWebSocket.Protocol.IProtocolProcessor.SendCloseHandshake(SuperWebSocket.IWebSocketSession,System.Int32,System.String)"> - <summary> - Sends the close handshake. - </summary> - <param name="session">The session.</param> - <param name="statusCode">The status code.</param> - <param name="closeReason">The close reason.</param> - </member> - <member name="M:SuperWebSocket.Protocol.IProtocolProcessor.SendPong(SuperWebSocket.IWebSocketSession,System.Byte[])"> - <summary> - Sends the pong. - </summary> - <param name="session">The session.</param> - <param name="pong">The pong.</param> - </member> - <member name="M:SuperWebSocket.Protocol.IProtocolProcessor.SendPing(SuperWebSocket.IWebSocketSession,System.Byte[])"> - <summary> - Sends the ping. - </summary> - <param name="session">The session.</param> - <param name="ping">The ping.</param> - </member> - <member name="M:SuperWebSocket.Protocol.IProtocolProcessor.IsValidCloseCode(System.Int32)"> - <summary> - Determines whether [is valid close code] [the specified code]. - </summary> - <param name="code">The code.</param> - <returns> - <c>true</c> if [is valid close code] [the specified code]; otherwise, <c>false</c>. - </returns> - </member> - <member name="P:SuperWebSocket.Protocol.IProtocolProcessor.CanSendBinaryData"> - <summary> - Gets a value indicating whether this instance can send binary data. - </summary> - <value> - <c>true</c> if this instance can send binary data; otherwise, <c>false</c>. - </value> - </member> - <member name="P:SuperWebSocket.Protocol.IProtocolProcessor.CloseStatusClode"> - <summary> - Gets the close status clode. - </summary> - </member> - <member name="P:SuperWebSocket.Protocol.IProtocolProcessor.NextProcessor"> - <summary> - Gets or sets the next processor. - </summary> - <value> - The next processor. - </value> - </member> - <member name="P:SuperWebSocket.Protocol.IProtocolProcessor.Version"> - <summary> - Gets the version of current protocol. - </summary> - </member> - <member name="T:SuperWebSocket.Protocol.DraftHybi10Processor"> - <summary> - http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10 - </summary> - </member> - <member name="T:SuperWebSocket.Protocol.HandshakeRequest"> - <summary> - Handshake request - </summary> - </member> - <member name="T:SuperWebSocket.Protocol.IWebSocketFragment"> - <summary> - WebSocketFragment request info - </summary> - </member> - <member name="P:SuperWebSocket.Protocol.HandshakeRequest.Key"> - <summary> - Gets the key of this request. - </summary> - </member> - <member name="T:SuperWebSocket.Protocol.MultipleProtocolSwitchProcessor"> - <summary> - http://tools.ietf.org/html/rfc6455#section-4.4 - </summary> - </member> - <member name="T:SuperWebSocket.Protocol.PlainFragment"> - <summary> - Plain text fragment - </summary> - </member> - <member name="M:SuperWebSocket.Protocol.PlainFragment.#ctor(System.String)"> - <summary> - Initializes a new instance of the <see cref="T:SuperWebSocket.Protocol.PlainFragment"/> class. - </summary> - <param name="message">The message.</param> - </member> - <member name="P:SuperWebSocket.Protocol.PlainFragment.Message"> - <summary> - Gets the message. - </summary> - </member> - <member name="P:SuperWebSocket.Protocol.PlainFragment.Key"> - <summary> - Gets the key of this request. - </summary> - </member> - <member name="T:SuperWebSocket.Protocol.Rfc6455Processor"> - <summary> - http://tools.ietf.org/html/rfc6455 - </summary> - </member> - <member name="T:SuperWebSocket.Protocol.WebSocketReceiveFilterBase"> - <summary> - WebSocketReceiveFilter basis - </summary> - </member> - <member name="F:SuperWebSocket.Protocol.WebSocketReceiveFilterBase.SecKey3Len"> - <summary> - The length of Sec3Key - </summary> - </member> - <member name="M:SuperWebSocket.Protocol.WebSocketReceiveFilterBase.#ctor(SuperWebSocket.IWebSocketSession)"> - <summary> - Initializes a new instance of the <see cref="T:SuperWebSocket.Protocol.WebSocketReceiveFilterBase"/> class. - </summary> - <param name="session">The session.</param> - </member> - <member name="M:SuperWebSocket.Protocol.WebSocketReceiveFilterBase.#ctor(SuperWebSocket.Protocol.WebSocketReceiveFilterBase)"> - <summary> - Initializes a new instance of the <see cref="T:SuperWebSocket.Protocol.WebSocketReceiveFilterBase"/> class. - </summary> - <param name="previousReceiveFilter">The previous receive filter.</param> - </member> - <member name="M:SuperWebSocket.Protocol.WebSocketReceiveFilterBase.Handshake(SuperWebSocket.Protocol.IProtocolProcessor,SuperWebSocket.IWebSocketSession)"> - <summary> - Handshakes the specified protocol processor. - </summary> - <param name="protocolProcessor">The protocol processor.</param> - <param name="session">The session.</param> - <returns></returns> - </member> - <member name="P:SuperWebSocket.Protocol.WebSocketReceiveFilterBase.HandshakeRequestInfo"> - <summary> - Gets the handshake request info. - </summary> - </member> - <member name="M:SuperWebSocket.Protocol.WebSocketDataReceiveFilter.Reset"> - <summary> - Resets this instance. - </summary> - </member> - <member name="T:SuperWebSocket.SubProtocol.AsyncJsonSubCommand`1"> - <summary> - Async json sub command - </summary> - <typeparam name="TJsonCommandInfo">The type of the json command info.</typeparam> - </member> - <member name="T:SuperWebSocket.SubProtocol.AsyncJsonSubCommand`2"> - <summary> - Async json sub command - </summary> - <typeparam name="TWebSocketSession">The type of the web socket session.</typeparam> - <typeparam name="TJsonCommandInfo">The type of the json command info.</typeparam> - </member> - <member name="T:SuperWebSocket.SubProtocol.JsonSubCommandBase`2"> - <summary> - Json SubCommand base - </summary> - <typeparam name="TWebSocketSession">The type of the web socket session.</typeparam> - <typeparam name="TJsonCommandInfo">The type of the json command info.</typeparam> - </member> - <member name="T:SuperWebSocket.SubProtocol.SubCommandBase`1"> - <summary> - SubCommand base - </summary> - <typeparam name="TWebSocketSession">The type of the web socket session.</typeparam> - </member> - <member name="T:SuperWebSocket.SubProtocol.ISubCommand`1"> - <summary> - SubCommand interface - </summary> - <typeparam name="TWebSocketSession">The type of the web socket session.</typeparam> - </member> - <member name="M:SuperWebSocket.SubProtocol.ISubCommand`1.ExecuteCommand(`0,SuperWebSocket.SubProtocol.SubRequestInfo)"> - <summary> - Executes the command. - </summary> - <param name="session">The session.</param> - <param name="requestInfo">The request info.</param> - </member> - <member name="T:SuperWebSocket.SubProtocol.ISubCommandFilterLoader"> - <summary> - The basic interface of sub command filter loader - </summary> - </member> - <member name="M:SuperWebSocket.SubProtocol.ISubCommandFilterLoader.LoadSubCommandFilters(System.Collections.Generic.IEnumerable{SuperWebSocket.SubProtocol.SubCommandFilterAttribute})"> - <summary> - Loads the sub command filters. - </summary> - <param name="globalFilters">The global filters.</param> - </member> - <member name="M:SuperWebSocket.SubProtocol.SubCommandBase`1.ExecuteCommand(`0,SuperWebSocket.SubProtocol.SubRequestInfo)"> - <summary> - Executes the command. - </summary> - <param name="session">The session.</param> - <param name="requestInfo">The request info.</param> - </member> - <member name="P:SuperWebSocket.SubProtocol.SubCommandBase`1.Name"> - <summary> - Gets the name. - </summary> - </member> - <member name="M:SuperWebSocket.SubProtocol.JsonSubCommandBase`2.#ctor"> - <summary> - Initializes a new instance of the <see cref="T:SuperWebSocket.SubProtocol.JsonSubCommandBase`2"/> class. - </summary> - </member> - <member name="M:SuperWebSocket.SubProtocol.JsonSubCommandBase`2.ExecuteCommand(`0,SuperWebSocket.SubProtocol.SubRequestInfo)"> - <summary> - Executes the command. - </summary> - <param name="session">The session.</param> - <param name="requestInfo">The request info.</param> - </member> - <member name="M:SuperWebSocket.SubProtocol.JsonSubCommandBase`2.ExecuteJsonCommand(`0,`1)"> - <summary> - Executes the json command. - </summary> - <param name="session">The session.</param> - <param name="commandInfo">The command info.</param> - </member> - <member name="M:SuperWebSocket.SubProtocol.JsonSubCommandBase`2.GetJsonMessage(`0,System.String,System.String,System.Object)"> - <summary> - Gets the json message. - </summary> - <param name="session">The session.</param> - <param name="name">The name.</param> - <param name="token">The token.</param> - <param name="content">The content.</param> - <returns></returns> - </member> - <member name="M:SuperWebSocket.SubProtocol.AsyncJsonSubCommand`2.#ctor"> - <summary> - Initializes a new instance of the <see cref="T:SuperWebSocket.SubProtocol.AsyncJsonSubCommand`2"/> class. - </summary> - </member> - <member name="M:SuperWebSocket.SubProtocol.AsyncJsonSubCommand`2.ExecuteJsonCommand(`0,`1)"> - <summary> - Executes the json command. - </summary> - <param name="session">The session.</param> - <param name="commandInfo">The command info.</param> - </member> - <member name="M:SuperWebSocket.SubProtocol.AsyncJsonSubCommand`2.ExecuteAsyncJsonCommand(`0,System.String,`1)"> - <summary> - Executes the async json command. - </summary> - <param name="session">The session.</param> - <param name="token">The token.</param> - <param name="commandInfo">The command info.</param> - </member> - <member name="M:SuperWebSocket.SubProtocol.AsyncJsonSubCommand`2.SendJsonMessage(`0,System.String,System.Object)"> - <summary> - Sends the json message. - </summary> - <param name="session">The session.</param> - <param name="token">The token.</param> - <param name="content">The content.</param> - </member> - <member name="M:SuperWebSocket.SubProtocol.AsyncJsonSubCommand`2.SendJsonMessage(`0,System.String,System.String,System.Object)"> - <summary> - Sends the json message. - </summary> - <param name="session">The session.</param> - <param name="name">The name.</param> - <param name="token">The token.</param> - <param name="content">The content.</param> - </member> - <member name="T:SuperWebSocket.SubProtocol.BasicSubCommandParser"> - <summary> - Basic sub command parser - </summary> - </member> - <member name="M:SuperWebSocket.SubProtocol.BasicSubCommandParser.ParseRequestInfo(System.String)"> - <summary> - Parses the request info. - </summary> - <param name="source">The source.</param> - <returns></returns> - </member> - <member name="T:SuperWebSocket.SubProtocol.BasicSubProtocol"> - <summary> - Default basic sub protocol implementation - </summary> - </member> - <member name="T:SuperWebSocket.SubProtocol.BasicSubProtocol`1"> - <summary> - Default basic sub protocol implementation - </summary> - </member> - <member name="T:SuperWebSocket.SubProtocol.SubProtocolBase`1"> - <summary> - SubProtocol basis - </summary> - <typeparam name="TWebSocketSession">The type of the web socket session.</typeparam> - </member> - <member name="T:SuperWebSocket.SubProtocol.ISubProtocol`1"> - <summary> - SubProtocol interface - </summary> - <typeparam name="TWebSocketSession">The type of the web socket session.</typeparam> - </member> - <member name="M:SuperWebSocket.SubProtocol.ISubProtocol`1.Initialize(SuperSocket.SocketBase.IAppServer,SuperWebSocket.Config.SubProtocolConfig,SuperSocket.SocketBase.Logging.ILog)"> - <summary> - Initializes with the specified config. - </summary> - <param name="appServer">The app server.</param> - <param name="protocolConfig">The protocol config.</param> - <param name="logger">The logger.</param> - <returns></returns> - </member> - <member name="M:SuperWebSocket.SubProtocol.ISubProtocol`1.TryGetCommand(System.String,SuperWebSocket.SubProtocol.ISubCommand{`0}@)"> - <summary> - Tries the get command. - </summary> - <param name="name">The name.</param> - <param name="command">The command.</param> - <returns></returns> - </member> - <member name="P:SuperWebSocket.SubProtocol.ISubProtocol`1.Name"> - <summary> - Gets the name. - </summary> - </member> - <member name="P:SuperWebSocket.SubProtocol.ISubProtocol`1.SubRequestParser"> - <summary> - Gets the sub request parser. - </summary> - </member> - <member name="M:SuperWebSocket.SubProtocol.SubProtocolBase`1.#ctor(System.String)"> - <summary> - Initializes a new instance of the <see cref="T:SuperWebSocket.SubProtocol.SubProtocolBase`1"/> class. - </summary> - <param name="name">The name.</param> - </member> - <member name="M:SuperWebSocket.SubProtocol.SubProtocolBase`1.Initialize(SuperSocket.SocketBase.IAppServer,SuperWebSocket.Config.SubProtocolConfig,SuperSocket.SocketBase.Logging.ILog)"> - <summary> - Initializes with the specified config. - </summary> - <param name="appServer">The app server.</param> - <param name="protocolConfig">The protocol config.</param> - <param name="logger">The logger.</param> - <returns></returns> - </member> - <member name="M:SuperWebSocket.SubProtocol.SubProtocolBase`1.TryGetCommand(System.String,SuperWebSocket.SubProtocol.ISubCommand{`0}@)"> - <summary> - Tries the get command. - </summary> - <param name="name">The name.</param> - <param name="command">The command.</param> - <returns></returns> - </member> - <member name="P:SuperWebSocket.SubProtocol.SubProtocolBase`1.Name"> - <summary> - Gets the name. - </summary> - </member> - <member name="P:SuperWebSocket.SubProtocol.SubProtocolBase`1.SubRequestParser"> - <summary> - Gets or sets the sub request parser. - </summary> - <value> - The sub request parser. - </value> - </member> - <member name="F:SuperWebSocket.SubProtocol.BasicSubProtocol`1.DefaultName"> - <summary> - Default basic sub protocol name - </summary> - </member> - <member name="M:SuperWebSocket.SubProtocol.BasicSubProtocol`1.#ctor"> - <summary> - Initializes a new instance of the <see cref="T:SuperWebSocket.SubProtocol.BasicSubProtocol`1"/> class with the calling aseembly as command assembly - </summary> - </member> - <member name="M:SuperWebSocket.SubProtocol.BasicSubProtocol`1.#ctor(System.String)"> - <summary> - Initializes a new instance of the <see cref="T:SuperWebSocket.SubProtocol.BasicSubProtocol`1"/> class with the calling aseembly as command assembly - </summary> - <param name="name">The sub protocol name.</param> - </member> - <member name="M:SuperWebSocket.SubProtocol.BasicSubProtocol`1.#ctor(System.Collections.Generic.IEnumerable{System.Reflection.Assembly})"> - <summary> - Initializes a new instance of the <see cref="T:SuperWebSocket.SubProtocol.BasicSubProtocol`1"/> class with command assemblies - </summary> - <param name="commandAssemblies">The command assemblies.</param> - </member> - <member name="M:SuperWebSocket.SubProtocol.BasicSubProtocol`1.#ctor(System.Reflection.Assembly)"> - <summary> - Initializes a new instance of the <see cref="T:SuperWebSocket.SubProtocol.BasicSubProtocol`1"/> class with single command assembly. - </summary> - <param name="commandAssembly">The command assembly.</param> - </member> - <member name="M:SuperWebSocket.SubProtocol.BasicSubProtocol`1.#ctor(System.String,System.Reflection.Assembly)"> - <summary> - Initializes a new instance of the <see cref="T:SuperWebSocket.SubProtocol.BasicSubProtocol`1"/> class with name and single command assembly. - </summary> - <param name="name">The sub protocol name.</param> - <param name="commandAssembly">The command assembly.</param> - </member> - <member name="M:SuperWebSocket.SubProtocol.BasicSubProtocol`1.#ctor(System.String,System.Collections.Generic.IEnumerable{System.Reflection.Assembly})"> - <summary> - Initializes a new instance of the <see cref="T:SuperWebSocket.SubProtocol.BasicSubProtocol`1"/> class with name and command assemblies. - </summary> - <param name="name">The sub protocol name.</param> - <param name="commandAssemblies">The command assemblies.</param> - </member> - <member name="M:SuperWebSocket.SubProtocol.BasicSubProtocol`1.#ctor(System.String,System.Collections.Generic.IEnumerable{System.Reflection.Assembly},SuperSocket.SocketBase.Protocol.IRequestInfoParser{SuperWebSocket.SubProtocol.SubRequestInfo})"> - <summary> - Initializes a new instance of the <see cref="T:SuperWebSocket.SubProtocol.BasicSubProtocol`1"/> class. - </summary> - <param name="name">The name.</param> - <param name="commandAssemblies">The command assemblies.</param> - <param name="requestInfoParser">The request info parser.</param> - </member> - <member name="M:SuperWebSocket.SubProtocol.BasicSubProtocol`1.Initialize(SuperSocket.SocketBase.IAppServer,SuperWebSocket.Config.SubProtocolConfig,SuperSocket.SocketBase.Logging.ILog)"> - <summary> - Initializes with the specified config. - </summary> - <param name="appServer">The app server.</param> - <param name="protocolConfig">The protocol config.</param> - <param name="logger">The logger.</param> - <returns></returns> - </member> - <member name="M:SuperWebSocket.SubProtocol.BasicSubProtocol`1.TryGetCommand(System.String,SuperWebSocket.SubProtocol.ISubCommand{`0}@)"> - <summary> - Tries get command from the sub protocol's command inventory. - </summary> - <param name="name">The name.</param> - <param name="command">The command.</param> - <returns></returns> - </member> - <member name="M:SuperWebSocket.SubProtocol.BasicSubProtocol.#ctor"> - <summary> - Initializes a new instance of the <see cref="T:SuperWebSocket.SubProtocol.BasicSubProtocol"/> class. - </summary> - </member> - <member name="M:SuperWebSocket.SubProtocol.BasicSubProtocol.#ctor(System.String)"> - <summary> - Initializes a new instance of the <see cref="T:SuperWebSocket.SubProtocol.BasicSubProtocol"/> class. - </summary> - <param name="name">The sub protocol name.</param> - </member> - <member name="M:SuperWebSocket.SubProtocol.BasicSubProtocol.#ctor(System.Reflection.Assembly)"> - <summary> - Initializes a new instance of the <see cref="T:SuperWebSocket.SubProtocol.BasicSubProtocol"/> class. - </summary> - <param name="commandAssembly">The command assembly.</param> - </member> - <member name="M:SuperWebSocket.SubProtocol.BasicSubProtocol.#ctor(System.Collections.Generic.IEnumerable{System.Reflection.Assembly})"> - <summary> - Initializes a new instance of the <see cref="T:SuperWebSocket.SubProtocol.BasicSubProtocol"/> class. - </summary> - <param name="commandAssemblies">The command assemblies.</param> - </member> - <member name="M:SuperWebSocket.SubProtocol.BasicSubProtocol.#ctor(System.String,System.Reflection.Assembly)"> - <summary> - Initializes a new instance of the <see cref="T:SuperWebSocket.SubProtocol.BasicSubProtocol"/> class. - </summary> - <param name="name">The sub protocol name.</param> - <param name="commandAssembly">The command assembly.</param> - </member> - <member name="M:SuperWebSocket.SubProtocol.BasicSubProtocol.#ctor(System.String,System.Collections.Generic.IEnumerable{System.Reflection.Assembly})"> - <summary> - Initializes a new instance of the <see cref="T:SuperWebSocket.SubProtocol.BasicSubProtocol"/> class. - </summary> - <param name="name">The sub protocol name.</param> - <param name="commandAssemblies">The command assemblies.</param> - </member> - <member name="M:SuperWebSocket.SubProtocol.BasicSubProtocol.#ctor(System.String,System.Collections.Generic.IEnumerable{System.Reflection.Assembly},SuperSocket.SocketBase.Protocol.IRequestInfoParser{SuperWebSocket.SubProtocol.SubRequestInfo})"> - <summary> - Initializes a new instance of the <see cref="T:SuperWebSocket.SubProtocol.BasicSubProtocol"/> class. - </summary> - <param name="name">The name.</param> - <param name="commandAssemblies">The command assemblies.</param> - <param name="requestInfoParser">The request info parser.</param> - </member> - <member name="T:SuperWebSocket.SubProtocol.ISubRequestInfo"> - <summary> - The basic interface of SubRequestInfo - </summary> - </member> - <member name="P:SuperWebSocket.SubProtocol.ISubRequestInfo.Token"> - <summary> - Gets the token. - </summary> - <value> - The token. - </value> - </member> - <member name="T:SuperWebSocket.SubProtocol.JsonSubCommand`1"> - <summary> - JsonSubCommand - </summary> - <typeparam name="TJsonCommandInfo">The type of the json command info.</typeparam> - </member> - <member name="T:SuperWebSocket.SubProtocol.JsonSubCommand`2"> - <summary> - JsonSubCommand - </summary> - <typeparam name="TWebSocketSession">The type of the web socket session.</typeparam> - <typeparam name="TJsonCommandInfo">The type of the json command info.</typeparam> - </member> - <member name="M:SuperWebSocket.SubProtocol.JsonSubCommand`2.GetJsonMessage(`0,System.Object)"> - <summary> - Gets the json message. - </summary> - <param name="session">The session.</param> - <param name="content">The content.</param> - <returns></returns> - </member> - <member name="M:SuperWebSocket.SubProtocol.JsonSubCommand`2.GetJsonMessage(`0,System.String,System.Object)"> - <summary> - Gets the json message. - </summary> - <param name="session">The session.</param> - <param name="name">The name.</param> - <param name="content">The content.</param> - <returns></returns> - </member> - <member name="M:SuperWebSocket.SubProtocol.JsonSubCommand`2.SendJsonMessage(`0,System.Object)"> - <summary> - Sends the json message. - </summary> - <param name="session">The session.</param> - <param name="content">The content.</param> - </member> - <member name="M:SuperWebSocket.SubProtocol.JsonSubCommand`2.SendJsonMessage(`0,System.String,System.Object)"> - <summary> - Sends the json message. - </summary> - <param name="session">The session.</param> - <param name="name">The name.</param> - <param name="content">The content.</param> - </member> - <member name="T:SuperWebSocket.SubProtocol.SubCommandBase"> - <summary> - SubCommand base - </summary> - </member> - <member name="T:SuperWebSocket.SubProtocol.SubCommandFilterAttribute"> - <summary> - SubCommandFilter Attribute - </summary> - </member> - <member name="P:SuperWebSocket.SubProtocol.SubCommandFilterAttribute.SubProtocol"> - <summary> - Gets or sets the sub protocol. - </summary> - <value> - The sub protocol. - </value> - </member> - <member name="T:SuperWebSocket.SubProtocol.SubRequestInfo"> - <summary> - SubProtocol RequestInfo type - </summary> - </member> - <member name="M:SuperWebSocket.SubProtocol.SubRequestInfo.#ctor(System.String,System.String,System.String)"> - <summary> - Initializes a new instance of the <see cref="T:SuperWebSocket.SubProtocol.SubRequestInfo"/> class. - </summary> - <param name="key">The key.</param> - <param name="token">The token.</param> - <param name="data">The data.</param> - </member> - <member name="P:SuperWebSocket.SubProtocol.SubRequestInfo.Token"> - <summary> - Gets the token of this request, used for callback - </summary> - </member> - <member name="T:SuperWebSocket.TextEncodingBinaryDataConverter"> - <summary> - Text encoding binary data converter - </summary> - </member> - <member name="M:SuperWebSocket.TextEncodingBinaryDataConverter.#ctor(System.Text.Encoding)"> - <summary> - Initializes a new instance of the <see cref="T:SuperWebSocket.TextEncodingBinaryDataConverter"/> class. - </summary> - <param name="encoding">The encoding.</param> - </member> - <member name="M:SuperWebSocket.TextEncodingBinaryDataConverter.ToString(System.Byte[],System.Int32,System.Int32)"> - <summary> - Returns a <see cref="T:System.String"/> that represents this instance. - </summary> - <param name="data">The data.</param> - <param name="offset">The offset.</param> - <param name="length">The length.</param> - <returns> - A <see cref="T:System.String"/> that represents this instance. - </returns> - </member> - <member name="P:SuperWebSocket.TextEncodingBinaryDataConverter.Encoding"> - <summary> - Gets the encoding. - </summary> - <value> - The encoding. - </value> - </member> - <member name="T:SuperWebSocket.WebSocketProtocol"> - <summary> - WebSocket protocol - </summary> - </member> - <member name="M:SuperWebSocket.WebSocketProtocol.#ctor"> - <summary> - Initializes a new instance of the <see cref="T:SuperWebSocket.WebSocketProtocol"/> class. - </summary> - </member> - <member name="M:SuperWebSocket.WebSocketProtocol.CreateFilter(SuperSocket.SocketBase.IAppServer,SuperSocket.SocketBase.IAppSession,System.Net.IPEndPoint)"> - <summary> - Creates the filter. - </summary> - <param name="appServer">The app server.</param> - <param name="appSession">The app session.</param> - <param name="remoteEndPoint">The remote end point.</param> - <returns></returns> - </member> - <member name="T:SuperWebSocket.DataType"> - <summary> - Blah - </summary> - </member> - <member name="F:SuperWebSocket.DataType.Raw"> - <summary> - Blah - </summary> - </member> - <member name="F:SuperWebSocket.DataType.String"> - <summary> - Blah - </summary> - </member> - <member name="T:SuperWebSocket.DataEventArgs"> - <summary> - Blah - </summary> - </member> - <member name="P:SuperWebSocket.DataEventArgs.Session"> - <summary> - Blah - </summary> - </member> - <member name="P:SuperWebSocket.DataEventArgs.Data"> - <summary> - Blah - </summary> - </member> - <member name="P:SuperWebSocket.DataEventArgs.Type"> - <summary> - Blah - </summary> - </member> - <member name="T:SuperWebSocket.IWebSocketServer"> - <summary> - WebSocket server interface - </summary> - </member> - <member name="M:SuperWebSocket.IWebSocketServer.ValidateHandshake(SuperWebSocket.IWebSocketSession,System.String)"> - <summary> - Validates the handshake request. - </summary> - <param name="session">The session.</param> - <param name="origin">The origin.</param> - <returns>the validation result</returns> - </member> - <member name="P:SuperWebSocket.IWebSocketServer.WebSocketProtocolProcessor"> - <summary> - Gets the web socket protocol processor. - </summary> - </member> - <member name="T:SuperWebSocket.WebSocketServer"> - <summary> - WebSocket AppServer - </summary> - </member> - <member name="T:SuperWebSocket.WebSocketServer`1"> - <summary> - WebSocket AppServer - </summary> - <typeparam name="TWebSocketSession">The type of the web socket session.</typeparam> - </member> - <member name="M:SuperWebSocket.WebSocketServer`1.#ctor(System.Collections.Generic.IEnumerable{SuperWebSocket.SubProtocol.ISubProtocol{`0}})"> - <summary> - Initializes a new instance of the <see cref="T:SuperWebSocket.WebSocketServer`1"/> class. - </summary> - <param name="subProtocols">The sub protocols.</param> - </member> - <member name="M:SuperWebSocket.WebSocketServer`1.#ctor(SuperWebSocket.SubProtocol.ISubProtocol{`0})"> - <summary> - Initializes a new instance of the <see cref="T:SuperWebSocket.WebSocketServer`1"/> class. - </summary> - <param name="subProtocol">The sub protocol.</param> - </member> - <member name="M:SuperWebSocket.WebSocketServer`1.#ctor"> - <summary> - Initializes a new instance of the <see cref="T:SuperWebSocket.WebSocketServer`1"/> class. - </summary> - </member> - <member name="F:SuperWebSocket.WebSocketServer`1.m_OpenHandshakeTimeOut"> - <summary> - The openning handshake timeout, in seconds - </summary> - </member> - <member name="F:SuperWebSocket.WebSocketServer`1.m_CloseHandshakeTimeOut"> - <summary> - The closing handshake timeout, in seconds - </summary> - </member> - <member name="F:SuperWebSocket.WebSocketServer`1.m_HandshakePendingQueueCheckingInterval"> - <summary> - The interval of checking handshake pending queue, in seconds - </summary> - </member> - <member name="M:SuperWebSocket.WebSocketServer`1.GetSubProtocol(System.String)"> - <summary> - Gets the sub protocol by sub protocol name. - </summary> - <param name="name">The name.</param> - <returns></returns> - </member> - <member name="M:SuperWebSocket.WebSocketServer`1.ValidateHandshake(`0,System.String)"> - <summary> - Validates the handshake request. - </summary> - <param name="session">The session.</param> - <param name="origin">The origin in the handshake request.</param> - <returns></returns> - </member> - <member name="M:SuperWebSocket.WebSocketServer`1.Setup(SuperSocket.SocketBase.Config.IRootConfig,SuperSocket.SocketBase.Config.IServerConfig)"> - <summary> - Setups with the specified root config. - </summary> - <param name="rootConfig">The root config.</param> - <param name="config">The config.</param> - <returns></returns> - </member> - <member name="M:SuperWebSocket.WebSocketServer`1.OnStartup"> - <summary> - Called when [startup]. - </summary> - </member> - <member name="M:SuperWebSocket.WebSocketServer`1.OnNewSessionConnected(`0)"> - <summary> - Called when [new session connected]. - </summary> - <param name="session">The session.</param> - </member> - <member name="M:SuperWebSocket.WebSocketServer`1.ExecuteMessage(`0,System.String)"> - <summary> - Blah - </summary> - </member> - <member name="M:SuperWebSocket.WebSocketServer`1.SetupCommands(System.Collections.Generic.Dictionary{System.String,SuperSocket.SocketBase.Command.ICommand{`0,SuperWebSocket.Protocol.IWebSocketFragment}})"> - <summary> - Setups the commands. - </summary> - <param name="discoveredCommands">The discovered commands.</param> - <returns></returns> - </member> - <member name="M:SuperWebSocket.WebSocketServer`1.ExecuteCommand(`0,SuperWebSocket.Protocol.IWebSocketFragment)"> - <summary> - Executes the command. - </summary> - <param name="session">The session.</param> - <param name="requestInfo">The request info.</param> - </member> - <member name="M:SuperWebSocket.WebSocketServer`1.JsonSerialize(System.Object)"> - <summary> - Serialize the target object by JSON - </summary> - <param name="target">The target.</param> - <returns></returns> - </member> - <member name="M:SuperWebSocket.WebSocketServer`1.JsonDeserialize(System.String,System.Type)"> - <summary> - Deserialize the JSON string to target type object. - </summary> - <param name="json">The json.</param> - <param name="type">The type.</param> - <returns></returns> - </member> - <member name="P:SuperWebSocket.WebSocketServer`1.BinaryDataConverter"> - <summary> - Gets or sets the binary data converter. - </summary> - <value> - The binary data converter. - </value> - </member> - <member name="P:SuperWebSocket.WebSocketServer`1.ReceiveFilterFactory"> - <summary> - Gets the request filter factory. - </summary> - </member> - <member name="E:SuperWebSocket.WebSocketServer`1.NewRequestReceived"> - <summary> - Occurs when [new request received]. - </summary> - <exception cref="T:System.NotSupportedException"></exception> - </member> - <member name="E:SuperWebSocket.WebSocketServer`1.NewMessageReceived"> - <summary> - Occurs when [new message received]. - </summary> - </member> - <member name="E:SuperWebSocket.WebSocketServer`1.Received"> - <summary> - Blah - </summary> - </member> - <member name="E:SuperWebSocket.WebSocketServer`1.NewDataReceived"> - <summary> - Occurs when [new data received]. - </summary> - </member> - <member name="M:SuperWebSocket.WebSocketServer.#ctor(System.Collections.Generic.IEnumerable{SuperWebSocket.SubProtocol.ISubProtocol{SuperWebSocket.WebSocketSession}})"> - <summary> - Initializes a new instance of the <see cref="T:SuperWebSocket.WebSocketServer"/> class. - </summary> - <param name="subProtocols">The sub protocols.</param> - </member> - <member name="M:SuperWebSocket.WebSocketServer.#ctor(SuperWebSocket.SubProtocol.ISubProtocol{SuperWebSocket.WebSocketSession})"> - <summary> - Initializes a new instance of the <see cref="T:SuperWebSocket.WebSocketServer"/> class. - </summary> - <param name="subProtocol">The sub protocol.</param> - </member> - <member name="M:SuperWebSocket.WebSocketServer.#ctor"> - <summary> - Initializes a new instance of the <see cref="T:SuperWebSocket.WebSocketServer"/> class. - </summary> - </member> - <member name="T:SuperWebSocket.WebSocketSession"> - <summary> - WebSocket AppSession - </summary> - </member> - <member name="P:SuperWebSocket.WebSocketSession.AppServer"> - <summary> - Gets the app server. - </summary> - </member> - </members> -</doc> diff --git a/cb-tools/SuperWebSocket/SuperWebSocket.dll b/cb-tools/SuperWebSocket/SuperWebSocket.dll deleted file mode 100644 index 572359830ad3b470a3b8be80cefa540a65b593f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 62464 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 S*X%c2?yJM!HTwSu1OE?x3H3q% diff --git a/cb-tools/SuperWebSocket/SuperWebSocket.pdb b/cb-tools/SuperWebSocket/SuperWebSocket.pdb deleted file mode 100644 index c5e3b5c44955386424edc6cc5a2fde1de96195d2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 216576 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Ã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 X%V)voz*g{Q_vak=a}NCP$$|d|Y22Kh diff --git a/cb-tools/SuperWebSocket/UninstallService.bat b/cb-tools/SuperWebSocket/UninstallService.bat deleted file mode 100644 index 9948b806..00000000 --- a/cb-tools/SuperWebSocket/UninstallService.bat +++ /dev/null @@ -1,2 +0,0 @@ -SuperSocket.SocketService.exe -u -pause \ No newline at end of file diff --git a/cb-tools/SuperWebSocket/WebSocket4Net.dll b/cb-tools/SuperWebSocket/WebSocket4Net.dll deleted file mode 100644 index 9ca44f139a5ff2379b93b37d64b58bc5b2214648..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 88576 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 ADgXcg diff --git a/cb-tools/SuperWebSocket/log4net.dll b/cb-tools/SuperWebSocket/log4net.dll deleted file mode 100644 index c3ced3548bda4109a7b789f1d9c4837bd45c7137..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 286720 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 diff --git a/cb-tools/burgbox/+plt/binoErrorbar.m b/cb-tools/burgbox/+plt/binoErrorbar.m deleted file mode 100644 index 8f49b2b8..00000000 --- a/cb-tools/burgbox/+plt/binoErrorbar.m +++ /dev/null @@ -1,24 +0,0 @@ -function [h, p, n] = binoErrorbar(axh, x, trialsByX, varargin) -%PLT.BINOERRORBAR Plot parametric binomial data with 95% CI errorbars -% H = PLT.BINOERRORBAR(axh, x, trialsByX, varargin) -% -% Part of Burgbox - -% 2013-10 CB created - -[phat, pci] = mapToCell(@(t) binofit(sum(t(:)), numel(t)), trialsByX); -% convert to percentages -phat = 100*cell2mat(phat'); -pci = 100*cell2mat(pci'); -if nargout > 2 - n = cellfun(@numel, trialsByX(:)); -end -p = phat/100; -if numel(x) > 0 - h = plt.errorbar(axh, x, phat, pci(:,1), pci(:,2), varargin{:}); -else - h = []; -end - -end - diff --git a/cb-tools/burgbox/+plt/errorbar.m b/cb-tools/burgbox/+plt/errorbar.m deleted file mode 100644 index 85eb9b61..00000000 --- a/cb-tools/burgbox/+plt/errorbar.m +++ /dev/null @@ -1,23 +0,0 @@ -function h = errorbar(axh, x, y, yL, yU, varargin) -%PLT.ERRORBAR Like MATLAB's errorbar but specify actual EB co-ordinates -% H = PLT.ERRORBAR(AXH, X, Y, YL, YU, ...) -% -% Differences to MATLAB's errorbar, this: -% * takes the actual y coordinates of the errorbar top and bottom, rather -% than the offset from y coordinates -% * always takes the axes as the first parameter -% * if there is no data to plot, then it won't throw a wobbly, it just -% wont plot anything -% -% Part of Burgbox - -% 2013-10 CB created - -if numel(x) > 0 - h = errorbar(x, y, y - yL, y - yU, varargin{:}, 'Parent', axh); -else - h = []; -end - -end - diff --git a/cb-tools/burgbox/+plt/hshade.m b/cb-tools/burgbox/+plt/hshade.m deleted file mode 100644 index 216e064a..00000000 --- a/cb-tools/burgbox/+plt/hshade.m +++ /dev/null @@ -1,37 +0,0 @@ -function [fillhandle, msg] = hshade(ax, xpoints, upper, lower, color, edge, alpha) -%USAGE: [fillhandle, msg] = fill(xpoints, upper, lower, color, edge, add, transparency) -%This function will fill a region with a color between the two vectors provided -%using the Matlab fill command. -% -%fillhandle is the returned handle to the filled region in the plot. -%xpoints= The horizontal data points (ie frequencies). Note length(Upper) -% must equal Length(lower)and must equal length(xpoints)! -%upper = the upper curve values (data can be less than lower) -%lower = the lower curve values (data can be more than upper) -%color = the color of the filled area -%edge = the color around the edge of the filled area -%add = a flag to add to the current plot or make a new one. -%transparency is a value ranging from 1 for opaque to 0 for invisible for -%the filled color only. -% -%John A. Bockstege November 2006; -%Example: -% a=rand(1,20);%Vector of random data -% b=a+2*rand(1,20);%2nd vector of data points; -% x=1:20;%horizontal vector -% [ph,msg]=jbfill(x,a,b,rand(1,3),rand(1,3),0,rand(1,1)) -% grid on -% legend('Datr') -if nargin<7;alpha=.5;end %default is to have a transparency of .5 -if nargin<6;edge='k';end %dfault edge color is black -if nargin<5;color='b';end %default color is blue - -if length(upper)==length(lower) && length(lower)==length(xpoints) - msg=''; - filled=[upper,fliplr(lower)]; - xpoints=[xpoints,fliplr(xpoints)]; - fillhandle = fill(xpoints, filled, color, 'Parent', ax);%plot the data - set(fillhandle, 'EdgeColor', edge, 'FaceAlpha', alpha, 'EdgeAlpha', alpha);%set edge color -else - msg='Error: Must use the same number of points in each vector'; -end diff --git a/cb-tools/burgbox/+plt/vshade.m b/cb-tools/burgbox/+plt/vshade.m deleted file mode 100644 index c4abb2b1..00000000 --- a/cb-tools/burgbox/+plt/vshade.m +++ /dev/null @@ -1,37 +0,0 @@ -function [fillhandle, msg] = vshade(ax, ypoints, left, right, color, edge, alpha) -%USAGE: [fillhandle, msg] = fill(xpoints, upper, lower, color, edge, add, transparency) -%This function will fill a region with a color between the two vectors provided -%using the Matlab fill command. -% -%fillhandle is the returned handle to the filled region in the plot. -%xpoints= The horizontal data points (ie frequencies). Note length(Upper) -% must equal Length(lower)and must equal length(xpoints)! -%upper = the upper curve values (data can be less than lower) -%lower = the lower curve values (data can be more than upper) -%color = the color of the filled area -%edge = the color around the edge of the filled area -%add = a flag to add to the current plot or make a new one. -%transparency is a value ranging from 1 for opaque to 0 for invisible for -%the filled color only. -% -%John A. Bockstege November 2006; -%Example: -% a=rand(1,20);%Vector of random data -% b=a+2*rand(1,20);%2nd vector of data points; -% x=1:20;%horizontal vector -% [ph,msg]=jbfill(x,a,b,rand(1,3),rand(1,3),0,rand(1,1)) -% grid on -% legend('Datr') -if nargin<7;alpha=.5;end %default is to have a transparency of .5 -if nargin<6;edge='k';end %dfault edge color is black -if nargin<5;color='b';end %default color is blue - -if length(left)==length(right) && length(right)==length(ypoints) - msg=''; - filled=[left,fliplr(right)]; - ypoints=[ypoints,fliplr(ypoints)]; - fillhandle=fill(filled,ypoints,color, 'Parent', ax);%plot the data - set(fillhandle,'EdgeColor',edge,'FaceAlpha',alpha,'EdgeAlpha',alpha);%set edge color -else - msg='Error: Must use the same number of points in each vector'; -end diff --git a/cb-tools/jsonlab/AUTHORS.txt b/cb-tools/jsonlab/AUTHORS.txt deleted file mode 100644 index 80446a89..00000000 --- a/cb-tools/jsonlab/AUTHORS.txt +++ /dev/null @@ -1,36 +0,0 @@ -The author of "jsonlab" toolbox is Qianqian Fang. Qianqian -is currently an Assistant Professor at Massachusetts General Hospital, -Harvard Medical School. - -Address: Martinos Center for Biomedical Imaging, - Massachusetts General Hospital, - Harvard Medical School - Bldg 149, 13th St, Charlestown, MA 02129, USA -URL: http://nmr.mgh.harvard.edu/~fangq/ -Email: <fangq at nmr.mgh.harvard.edu> or <fangqq at gmail.com> - - -The script loadjson.m was built upon previous works by - -- Nedialko Krouchev: http://www.mathworks.com/matlabcentral/fileexchange/25713 - date: 2009/11/02 -- François Glineur: http://www.mathworks.com/matlabcentral/fileexchange/23393 - date: 2009/03/22 -- Joel Feenstra: http://www.mathworks.com/matlabcentral/fileexchange/20565 - date: 2008/07/03 - - -This toolbox contains patches submitted by the following contributors: - -- Blake Johnson <bjohnso at bbn.com> - part of revision 341 - -- Niclas Borlin <Niclas.Borlin at cs.umu.se> - various fixes in revision 394, including - - loadjson crashes for all-zero sparse matrix. - - loadjson crashes for empty sparse matrix. - - Non-zero size of 0-by-N and N-by-0 empty matrices is lost after savejson/loadjson. - - loadjson crashes for sparse real column vector. - - loadjson crashes for sparse complex column vector. - - Data is corrupted by savejson for sparse real row vector. - - savejson crashes for sparse complex row vector. diff --git a/cb-tools/jsonlab/ChangeLog.txt b/cb-tools/jsonlab/ChangeLog.txt deleted file mode 100644 index f409e767..00000000 --- a/cb-tools/jsonlab/ChangeLog.txt +++ /dev/null @@ -1,47 +0,0 @@ -============================================================================ - - JSONlab - a toolbox to encode/decode JSON/UBJSON files in MATLAB/Octave - ----------------------------------------------------------------------------- - -JSONlab ChangeLog (key features marked by *): - -== JSONlab 0.9.8 (codename: Optimus - alpha), FangQ <fangq (at) nmr.mgh.harvard.edu> == - 2013/08/23 *Universal Binary JSON (UBJSON) support, including both saveubjson and loadubjson - -== JSONlab 0.9.1 (codename: Rodimus, update 1), FangQ <fangq (at) nmr.mgh.harvard.edu> == - 2012/12/18 *handling of various empty and sparse matrices (fixes submitted by Niclas Borlin) - -== JSONlab 0.9.0 (codename: Rodimus), FangQ <fangq (at) nmr.mgh.harvard.edu> == - - 2012/06/17 *new format for an invalid leading char, unpacking hex code in savejson - 2012/06/01 support JSONP in savejson - 2012/05/25 fix the empty cell bug (reported by Cyril Davin) - 2012/04/05 savejson can save to a file (suggested by Patrick Rapin) - -== JSONlab 0.8.1 (codename: Sentiel, Update 1), FangQ <fangq (at) nmr.mgh.harvard.edu> == - - 2012/02/28 loadjson quotation mark escape bug, see http://bit.ly/yyk1nS - 2012/01/25 patch to handle root-less objects, contributed by Blake Johnson - -== JSONlab 0.8.0 (codename: Sentiel), FangQ <fangq (at) nmr.mgh.harvard.edu> == - - 2012/01/13 *speed up loadjson by 20 fold when parsing large data arrays in matlab - 2012/01/11 remove row bracket if an array has 1 element, suggested by Mykel Kochenderfer - 2011/12/22 *accept sequence of 'param',value input in savejson and loadjson - 2011/11/18 fix struct array bug reported by Mykel Kochenderfer - -== JSONlab 0.5.1 (codename: Nexus Update 1), FangQ <fangq (at) nmr.mgh.harvard.edu> == - - 2011/10/21 fix a bug in loadjson, previous code does not use any of the acceleration - 2011/10/20 loadjson supports JSON collections - concatenated JSON objects - -== JSONlab 0.5.0 (codename: Nexus), FangQ <fangq (at) nmr.mgh.harvard.edu> == - - 2011/10/16 package and release jsonlab 0.5.0 - 2011/10/15 *add json demo and regression test, support cpx numbers, fix double quote bug - 2011/10/11 *speed up readjson dramatically, interpret _Array* tags, show data in root level - 2011/10/10 create jsonlab project, start jsonlab website, add online documentation - 2011/10/07 *speed up savejson by 25x using sprintf instead of mat2str, add options support - 2011/10/06 *savejson works for structs, cells and arrays - 2011/09/09 derive loadjson from JSON parser from MATLAB Central, draft savejson.m diff --git a/cb-tools/jsonlab/LICENSE_BSD.txt b/cb-tools/jsonlab/LICENSE_BSD.txt deleted file mode 100644 index cc5fb4d6..00000000 --- a/cb-tools/jsonlab/LICENSE_BSD.txt +++ /dev/null @@ -1,25 +0,0 @@ -Copyright 2011 Qianqian Fang <fangq at nmr.mgh.harvard.edu>. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are -permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, this list of - conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright notice, this list - of conditions and the following disclaimer in the documentation and/or other materials - provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY EXPRESS OR IMPLIED -WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS -OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -The views and conclusions contained in the software and documentation are those of the -authors and should not be interpreted as representing official policies, either expressed -or implied, of the copyright holders. diff --git a/cb-tools/jsonlab/LICENSE_GPLv3.txt b/cb-tools/jsonlab/LICENSE_GPLv3.txt deleted file mode 100644 index 94a9ed02..00000000 --- a/cb-tools/jsonlab/LICENSE_GPLv3.txt +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - <one line to give the program's name and a brief idea of what it does.> - Copyright (C) <year> <name of author> - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - <program> Copyright (C) <year> <name of author> - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -<http://www.gnu.org/licenses/>. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -<http://www.gnu.org/philosophy/why-not-lgpl.html>. diff --git a/cb-tools/jsonlab/README.txt b/cb-tools/jsonlab/README.txt deleted file mode 100644 index 83616b81..00000000 --- a/cb-tools/jsonlab/README.txt +++ /dev/null @@ -1,335 +0,0 @@ -=============================================================================== -= JSONlab = -= An open-source MATLAB/Octave JSON encoder and decoder = -=============================================================================== - -*Copyright (c) 2011-2013 Qianqian Fang <fangq at nmr.mgh.harvard.edu> -*License: BSD or GNU General Public License version 3 (GPL v3), see License*.txt -*Version: 0.9.8 (Optimus - alpha) - -------------------------------------------------------------------------------- - -Table of Content: - -I. Introduction -II. Installation -III.Using JSONlab -IV. Known Issues and TODOs -V. Contribution and feedback - -------------------------------------------------------------------------------- - -I. Introduction - -JSON ([http://www.json.org/ JavaScript Object Notation]) is a highly portable, -human-readable and "[http://en.wikipedia.org/wiki/JSON fat-free]" text format -to represent complex and hierarchical data. It is as powerful as -[http://en.wikipedia.org/wiki/XML XML], but less verbose. JSON format is widely -used for data-exchange in applications, and is essential for the wild success -of [http://en.wikipedia.org/wiki/Ajax_(programming) Ajax] and -[http://en.wikipedia.org/wiki/Web_2.0 Web2.0]. - -UBJSON (Universal Binary JSON) is a binary JSON format, specifically -optimized for compact file size and better performance while keeping -the semantics as simple as the text-based JSON format. Using the UBJSON -format allows to wrap complex binary data in a flexible and extensible -structure, making it possible to process complex and large dataset -without accuracy loss due to text conversions. - -We envision that both JSON and its binary version will serve as part of -the mainstream data-exchange formats for scientific research in the future. -It will provide the flexibility and generality achieved by other popular -general-purpose file specifications, such as -[http://www.hdfgroup.org/HDF5/whatishdf5.html HDF5], with significantly -reduced complexity and enhanced performance. - -JSONlab is a free and open-source implementation of a JSON/UBJSON encoder -and a decoder in the native MATLAB language. It can be used to convert a MATLAB -data structure (array, struct, cell, struct array and cell array) into -JSON/UBJSON formatted strings, or to decode a JSON/UBJSON file into MATLAB -data structure. JSONlab supports both MATLAB and -[http://www.gnu.org/software/octave/ GNU Octave] (a free MATLAB clone). - -------------------------------------------------------------------------------- - -II. Installation - -The installation of JSONlab is no different than any other simple -MATLAB toolbox. You only need to download/unzip the JSONlab package -to a folder, and add the folder's path to MATLAB/Octave's path list -by using the following command: - - addpath('/path/to/jsonlab'); - -If you want to add this path permanently, you need to type "pathtool", -browse to the jsonlab root folder and add to the list, then click "Save". -Then, run "rehash" in MATLAB, and type "which loadjson", if you see an -output, that means JSONlab is installed for MATLAB/Octave. - -------------------------------------------------------------------------------- - -III.Using JSONlab - -JSONlab provides two functions, loadjson.m -- a MATLAB->JSON decoder, -and savejson.m -- a MATLAB->JSON encoder, for the text-based JSON, and -two equivallent functions -- loadubjson and saveubjson for the binary -JSON. The detailed help info for the four functions can be found below: - -=== loadjson.m === -<pre> - data=loadjson(fname,opt) - or - data=loadjson(fname,'param1',value1,'param2',value2,...) - - parse a JSON (JavaScript Object Notation) file or string - - authors:Qianqian Fang (fangq<at> nmr.mgh.harvard.edu) - date: 2011/09/09 - Nedialko Krouchev: http://www.mathworks.com/matlabcentral/fileexchange/25713 - date: 2009/11/02 - Fran�ois Glineur: http://www.mathworks.com/matlabcentral/fileexchange/23393 - date: 2009/03/22 - Joel Feenstra: - http://www.mathworks.com/matlabcentral/fileexchange/20565 - date: 2008/07/03 - - $Id: loadjson.m 394 2012-12-18 17:58:11Z fangq $ - - input: - fname: input file name, if fname contains "{}" or "[]", fname - will be interpreted as a JSON string - opt: a struct to store parsing options, opt can be replaced by - a list of ('param',value) pairs. The param string is equivallent - to a field in opt. - - output: - dat: a cell array, where {...} blocks are converted into cell arrays, - and [...] are converted to arrays -</pre> - -=== savejson.m === - -<pre> - json=savejson(rootname,obj,filename) - or - json=savejson(rootname,obj,opt) - json=savejson(rootname,obj,'param1',value1,'param2',value2,...) - - convert a MATLAB object (cell, struct or array) into a JSON (JavaScript - Object Notation) string - - author: Qianqian Fang (fangq<at> nmr.mgh.harvard.edu) - created on 2011/09/09 - - $Id: savejson.m 394 2012-12-18 17:58:11Z fangq $ - - input: - rootname: name of the root-object, if set to '', will use variable name - obj: a MATLAB object (array, cell, cell array, struct, struct array) - filename: a string for the file name to save the output JSON data - opt: a struct for additional options, use [] if all use default - opt can have the following fields (first in [.|.] is the default) - - opt.FileName [''|string]: a file name to save the output JSON data - opt.FloatFormat ['%.10g'|string]: format to show each numeric element - of a 1D/2D array; - opt.ArrayIndent [1|0]: if 1, output explicit data array with - precedent indentation; if 0, no indentation - opt.ArrayToStruct[0|1]: when set to 0, savejson outputs 1D/2D - array in JSON array format; if sets to 1, an - array will be shown as a struct with fields - "_ArrayType_", "_ArraySize_" and "_ArrayData_"; for - sparse arrays, the non-zero elements will be - saved to _ArrayData_ field in triplet-format i.e. - (ix,iy,val) and "_ArrayIsSparse_" will be added - with a value of 1; for a complex array, the - _ArrayData_ array will include two columns - (4 for sparse) to record the real and imaginary - parts, and also "_ArrayIsComplex_":1 is added. - opt.ParseLogical [0|1]: if this is set to 1, logical array elem - will use true/false rather than 1/0. - opt.NoRowBracket [1|0]: if this is set to 1, arrays with a single - numerical element will be shown without a square - bracket, unless it is the root object; if 0, square - brackets are forced for any numerical arrays. - opt.ForceRootName [0|1]: when set to 1 and rootname is empty, savejson - will use the name of the passed obj variable as the - root object name; if obj is an expression and - does not have a name, 'root' will be used; if this - is set to 0 and rootname is empty, the root level - will be merged down to the lower level. - opt.Inf ['"$1_Inf_"'|string]: a customized regular expression pattern - to represent +/-Inf. The matched pattern is '([-+]*)Inf' - and $1 represents the sign. For those who want to use - 1e999 to represent Inf, they can set opt.Inf to '$11e999' - opt.NaN ['"_NaN_"'|string]: a customized regular expression pattern - to represent NaN - opt.JSONP [''|string]: to generate a JSONP output (JSON with padding), - for example, if opt.JSON='foo', the JSON data is - wrapped inside a function call as 'foo(...);' - opt.UnpackHex [1|0]: conver the 0x[hex code] output by loadjson - back to the string form - opt can be replaced by a list of ('param',value) pairs. The param - string is equivallent to a field in opt. - output: - json: a string in the JSON format (see http://json.org) - - examples: - a=struct('node',[1 9 10; 2 1 1.2], 'elem',[9 1;1 2;2 3],... - 'face',[9 01 2; 1 2 3; NaN,Inf,-Inf], 'author','FangQ'); - savejson('mesh',a) - savejson('',a,'ArrayIndent',0,'FloatFormat','\t%.5g') -</pre> - -=== loadubjson.m === - -<pre> - data=loadubjson(fname,opt) - or - data=loadubjson(fname,'param1',value1,'param2',value2,...) - - parse a JSON (JavaScript Object Notation) file or string - - authors:Qianqian Fang (fangq<at> nmr.mgh.harvard.edu) - date: 2013/08/01 - - $Id: loadubjson.m 410 2013-08-24 03:33:18Z fangq $ - - input: - fname: input file name, if fname contains "{}" or "[]", fname - will be interpreted as a UBJSON string - opt: a struct to store parsing options, opt can be replaced by - a list of ('param',value) pairs. The param string is equivallent - to a field in opt. - - output: - dat: a cell array, where {...} blocks are converted into cell arrays, - and [...] are converted to arrays -</pre> - -=== saveubjson.m === - -<pre> - json=saveubjson(rootname,obj,filename) - or - json=saveubjson(rootname,obj,opt) - json=saveubjson(rootname,obj,'param1',value1,'param2',value2,...) - - convert a MATLAB object (cell, struct or array) into a Universal - Binary JSON (UBJSON) binary string - - author: Qianqian Fang (fangq<at> nmr.mgh.harvard.edu) - created on 2013/08/17 - - $Id: saveubjson.m 410 2013-08-24 03:33:18Z fangq $ - - input: - rootname: name of the root-object, if set to '', will use variable name - obj: a MATLAB object (array, cell, cell array, struct, struct array) - filename: a string for the file name to save the output JSON data - opt: a struct for additional options, use [] if all use default - opt can have the following fields (first in [.|.] is the default) - - opt.FileName [''|string]: a file name to save the output JSON data - opt.ArrayToStruct[0|1]: when set to 0, saveubjson outputs 1D/2D - array in JSON array format; if sets to 1, an - array will be shown as a struct with fields - "_ArrayType_", "_ArraySize_" and "_ArrayData_"; for - sparse arrays, the non-zero elements will be - saved to _ArrayData_ field in triplet-format i.e. - (ix,iy,val) and "_ArrayIsSparse_" will be added - with a value of 1; for a complex array, the - _ArrayData_ array will include two columns - (4 for sparse) to record the real and imaginary - parts, and also "_ArrayIsComplex_":1 is added. - opt.ParseLogical [1|0]: if this is set to 1, logical array elem - will use true/false rather than 1/0. - opt.NoRowBracket [1|0]: if this is set to 1, arrays with a single - numerical element will be shown without a square - bracket, unless it is the root object; if 0, square - brackets are forced for any numerical arrays. - opt.ForceRootName [0|1]: when set to 1 and rootname is empty, saveubjson - will use the name of the passed obj variable as the - root object name; if obj is an expression and - does not have a name, 'root' will be used; if this - is set to 0 and rootname is empty, the root level - will be merged down to the lower level. - opt.JSONP [''|string]: to generate a JSONP output (JSON with padding), - for example, if opt.JSON='foo', the JSON data is - wrapped inside a function call as 'foo(...);' - opt.UnpackHex [1|0]: conver the 0x[hex code] output by loadjson - back to the string form - opt can be replaced by a list of ('param',value) pairs. The param - string is equivallent to a field in opt. - output: - json: a string in the JSON format (see http://json.org) - - examples: - a=struct('node',[1 9 10; 2 1 1.2], 'elem',[9 1;1 2;2 3],... - 'face',[9 01 2; 1 2 3; NaN,Inf,-Inf], 'author','FangQ'); - saveubjson('mesh',a) - saveubjson('',a,'ArrayIndent',0,'FloatFormat','\t%.5g') -</pre> - - -=== examples === - -Under the "examples" folder, you can find several scripts to demonstrate the -basic utilities of JSONlab. Running the "demo_jsonlab_basic.m" script, you -will see the conversions from MATLAB data structure to JSON text and backward. -In "jsonlab_selftest.m", we load complex JSON files downloaded from the Internet -and validate the loadjson/savejson functions for regression testing purposes. -Similarly, a "demo_ubjson_basic.m" script is provided to test the saveubjson -and loadubjson pairs for various matlab data structures. - -Please run these examples and understand how JSONlab works before you use -it to process your data. - -------------------------------------------------------------------------------- - -IV. Known Issues and TODOs - -JSONlab has several known limitations. We are striving to make it more general -and robust. Hopefully in a few future releases, the limitations become less. - -Here are the known issues: - -# Any high-dimensional cell-array will be converted to a 1D array; -# When processing names containing multi-byte characters, Octave and MATLAB \ -can give different field-names; you can use feature('DefaultCharacterSet','latin1') \ -in MATLAB to get consistant results -# Can not handle classes. -# saveubjson has not yet supported arbitrary data ([H] in the UBJSON specification) -# saveubjson now converts a logical array into a uint8 ([U]) array for now -# an unofficial N-D array count syntax is implemented in saveubjson. We are \ -actively communicating with the UBJSON spec maintainer to investigate the \ -possibility of making it upstream - -------------------------------------------------------------------------------- - -V. Contribution and feedback - -JSONlab is an open-source project. This means you can not only use it and modify -it as you wish, but also you can contribute your changes back to JSONlab so -that everyone else can enjoy the improvement. For anyone who want to contribute, -please download JSONlab source code from it's subversion repository by using the -following command: - - svn co https://iso2mesh.svn.sourceforge.net/svnroot/iso2mesh/trunk/jsonlab jsonlab - -You can make changes to the files as needed. Once you are satisfied with your -changes, and ready to share it with others, please cd the root directory of -JSONlab, and type - - svn diff > yourname_featurename.patch - -You then email the .patch file to JSONlab's maintainer, Qianqian Fang, at -the email address shown in the beginning of this file. Qianqian will review -the changes and commit it to the subversion if they are satisfactory. - -We appreciate any suggestions and feedbacks from you. Please use iso2mesh's -mailing list to report any questions you may have with JSONlab: - -http://groups.google.com/group/iso2mesh-users?hl=en&pli=1 - -(Subscription to the mailing list is needed in order to post messages). diff --git a/cb-tools/jsonlab/examples/demo_jsonlab_basic.m b/cb-tools/jsonlab/examples/demo_jsonlab_basic.m deleted file mode 100644 index d094a64e..00000000 --- a/cb-tools/jsonlab/examples/demo_jsonlab_basic.m +++ /dev/null @@ -1,161 +0,0 @@ -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Demonstration of Basic Utilities of JSONlab -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -rngstate = rand ('state'); -randseed=hex2dec('623F9A9E'); -clear data2json json2data - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a simple scalar value \n') -fprintf(1,'%%=================================================\n\n') - -data2json=pi -savejson('',data2json) -json2data=loadjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a complex number\n') -fprintf(1,'%%=================================================\n\n') - -clear i; -data2json=1+2*i -savejson('',data2json) -json2data=loadjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a complex matrix\n') -fprintf(1,'%%=================================================\n\n') - -data2json=magic(6); -data2json=data2json(:,1:3)+data2json(:,4:6)*i -savejson('',data2json) -json2data=loadjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% MATLAB special constants\n') -fprintf(1,'%%=================================================\n\n') - -data2json=[NaN Inf -Inf] -savejson('specials',data2json) -json2data=loadjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a real sparse matrix\n') -fprintf(1,'%%=================================================\n\n') - -data2json=sprand(10,10,0.1) -savejson('sparse',data2json) -json2data=loadjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a complex sparse matrix\n') -fprintf(1,'%%=================================================\n\n') - -data2json=data2json-data2json*i -savejson('complex_sparse',data2json) -json2data=loadjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% an all-zero sparse matrix\n') -fprintf(1,'%%=================================================\n\n') - -data2json=sparse(2,3); -savejson('all_zero_sparse',data2json) -json2data=loadjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% an empty sparse matrix\n') -fprintf(1,'%%=================================================\n\n') - -data2json=sparse([]); -savejson('empty_sparse',data2json) -json2data=loadjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% an empty 0-by-0 real matrix\n') -fprintf(1,'%%=================================================\n\n') - -data2json=[]; -savejson('empty_0by0_real',data2json) -json2data=loadjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% an empty 0-by-3 real matrix\n') -fprintf(1,'%%=================================================\n\n') - -data2json=zeros(0,3); -savejson('empty_0by3_real',data2json) -json2data=loadjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a sparse real column vector\n') -fprintf(1,'%%=================================================\n\n') - -data2json=sparse([0,3,0,1,4]'); -savejson('sparse_column_vector',data2json) -json2data=loadjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a sparse complex column vector\n') -fprintf(1,'%%=================================================\n\n') - -data2json=data2json-1i*data2json; -savejson('complex_sparse_column_vector',data2json) -json2data=loadjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a sparse real row vector\n') -fprintf(1,'%%=================================================\n\n') - -data2json=sparse([0,3,0,1,4]); -savejson('sparse_row_vector',data2json) -json2data=loadjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a sparse complex row vector\n') -fprintf(1,'%%=================================================\n\n') - -data2json=data2json-1i*data2json; -savejson('complex_sparse_row_vector',data2json) -json2data=loadjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a structure\n') -fprintf(1,'%%=================================================\n\n') - -data2json=struct('name','Think Different','year',1997,'magic',magic(3),... - 'misfits',[Inf,NaN],'embedded',struct('left',true,'right',false)) -savejson('astruct',data2json,struct('ParseLogical',1)) -json2data=loadjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a structure array\n') -fprintf(1,'%%=================================================\n\n') - -data2json=struct('name','Nexus Prime','rank',9); -data2json(2)=struct('name','Sentinel Prime','rank',9); -data2json(3)=struct('name','Optimus Prime','rank',9); -savejson('Supreme Commander',data2json) -json2data=loadjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a cell array\n') -fprintf(1,'%%=================================================\n\n') - -data2json=cell(3,1); -data2json{1}=struct('buzz',1.1,'rex',1.2,'bo',1.3,'hamm',2.0,'slink',2.1,'potato',2.2,... - 'woody',3.0,'sarge',3.1,'etch',4.0,'lenny',5.0,'squeeze',6.0,'wheezy',7.0); -data2json{2}=struct('Ubuntu',['Kubuntu';'Xubuntu';'Lubuntu']); -data2json{3}=[10.04,10.10,11.04,11.10] -savejson('debian',data2json,struct('FloatFormat','%.2f')) -json2data=loadjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% invalid field-name handling\n') -fprintf(1,'%%=================================================\n\n') - -json2data=loadjson('{"ValidName":1, "_InvalidName":2, ":Field:":3, "项目":"绝密"}') - -rand ('state',rngstate); - diff --git a/cb-tools/jsonlab/examples/demo_ubjson_basic.m b/cb-tools/jsonlab/examples/demo_ubjson_basic.m deleted file mode 100644 index fd5096c3..00000000 --- a/cb-tools/jsonlab/examples/demo_ubjson_basic.m +++ /dev/null @@ -1,161 +0,0 @@ -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Demonstration of Basic Utilities of JSONlab -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -rngstate = rand ('state'); -randseed=hex2dec('623F9A9E'); -clear data2json json2data - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a simple scalar value \n') -fprintf(1,'%%=================================================\n\n') - -data2json=pi -saveubjson('',data2json) -json2data=loadubjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a complex number\n') -fprintf(1,'%%=================================================\n\n') - -clear i; -data2json=1+2*i -saveubjson('',data2json) -json2data=loadubjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a complex matrix\n') -fprintf(1,'%%=================================================\n\n') - -data2json=magic(6); -data2json=data2json(:,1:3)+data2json(:,4:6)*i -saveubjson('',data2json) -json2data=loadubjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% MATLAB special constants\n') -fprintf(1,'%%=================================================\n\n') - -data2json=[NaN Inf -Inf] -saveubjson('specials',data2json) -json2data=loadubjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a real sparse matrix\n') -fprintf(1,'%%=================================================\n\n') - -data2json=sprand(10,10,0.1) -saveubjson('sparse',data2json) -json2data=loadubjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a complex sparse matrix\n') -fprintf(1,'%%=================================================\n\n') - -data2json=data2json-data2json*i -saveubjson('complex_sparse',data2json) -json2data=loadubjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% an all-zero sparse matrix\n') -fprintf(1,'%%=================================================\n\n') - -data2json=sparse(2,3); -saveubjson('all_zero_sparse',data2json) -json2data=loadubjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% an empty sparse matrix\n') -fprintf(1,'%%=================================================\n\n') - -data2json=sparse([]); -saveubjson('empty_sparse',data2json) -json2data=loadubjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% an empty 0-by-0 real matrix\n') -fprintf(1,'%%=================================================\n\n') - -data2json=[]; -saveubjson('empty_0by0_real',data2json) -json2data=loadubjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% an empty 0-by-3 real matrix\n') -fprintf(1,'%%=================================================\n\n') - -data2json=zeros(0,3); -saveubjson('empty_0by3_real',data2json) -json2data=loadubjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a sparse real column vector\n') -fprintf(1,'%%=================================================\n\n') - -data2json=sparse([0,3,0,1,4]'); -saveubjson('sparse_column_vector',data2json) -json2data=loadubjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a sparse complex column vector\n') -fprintf(1,'%%=================================================\n\n') - -data2json=data2json-1i*data2json; -saveubjson('complex_sparse_column_vector',data2json) -json2data=loadubjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a sparse real row vector\n') -fprintf(1,'%%=================================================\n\n') - -data2json=sparse([0,3,0,1,4]); -saveubjson('sparse_row_vector',data2json) -json2data=loadubjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a sparse complex row vector\n') -fprintf(1,'%%=================================================\n\n') - -data2json=data2json-1i*data2json; -saveubjson('complex_sparse_row_vector',data2json) -json2data=loadubjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a structure\n') -fprintf(1,'%%=================================================\n\n') - -data2json=struct('name','Think Different','year',1997,'magic',magic(3),... - 'misfits',[Inf,NaN],'embedded',struct('left',true,'right',false)) -saveubjson('astruct',data2json,struct('ParseLogical',1)) -json2data=loadubjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a structure array\n') -fprintf(1,'%%=================================================\n\n') - -data2json=struct('name','Nexus Prime','rank',9); -data2json(2)=struct('name','Sentinel Prime','rank',9); -data2json(3)=struct('name','Optimus Prime','rank',9); -saveubjson('Supreme Commander',data2json) -json2data=loadubjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a cell array\n') -fprintf(1,'%%=================================================\n\n') - -data2json=cell(3,1); -data2json{1}=struct('buzz',1.1,'rex',1.2,'bo',1.3,'hamm',2.0,'slink',2.1,'potato',2.2,... - 'woody',3.0,'sarge',3.1,'etch',4.0,'lenny',5.0,'squeeze',6.0,'wheezy',7.0); -data2json{2}=struct('Ubuntu',['Kubuntu';'Xubuntu';'Lubuntu']); -data2json{3}=[10.04,10.10,11.04,11.10] -saveubjson('debian',data2json,struct('FloatFormat','%.2f')) -json2data=loadubjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% invalid field-name handling\n') -fprintf(1,'%%=================================================\n\n') - -json2data=loadubjson(saveubjson('',loadjson('{"ValidName":1, "_InvalidName":2, ":Field:":3, "项目":"绝密"}'))) - -rand ('state',rngstate); - diff --git a/cb-tools/jsonlab/examples/example1.json b/cb-tools/jsonlab/examples/example1.json deleted file mode 100644 index be0993ef..00000000 --- a/cb-tools/jsonlab/examples/example1.json +++ /dev/null @@ -1,23 +0,0 @@ - { - "firstName": "John", - "lastName": "Smith", - "age": 25, - "address": - { - "streetAddress": "21 2nd Street", - "city": "New York", - "state": "NY", - "postalCode": "10021" - }, - "phoneNumber": - [ - { - "type": "home", - "number": "212 555-1234" - }, - { - "type": "fax", - "number": "646 555-4567" - } - ] - } diff --git a/cb-tools/jsonlab/examples/example2.json b/cb-tools/jsonlab/examples/example2.json deleted file mode 100644 index eacfbf5e..00000000 --- a/cb-tools/jsonlab/examples/example2.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "glossary": { - "title": "example glossary", - "GlossDiv": { - "title": "S", - "GlossList": { - "GlossEntry": { - "ID": "SGML", - "SortAs": "SGML", - "GlossTerm": "Standard Generalized Markup Language", - "Acronym": "SGML", - "Abbrev": "ISO 8879:1986", - "GlossDef": { - "para": "A meta-markup language, used to create markup languages such as DocBook.", - "GlossSeeAlso": ["GML", "XML"] - }, - "GlossSee": "markup" - } - } - } - } -} diff --git a/cb-tools/jsonlab/examples/example3.json b/cb-tools/jsonlab/examples/example3.json deleted file mode 100644 index b7ca9411..00000000 --- a/cb-tools/jsonlab/examples/example3.json +++ /dev/null @@ -1,11 +0,0 @@ -{"menu": { - "id": "file", - "value": "_&File", - "popup": { - "menuitem": [ - {"value": "_&New", "onclick": "CreateNewDoc(\"\"\")"}, - {"value": "_&Open", "onclick": "OpenDoc()"}, - {"value": "_&Close", "onclick": "CloseDoc()"} - ] - } -}} diff --git a/cb-tools/jsonlab/examples/example4.json b/cb-tools/jsonlab/examples/example4.json deleted file mode 100644 index 66deeafb..00000000 --- a/cb-tools/jsonlab/examples/example4.json +++ /dev/null @@ -1,34 +0,0 @@ -[ - { - "sample" : { - "rho" : 1 - } - }, - { - "sample" : { - "rho" : 2 - } - }, - [ - { - "_ArrayType_" : "double", - "_ArraySize_" : [1,2], - "_ArrayData_" : [1,0] - }, - { - "_ArrayType_" : "double", - "_ArraySize_" : [1,2], - "_ArrayData_" : [1,1] - }, - { - "_ArrayType_" : "double", - "_ArraySize_" : [1,2], - "_ArrayData_" : [1,2] - } - ], - [ - "Paper", - "Scissors", - "Stone" - ] -] diff --git a/cb-tools/jsonlab/examples/jsonlab_basictest.matlab b/cb-tools/jsonlab/examples/jsonlab_basictest.matlab deleted file mode 100644 index ff8380a1..00000000 --- a/cb-tools/jsonlab/examples/jsonlab_basictest.matlab +++ /dev/null @@ -1,361 +0,0 @@ - - < M A T L A B > - Copyright 1984-2007 The MathWorks, Inc. - Version 7.4.0.287 (R2007a) - January 29, 2007 - - - To get started, type one of these: helpwin, helpdesk, or demo. - For product information, visit www.mathworks.com. - ->> >> >> >> >> >> >> >> >> -%================================================= ->> % a simple scalar value ->> %================================================= - ->> >> -data2json = - - 3.1416 - ->> -ans = - -[3.141592654] - - ->> -json2data = - - 3.1416 - ->> >> -%================================================= ->> % a complex number ->> %================================================= - ->> >> >> -data2json = - - 1.0000 + 2.0000i - ->> -ans = - -{ - "_ArrayType_": "double", - "_ArraySize_": [1,1], - "_ArrayIsComplex_": 1, - "_ArrayData_": [1,2] -} - - ->> -json2data = - - 1.0000 + 2.0000i - ->> >> -%================================================= ->> % a complex matrix ->> %================================================= - ->> >> >> -data2json = - - 35.0000 +26.0000i 1.0000 +19.0000i 6.0000 +24.0000i - 3.0000 +21.0000i 32.0000 +23.0000i 7.0000 +25.0000i - 31.0000 +22.0000i 9.0000 +27.0000i 2.0000 +20.0000i - 8.0000 +17.0000i 28.0000 +10.0000i 33.0000 +15.0000i - 30.0000 +12.0000i 5.0000 +14.0000i 34.0000 +16.0000i - 4.0000 +13.0000i 36.0000 +18.0000i 29.0000 +11.0000i - ->> -ans = - -{ - "_ArrayType_": "double", - "_ArraySize_": [6,3], - "_ArrayIsComplex_": 1, - "_ArrayData_": [ - [35,26], - [3,21], - [31,22], - [8,17], - [30,12], - [4,13], - [1,19], - [32,23], - [9,27], - [28,10], - [5,14], - [36,18], - [6,24], - [7,25], - [2,20], - [33,15], - [34,16], - [29,11] - ] -} - - ->> -json2data = - - 35.0000 +26.0000i 1.0000 +19.0000i 6.0000 +24.0000i - 3.0000 +21.0000i 32.0000 +23.0000i 7.0000 +25.0000i - 31.0000 +22.0000i 9.0000 +27.0000i 2.0000 +20.0000i - 8.0000 +17.0000i 28.0000 +10.0000i 33.0000 +15.0000i - 30.0000 +12.0000i 5.0000 +14.0000i 34.0000 +16.0000i - 4.0000 +13.0000i 36.0000 +18.0000i 29.0000 +11.0000i - ->> >> -%================================================= ->> % MATLAB special constants ->> %================================================= - ->> >> -data2json = - - NaN Inf -Inf - ->> -ans = - -{ - "specials": ["_NaN_","_Inf_","-_Inf_"] -} - - ->> -json2data = - - specials: [NaN Inf -Inf] - ->> >> -%================================================= ->> % a real sparse matrix ->> %================================================= - ->> >> -data2json = - - (1,2) 0.6557 - (9,2) 0.7577 - (3,5) 0.8491 - (10,5) 0.7431 - (10,8) 0.3922 - (7,9) 0.6787 - (2,10) 0.0357 - (6,10) 0.9340 - (10,10) 0.6555 - ->> -ans = - -{ - "sparse": { - "_ArrayType_": "double", - "_ArraySize_": [10,10], - "_ArrayIsSparse_": 1, - "_ArrayData_": [ - [1,2,0.6557406992], - [9,2,0.7577401306], - [3,5,0.8491293059], - [10,5,0.7431324681], - [10,8,0.3922270195], - [7,9,0.6787351549], - [2,10,0.03571167857], - [6,10,0.9339932478], - [10,10,0.6554778902] - ] - } -} - - ->> -json2data = - - sparse: [10x10 double] - ->> >> -%================================================= ->> % a complex sparse matrix ->> %================================================= - ->> >> -data2json = - - (1,2) 0.6557 - 0.6557i - (9,2) 0.7577 - 0.7577i - (3,5) 0.8491 - 0.8491i - (10,5) 0.7431 - 0.7431i - (10,8) 0.3922 - 0.3922i - (7,9) 0.6787 - 0.6787i - (2,10) 0.0357 - 0.0357i - (6,10) 0.9340 - 0.9340i - (10,10) 0.6555 - 0.6555i - ->> -ans = - -{ - "complex_sparse": { - "_ArrayType_": "double", - "_ArraySize_": [10,10], - "_ArrayIsComplex_": 1, - "_ArrayIsSparse_": 1, - "_ArrayData_": [ - [1,2,0.6557406992,-0.6557406992], - [9,2,0.7577401306,-0.7577401306], - [3,5,0.8491293059,-0.8491293059], - [10,5,0.7431324681,-0.7431324681], - [10,8,0.3922270195,-0.3922270195], - [7,9,0.6787351549,-0.6787351549], - [2,10,0.03571167857,-0.03571167857], - [6,10,0.9339932478,-0.9339932478], - [10,10,0.6554778902,-0.6554778902] - ] - } -} - - ->> -json2data = - - complex_sparse: [10x10 double] - ->> >> -%================================================= ->> % a structure ->> %================================================= - ->> >> -data2json = - - name: 'Think Different' - year: 1997 - magic: [3x3 double] - misfits: [Inf NaN] - embedded: [1x1 struct] - ->> -ans = - -{ - "astruct": { - "name": "Think Different", - "year": 1997, - "magic": [ - [8,1,6], - [3,5,7], - [4,9,2] - ], - "misfits": ["_Inf_","_NaN_"], - "embedded": { - "left": true, - "right": false - } - } -} - - ->> -json2data = - - astruct: [1x1 struct] - ->> >> -%================================================= ->> % a structure array ->> %================================================= - ->> >> >> >> >> -ans = - -{ - "Supreme Commander": [ - { - "name": "Nexus Prime", - "rank": 9 - }, - { - "name": "Sentinel Prime", - "rank": 9 - }, - { - "name": "Optimus Prime", - "rank": 9 - } - ] -} - - ->> -json2data = - - Supreme_0x20_Commander: [1x3 struct] - ->> >> -%================================================= ->> % a cell array ->> %================================================= - ->> >> >> >> >> -data2json = - - [1x1 struct] - [1x1 struct] - [1x4 double] - ->> -ans = - -{ - "debian": [ - { - "buzz": 1.10, - "rex": 1.20, - "bo": 1.30, - "hamm": 2.00, - "slink": 2.10, - "potato": 2.20, - "woody": 3.00, - "sarge": 3.10, - "etch": 4.00, - "lenny": 5.00, - "squeeze": 6.00, - "wheezy": 7.00 - }, - { - "Ubuntu": [ - "Kubuntu", - "Xubuntu", - "Lubuntu" - ] - }, - [10.04,10.10,11.04,11.10] - ] -} - - ->> -json2data = - - debian: {[1x1 struct] [1x1 struct] [10.0400 10.1000 11.0400 11.1000]} - ->> >> -%================================================= ->> % invalid field-name handling ->> %================================================= - ->> >> -json2data = - - ValidName: 1 - x0x5F_InvalidName: 2 - x0x3A_Field_0x3A_: 3 - x0xE9A1B9__0xE79BAE_: '绝密' - ->> >> >> >> \ No newline at end of file diff --git a/cb-tools/jsonlab/examples/jsonlab_selftest.m b/cb-tools/jsonlab/examples/jsonlab_selftest.m deleted file mode 100644 index c29ffbe3..00000000 --- a/cb-tools/jsonlab/examples/jsonlab_selftest.m +++ /dev/null @@ -1,12 +0,0 @@ -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Regression Test Unit of loadjson and savejson -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -for i=1:4 - fname=sprintf('example%d.json',i); - if(exist(fname,'file')==0) break; end - fprintf(1,'===============================================\n>> %s\n',fname); - json=savejson('data',loadjson(fname)); - fprintf(1,'%s\n',json); - data=loadjson(json); -end diff --git a/cb-tools/jsonlab/examples/jsonlab_selftest.matlab b/cb-tools/jsonlab/examples/jsonlab_selftest.matlab deleted file mode 100644 index 84e66437..00000000 --- a/cb-tools/jsonlab/examples/jsonlab_selftest.matlab +++ /dev/null @@ -1,121 +0,0 @@ - - < M A T L A B > - Copyright 1984-2007 The MathWorks, Inc. - Version 7.4.0.287 (R2007a) - January 29, 2007 - - - To get started, type one of these: helpwin, helpdesk, or demo. - For product information, visit www.mathworks.com. - ->> >> >> >> >> =============================================== ->> example1.json -{ - "data": { - "firstName": "John", - "lastName": "Smith", - "age": 25, - "address": { - "streetAddress": "21 2nd Street", - "city": "New York", - "state": "NY", - "postalCode": "10021" - }, - "phoneNumber": [ - { - "type": "home", - "number": "212 555-1234" - }, - { - "type": "fax", - "number": "646 555-4567" - } - ] - } -} - -=============================================== ->> example2.json -{ - "data": { - "glossary": { - "title": "example glossary", - "GlossDiv": { - "title": "S", - "GlossList": { - "GlossEntry": { - "ID": "SGML", - "SortAs": "SGML", - "GlossTerm": "Standard Generalized Markup Language", - "Acronym": "SGML", - "Abbrev": "ISO 8879:1986", - "GlossDef": { - "para": "A meta-markup language, used to create markup languages such as DocBook.", - "GlossSeeAlso": [ - "GML", - "XML" - ] - }, - "GlossSee": "markup" - } - } - } - } - } -} - -=============================================== ->> example3.json -{ - "data": { - "menu": { - "id": "file", - "value": "_&File", - "popup": { - "menuitem": [ - { - "value": "_&New", - "onclick": "CreateNewDoc(\"\"\")" - }, - { - "value": "_&Open", - "onclick": "OpenDoc()" - }, - { - "value": "_&Close", - "onclick": "CloseDoc()" - } - ] - } - } - } -} - -=============================================== ->> example4.json -{ - "data": [ - { - "sample": { - "rho": 1 - } - }, - { - "sample": { - "rho": 2 - } - }, - [ - [1,0], - [1,1], - [1,2] - ], - [ - "Paper", - "Scissors", - "Stone" - ] - ] -} - ->> \ No newline at end of file diff --git a/cb-tools/jsonlab/examples/jsonlab_speedtest.m b/cb-tools/jsonlab/examples/jsonlab_speedtest.m deleted file mode 100644 index 4990fba0..00000000 --- a/cb-tools/jsonlab/examples/jsonlab_speedtest.m +++ /dev/null @@ -1,21 +0,0 @@ -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Benchmarking processing speed of savejson and loadjson -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -datalen=[1e3 1e4 1e5 1e6]; -len=length(datalen); -tsave=zeros(len,1); -tload=zeros(len,1); -for i=1:len - tic; - json=savejson('data',struct('d1',rand(datalen(i),3),'d2',rand(datalen(i),3)>0.5)); - tsave(i)=toc; - data=loadjson(json); - tload(i)=toc-tsave(i); - fprintf(1,'matrix size: %d\n',datalen(i)); -end - -loglog(datalen,tsave,'o-',datalen,tload,'r*-'); -legend('savejson runtime (s)','loadjson runtime (s)'); -xlabel('array size'); -ylabel('running time (s)'); diff --git a/cb-tools/jsonlab/jsonopt.m b/cb-tools/jsonlab/jsonopt.m deleted file mode 100644 index 4b541d33..00000000 --- a/cb-tools/jsonlab/jsonopt.m +++ /dev/null @@ -1,32 +0,0 @@ -function val=jsonopt(key,default,varargin) -% -% val=jsonopt(key,default,optstruct) -% -% setting options based on a struct. The struct can be produced -% by varargin2struct from a list of 'param','value' pairs -% -% authors:Qianqian Fang (fangq<at> nmr.mgh.harvard.edu) -% -% $Id: loadjson.m 371 2012-06-20 12:43:06Z fangq $ -% -% input: -% key: a string with which one look up a value from a struct -% default: if the key does not exist, return default -% optstruct: a struct where each sub-field is a key -% -% output: -% val: if key exists, val=optstruct.key; otherwise val=default -% -% license: -% BSD or GPL version 3, see LICENSE_{BSD,GPLv3}.txt files for details -% -% -- this function is part of jsonlab toolbox (http://iso2mesh.sf.net/cgi-bin/index.cgi?jsonlab) -% - -val=default; -if(nargin<=2) return; end -opt=varargin{1}; -if(isstruct(opt) && isfield(opt,key)) - val=getfield(opt,key); -end - diff --git a/cb-tools/jsonlab/loadjson.m b/cb-tools/jsonlab/loadjson.m deleted file mode 100644 index 2c5d77db..00000000 --- a/cb-tools/jsonlab/loadjson.m +++ /dev/null @@ -1,520 +0,0 @@ -function data = loadjson(fname,varargin) -% -% data=loadjson(fname,opt) -% or -% data=loadjson(fname,'param1',value1,'param2',value2,...) -% -% parse a JSON (JavaScript Object Notation) file or string -% -% authors:Qianqian Fang (fangq<at> nmr.mgh.harvard.edu) -% date: 2011/09/09 -% Nedialko Krouchev: http://www.mathworks.com/matlabcentral/fileexchange/25713 -% date: 2009/11/02 -% François Glineur: http://www.mathworks.com/matlabcentral/fileexchange/23393 -% date: 2009/03/22 -% Joel Feenstra: -% http://www.mathworks.com/matlabcentral/fileexchange/20565 -% date: 2008/07/03 -% -% $Id: loadjson.m 394 2012-12-18 17:58:11Z fangq $ -% -% input: -% fname: input file name, if fname contains "{}" or "[]", fname -% will be interpreted as a JSON string -% opt: a struct to store parsing options, opt can be replaced by -% a list of ('param',value) pairs. The param string is equivallent -% to a field in opt. -% -% output: -% dat: a cell array, where {...} blocks are converted into cell arrays, -% and [...] are converted to arrays -% -% license: -% BSD or GPL version 3, see LICENSE_{BSD,GPLv3}.txt files for details -% -% -- this function is part of jsonlab toolbox (http://iso2mesh.sf.net/cgi-bin/index.cgi?jsonlab) -% - -global pos inStr len esc index_esc len_esc isoct arraytoken - -if(regexp(fname,'[\{\}\]\[]','once')) - string=fname; -elseif(exist(fname,'file')) - fid = fopen(fname,'rt'); - string = fscanf(fid,'%c'); - fclose(fid); -else - error('input file does not exist'); -end - -pos = 1; len = length(string); inStr = string; -isoct=false;%this slows things down a lot! exist('OCTAVE_VERSION'); -arraytoken=find(inStr=='[' | inStr==']' | inStr=='"'); -jstr=regexprep(inStr,'\\\\',' '); -escquote=regexp(jstr,'\\"'); -arraytoken=sort([arraytoken escquote]); - -% String delimiters and escape chars identified to improve speed: -esc = find(inStr=='"' | inStr=='\' ); % comparable to: regexp(inStr, '["\\]'); -index_esc = 1; len_esc = length(esc); - -opt=varargin2struct(varargin{:}); -jsoncount=1; -while pos <= len - switch(next_char) - case '{' - data{jsoncount} = parse_object(opt); - case '[' - data{jsoncount} = parse_array(opt); - otherwise - error_pos('Outer level structure must be an object or an array'); - end - jsoncount=jsoncount+1; -end % while - -jsoncount=length(data); -if(jsoncount==1 && iscell(data)) - data=data{1}; -end - -if(~isempty(data)) - if(isstruct(data)) % data can be a struct array - data=jstruct2array(data); - elseif(iscell(data)) - data=jcell2array(data); - end -end - - -%% -function newdata=parse_collection(id,data,obj) - -if(jsoncount>0 && exist('data','var')) - if(~iscell(data)) - newdata=cell(1); - newdata{1}=data; - data=newdata; - end -end - -%% -function newdata=jcell2array(data) -len=length(data); -newdata=data; -for i=1:len - if(isstruct(data{i})) - newdata{i}=jstruct2array(data{i}); - elseif(iscell(data{i})) - newdata{i}=jcell2array(data{i}); - end -end - -%%------------------------------------------------------------------------- -function newdata=jstruct2array(data) -fn=fieldnames(data); -newdata=data; -len=length(data); -for i=1:length(fn) % depth-first - for j=1:len - if(isstruct(getfield(data(j),fn{i}))) - newdata(j)=setfield(newdata(j),fn{i},jstruct2array(getfield(data(j),fn{i}))); - end - end -end -if(~isempty(strmatch('x0x5F_ArrayType_',fn)) && ~isempty(strmatch('x0x5F_ArrayData_',fn))) - newdata=cell(len,1); - for j=1:len - ndata=cast(data(j).x0x5F_ArrayData_,data(j).x0x5F_ArrayType_); - iscpx=0; - if(~isempty(strmatch('x0x5F_ArrayIsComplex_',fn))) - if(data(j).x0x5F_ArrayIsComplex_) - iscpx=1; - end - end - if(~isempty(strmatch('x0x5F_ArrayIsSparse_',fn))) - if(data(j).x0x5F_ArrayIsSparse_) - if(~isempty(strmatch('x0x5F_ArraySize_',fn))) - dim=data(j).x0x5F_ArraySize_; - if(iscpx && size(ndata,2)==4-any(dim==1)) - ndata(:,end-1)=complex(ndata(:,end-1),ndata(:,end)); - end - if isempty(ndata) - % All-zeros sparse - ndata=sparse(dim(1),prod(dim(2:end))); - elseif dim(1)==1 - % Sparse row vector - ndata=sparse(1,ndata(:,1),ndata(:,2),dim(1),prod(dim(2:end))); - elseif dim(2)==1 - % Sparse column vector - ndata=sparse(ndata(:,1),1,ndata(:,2),dim(1),prod(dim(2:end))); - else - % Generic sparse array. - ndata=sparse(ndata(:,1),ndata(:,2),ndata(:,3),dim(1),prod(dim(2:end))); - end - else - if(iscpx && size(ndata,2)==4) - ndata(:,3)=complex(ndata(:,3),ndata(:,4)); - end - ndata=sparse(ndata(:,1),ndata(:,2),ndata(:,3)); - end - end - elseif(~isempty(strmatch('x0x5F_ArraySize_',fn))) - if(iscpx && size(ndata,2)==2) - ndata=complex(ndata(:,1),ndata(:,2)); - end - ndata=reshape(ndata(:),data(j).x0x5F_ArraySize_); - end - newdata{j}=ndata; - end - if(len==1) - newdata=newdata{1}; - end -end - -%%------------------------------------------------------------------------- -function object = parse_object(varargin) - parse_char('{'); - object = []; - if next_char ~= '}' - while 1 - str = parseStr(varargin{:}); - if isempty(str) - error_pos('Name of value at position %d cannot be empty'); - end - parse_char(':'); - val = parse_value(varargin{:}); - eval( sprintf( 'object.%s = val;', valid_field(str) ) ); - if next_char == '}' - break; - end - parse_char(','); - end - end - parse_char('}'); - -%%------------------------------------------------------------------------- - -function object = parse_array(varargin) % JSON array is written in row-major order -global pos inStr isoct - parse_char('['); - object = cell(0, 1); - dim2=[]; - if next_char ~= ']' - [endpos e1l e1r maxlevel]=matching_bracket(inStr,pos); - arraystr=['[' inStr(pos:endpos)]; - arraystr=regexprep(arraystr,'"_NaN_"','NaN'); - arraystr=regexprep(arraystr,'"([-+]*)_Inf_"','$1Inf'); - arraystr(find(arraystr==sprintf('\n')))=[]; - arraystr(find(arraystr==sprintf('\r')))=[]; - %arraystr=regexprep(arraystr,'\s*,',','); % this is slow,sometimes needed - if(~isempty(e1l) && ~isempty(e1r)) % the array is in 2D or higher D - astr=inStr((e1l+1):(e1r-1)); - astr=regexprep(astr,'"_NaN_"','NaN'); - astr=regexprep(astr,'"([-+]*)_Inf_"','$1Inf'); - astr(find(astr==sprintf('\n')))=[]; - astr(find(astr==sprintf('\r')))=[]; - astr(find(astr==' '))=''; - if(isempty(find(astr=='[', 1))) % array is 2D - dim2=length(sscanf(astr,'%f,',[1 inf])); - end - else % array is 1D - astr=arraystr(2:end-1); - astr(find(astr==' '))=''; - [obj count errmsg nextidx]=sscanf(astr,'%f,',[1,inf]); - if(nextidx>=length(astr)-1) - object=obj; - pos=endpos; - parse_char(']'); - return; - end - end - if(~isempty(dim2)) - astr=arraystr; - astr(find(astr=='['))=''; - astr(find(astr==']'))=''; - astr(find(astr==' '))=''; - [obj count errmsg nextidx]=sscanf(astr,'%f,',inf); - if(nextidx>=length(astr)-1) - object=reshape(obj,dim2,numel(obj)/dim2)'; - pos=endpos; - parse_char(']'); - return; - end - end - arraystr=regexprep(arraystr,'\]\s*,','];'); - try - if(isoct && regexp(arraystr,'"','once')) - error('Octave eval can produce empty cells for JSON-like input'); - end - object=eval(arraystr); - pos=endpos; - catch - while 1 - val = parse_value(varargin{:}); - object{end+1} = val; - if next_char == ']' - break; - end - parse_char(','); - end - end - end - % 2014-02-13 CB added check to not "simplify" cells of strings - if jsonopt('SimplifyCell',0,varargin{:}) == 1 && ~iscellstr(object) - try - oldobj=object; - object=cell2mat(object')'; - if(iscell(oldobj) && isstruct(object) && numel(object)>1 && jsonopt('SimplifyCellArray',1,varargin{:})==0) - object=oldobj; - elseif(size(object,1)>1 && ndims(object)==2) - object=object'; - end - catch - end - end - parse_char(']'); - -%%------------------------------------------------------------------------- - -function parse_char(c) - global pos inStr len - skip_whitespace; - if pos > len || inStr(pos) ~= c - error_pos(sprintf('Expected %c at position %%d', c)); - else - pos = pos + 1; - skip_whitespace; - end - -%%------------------------------------------------------------------------- - -function c = next_char - global pos inStr len - skip_whitespace; - if pos > len - c = []; - else - c = inStr(pos); - end - -%%------------------------------------------------------------------------- - -function skip_whitespace - global pos inStr len - while pos <= len && isspace(inStr(pos)) - pos = pos + 1; - end - -%%------------------------------------------------------------------------- -function str = parseStr(varargin) - global pos inStr len esc index_esc len_esc - % len, ns = length(inStr), keyboard - if inStr(pos) ~= '"' - error_pos('String starting with " expected at position %d'); - else - pos = pos + 1; - end - str = ''; - while pos <= len - while index_esc <= len_esc && esc(index_esc) < pos - index_esc = index_esc + 1; - end - if index_esc > len_esc - str = [str inStr(pos:len)]; - pos = len + 1; - break; - else - str = [str inStr(pos:esc(index_esc)-1)]; - pos = esc(index_esc); - end - nstr = length(str); switch inStr(pos) - case '"' - pos = pos + 1; - if(~isempty(str)) - if(strcmp(str,'_Inf_')) - str=Inf; - elseif(strcmp(str,'-_Inf_')) - str=-Inf; - elseif(strcmp(str,'_NaN_')) - str=NaN; - end - end - return; - case '\' - if pos+1 > len - error_pos('End of file reached right after escape character'); - end - pos = pos + 1; - switch inStr(pos) - case {'"' '\' '/'} - str(nstr+1) = inStr(pos); - pos = pos + 1; - case {'b' 'f' 'n' 'r' 't'} - str(nstr+1) = sprintf(['\' inStr(pos)]); - pos = pos + 1; - case 'u' - if pos+4 > len - error_pos('End of file reached in escaped unicode character'); - end - str(nstr+(1:6)) = inStr(pos-1:pos+4); - pos = pos + 5; - end - otherwise % should never happen - str(nstr+1) = inStr(pos), keyboard - pos = pos + 1; - end - end - error_pos('End of file while expecting end of inStr'); - -%%------------------------------------------------------------------------- - -function num = parse_number(varargin) - global pos inStr len isoct - currstr=inStr(pos:end); - numstr=0; - if(isoct~=0) - numstr=regexp(currstr,'^\s*-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+\-]?\d+)?','end'); - [num, one] = sscanf(currstr, '%f', 1); - delta=numstr+1; - else - [num, one, err, delta] = sscanf(currstr, '%f', 1); - if ~isempty(err) - error_pos('Error reading number at position %d'); - end - end - pos = pos + delta-1; - -%%------------------------------------------------------------------------- - -function val = parse_value(varargin) - global pos inStr len - true = 1; false = 0; - - switch(inStr(pos)) - case '"' - val = parseStr(varargin{:}); - return; - case '[' - val = parse_array(varargin{:}); - return; - case '{' - val = parse_object(varargin{:}); - return; - case {'-','0','1','2','3','4','5','6','7','8','9'} - val = parse_number(varargin{:}); - return; - case 't' - if pos+3 <= len && strcmpi(inStr(pos:pos+3), 'true') - val = true; - pos = pos + 4; - return; - end - case 'f' - if pos+4 <= len && strcmpi(inStr(pos:pos+4), 'false') - val = false; - pos = pos + 5; - return; - end - case 'n' - if pos+3 <= len && strcmpi(inStr(pos:pos+3), 'null') - val = []; - pos = pos + 4; - return; - end - end - error_pos('Value expected at position %d'); -%%------------------------------------------------------------------------- - -function error_pos(msg) - global pos inStr len - poShow = max(min([pos-15 pos-1 pos pos+20],len),1); - if poShow(3) == poShow(2) - poShow(3:4) = poShow(2)+[0 -1]; % display nothing after - end - msg = [sprintf(msg, pos) ': ' ... - inStr(poShow(1):poShow(2)) '<error>' inStr(poShow(3):poShow(4)) ]; - error( ['JSONparser:invalidFormat: ' msg] ); - -%%------------------------------------------------------------------------- - -function str = valid_field(str) -global isoct -% From MATLAB doc: field names must begin with a letter, which may be -% followed by any combination of letters, digits, and underscores. -% Invalid characters will be converted to underscores, and the prefix -% "x0x[Hex code]_" will be added if the first character is not a letter. - pos=regexp(str,'^[^A-Za-z]','once'); - if(~isempty(pos)) - if(~isoct) - str=regexprep(str,'^([^A-Za-z])','x0x${sprintf(''%X'',unicode2native($1))}_','once'); - else - str=sprintf('x0x%X_%s',char(str(1)),str(2:end)); - end - end - if(isempty(regexp(str,'[^0-9A-Za-z_]', 'once' ))) return; end - if(~isoct) - str=regexprep(str,'([^0-9A-Za-z_])','_0x${sprintf(''%X'',unicode2native($1))}_'); - else - pos=regexp(str,'[^0-9A-Za-z_]'); - if(isempty(pos)) return; end - str0=str; - pos0=[0 pos(:)' length(str)]; - str=''; - for i=1:length(pos) - str=[str str0(pos0(i)+1:pos(i)-1) sprintf('_0x%X_',str0(pos(i)))]; - end - if(pos(end)~=length(str)) - str=[str str0(pos0(end-1)+1:pos0(end))]; - end - end - %str(~isletter(str) & ~('0' <= str & str <= '9')) = '_'; - -%%------------------------------------------------------------------------- -function endpos = matching_quote(str,pos) -len=length(str); -while(pos<len) - if(str(pos)=='"') - if(~(pos>1 && str(pos-1)=='\')) - endpos=pos; - return; - end - end - pos=pos+1; -end -error('unmatched quotation mark'); -%%------------------------------------------------------------------------- -function [endpos e1l e1r maxlevel] = matching_bracket(str,pos) -global arraytoken -level=1; -maxlevel=level; -endpos=0; -bpos=arraytoken(arraytoken>=pos); -tokens=str(bpos); -len=length(tokens); -pos=1; -e1l=[]; -e1r=[]; -while(pos<=len) - c=tokens(pos); - if(c==']') - level=level-1; - if(isempty(e1r)) e1r=bpos(pos); end - if(level==0) - endpos=bpos(pos); - return - end - end - if(c=='[') - if(isempty(e1l)) e1l=bpos(pos); end - level=level+1; - maxlevel=max(maxlevel,level); - end - if(c=='"') - pos=matching_quote(tokens,pos+1); - end - pos=pos+1; -end -if(endpos==0) - error('unmatched "]"'); -end - diff --git a/cb-tools/jsonlab/loadubjson.m b/cb-tools/jsonlab/loadubjson.m deleted file mode 100644 index 5ea53461..00000000 --- a/cb-tools/jsonlab/loadubjson.m +++ /dev/null @@ -1,478 +0,0 @@ -function data = loadubjson(fname,varargin) -% -% data=loadubjson(fname,opt) -% or -% data=loadubjson(fname,'param1',value1,'param2',value2,...) -% -% parse a JSON (JavaScript Object Notation) file or string -% -% authors:Qianqian Fang (fangq<at> nmr.mgh.harvard.edu) -% date: 2013/08/01 -% -% $Id: loadubjson.m 410 2013-08-24 03:33:18Z fangq $ -% -% input: -% fname: input file name, if fname contains "{}" or "[]", fname -% will be interpreted as a UBJSON string -% opt: a struct to store parsing options, opt can be replaced by -% a list of ('param',value) pairs. The param string is equivallent -% to a field in opt. -% -% output: -% dat: a cell array, where {...} blocks are converted into cell arrays, -% and [...] are converted to arrays -% -% license: -% BSD or GPL version 3, see LICENSE_{BSD,GPLv3}.txt files for details -% -% -- this function is part of jsonlab toolbox (http://iso2mesh.sf.net/cgi-bin/index.cgi?jsonlab) -% - -global pos inStr len esc index_esc len_esc isoct arraytoken - -if(regexp(fname,'[\{\}\]\[]','once')) - string=fname; -elseif(exist(fname,'file')) - fid = fopen(fname,'rt'); - string = fscanf(fid,'%c'); - fclose(fid); -else - error('input file does not exist'); -end - -pos = 1; len = length(string); inStr = string; -isoct=exist('OCTAVE_VERSION'); -arraytoken=find(inStr=='[' | inStr==']' | inStr=='"'); -jstr=regexprep(inStr,'\\\\',' '); -escquote=regexp(jstr,'\\"'); -arraytoken=sort([arraytoken escquote]); - -% String delimiters and escape chars identified to improve speed: -esc = find(inStr=='"' | inStr=='\' ); % comparable to: regexp(inStr, '["\\]'); -index_esc = 1; len_esc = length(esc); - -opt=varargin2struct(varargin{:}); -jsoncount=1; -while pos <= len - switch(next_char) - case '{' - data{jsoncount} = parse_object(opt); - case '[' - data{jsoncount} = parse_array(opt); - otherwise - error_pos('Outer level structure must be an object or an array'); - end - jsoncount=jsoncount+1; -end % while - -jsoncount=length(data); -if(jsoncount==1 && iscell(data)) - data=data{1}; -end - -if(~isempty(data)) - if(isstruct(data)) % data can be a struct array - data=jstruct2array(data); - elseif(iscell(data)) - data=jcell2array(data); - end -end - - -%% -function newdata=parse_collection(id,data,obj) - -if(jsoncount>0 && exist('data','var')) - if(~iscell(data)) - newdata=cell(1); - newdata{1}=data; - data=newdata; - end -end - -%% -function newdata=jcell2array(data) -len=length(data); -newdata=data; -for i=1:len - if(isstruct(data{i})) - newdata{i}=jstruct2array(data{i}); - elseif(iscell(data{i})) - newdata{i}=jcell2array(data{i}); - end -end - -%%------------------------------------------------------------------------- -function newdata=jstruct2array(data) -fn=fieldnames(data); -newdata=data; -len=length(data); -for i=1:length(fn) % depth-first - for j=1:len - if(isstruct(getfield(data(j),fn{i}))) - newdata(j)=setfield(newdata(j),fn{i},jstruct2array(getfield(data(j),fn{i}))); - end - end -end -if(~isempty(strmatch('x0x5F_ArrayType_',fn)) && ~isempty(strmatch('x0x5F_ArrayData_',fn))) - newdata=cell(len,1); - for j=1:len - ndata=cast(data(j).x0x5F_ArrayData_,data(j).x0x5F_ArrayType_); - iscpx=0; - if(~isempty(strmatch('x0x5F_ArrayIsComplex_',fn))) - if(data(j).x0x5F_ArrayIsComplex_) - iscpx=1; - end - end - if(~isempty(strmatch('x0x5F_ArrayIsSparse_',fn))) - if(data(j).x0x5F_ArrayIsSparse_) - if(~isempty(strmatch('x0x5F_ArraySize_',fn))) - dim=double(data(j).x0x5F_ArraySize_); - if(iscpx && size(ndata,2)==4-any(dim==1)) - ndata(:,end-1)=complex(ndata(:,end-1),ndata(:,end)); - end - if isempty(ndata) - % All-zeros sparse - ndata=sparse(dim(1),prod(dim(2:end))); - elseif dim(1)==1 - % Sparse row vector - ndata=sparse(1,ndata(:,1),ndata(:,2),dim(1),prod(dim(2:end))); - elseif dim(2)==1 - % Sparse column vector - ndata=sparse(ndata(:,1),1,ndata(:,2),dim(1),prod(dim(2:end))); - else - % Generic sparse array. - ndata=sparse(ndata(:,1),ndata(:,2),ndata(:,3),dim(1),prod(dim(2:end))); - end - else - if(iscpx && size(ndata,2)==4) - ndata(:,3)=complex(ndata(:,3),ndata(:,4)); - end - ndata=sparse(ndata(:,1),ndata(:,2),ndata(:,3)); - end - end - elseif(~isempty(strmatch('x0x5F_ArraySize_',fn))) - if(iscpx && size(ndata,2)==2) - ndata=complex(ndata(:,1),ndata(:,2)); - end - ndata=reshape(ndata(:),data(j).x0x5F_ArraySize_); - end - newdata{j}=ndata; - end - if(len==1) - newdata=newdata{1}; - end -end - -%%------------------------------------------------------------------------- -function object = parse_object(varargin) - parse_char('{'); - object = []; - if next_char ~= '}' - while 1 - str = parseStr(varargin{:}); - if isempty(str) - error_pos('Name of value at position %d cannot be empty'); - end - %parse_char(':'); - val = parse_value(varargin{:}); - eval( sprintf( 'object.%s = val;', valid_field(str) ) ); - if next_char == '}' - break; - end - %parse_char(','); - end - end - parse_char('}'); - -%%------------------------------------------------------------------------- -function [cid,len]=elem_info(type) -id=strfind('iUIlLdD',type); -dataclass={'int8','uint8','int16','int32','int64','single','double'}; -bytelen=[1,1,2,4,8,4,8]; -if(id>0) - cid=dataclass{id}; - len=bytelen(id); -else - error_pos('unsupported type at position %d'); -end -%%------------------------------------------------------------------------- - - -function [data adv]=parse_block(type,count,varargin) -global pos inStr isoct -[cid,len]=elem_info(type); -if(isoct) - data=typecast(int8(inStr(pos:pos+len*count-1)),cid); -else - data=typecast(uint8(inStr(pos:pos+len*count-1)),cid); -end -adv=double(len*count); - -%%------------------------------------------------------------------------- - - -function object = parse_array(varargin) % JSON array is written in row-major order -global pos inStr isoct - parse_char('['); - object = cell(0, 1); - dim=[]; - type=''; - count=-1; - if(next_char == '$') - type=inStr(pos+1); - pos=pos+2; - end - if(next_char == '#') - pos=pos+1; - if(next_char=='[') - dim=parse_array(varargin{:}); - count=prod(double(dim)); - else - count=double(parse_number()); - end - end - if(~isempty(type)) - if(count>=0) - [object adv]=parse_block(type,count,varargin{:}); - if(~isempty(dim)) - object=reshape(object,dim); - end - pos=pos+adv; - return; - else - endpos=matching_bracket(inStr,pos); - [cid,len]=elem_info(type); - count=(endpos-pos)/len; - [object adv]=parse_block(type,count,varargin{:}); - pos=pos+adv; - parse_char(']'); - return; - end - end - - if next_char ~= ']' - while 1 - val = parse_value(varargin{:}); - object{end+1} = val; - if next_char == ']' - break; - end - %parse_char(','); - end - end - if(jsonopt('SimplifyCell',0,varargin{:})==1) - try - oldobj=object; - object=cell2mat(object')'; - if(iscell(oldobj) && isstruct(object) && numel(object)>1 && jsonopt('SimplifyCellArray',1,varargin{:})==0) - object=oldobj; - elseif(size(object,1)>1 && ndims(object)==2) - object=object'; - end - catch - end - end - if(count==-1) - parse_char(']'); - end - -%%------------------------------------------------------------------------- - -function parse_char(c) - global pos inStr len - skip_whitespace; - if pos > len || inStr(pos) ~= c - error_pos(sprintf('Expected %c at position %%d', c)); - else - pos = pos + 1; - skip_whitespace; - end - -%%------------------------------------------------------------------------- - -function c = next_char - global pos inStr len - skip_whitespace; - if pos > len - c = []; - else - c = inStr(pos); - end - -%%------------------------------------------------------------------------- - -function skip_whitespace - global pos inStr len - while pos <= len && isspace(inStr(pos)) - pos = pos + 1; - end - -%%------------------------------------------------------------------------- -function str = parseStr(varargin) - global pos inStr esc index_esc len_esc - % len, ns = length(inStr), keyboard - type=inStr(pos); - if type ~= 'S' && type ~= 'C' - error_pos('String starting with S expected at position %d'); - else - pos = pos + 1; - end - if(type == 'C') - str=inStr(pos); - pos=pos+1; - return; - end - bytelen=double(parse_number()); - if(length(inStr)>=pos+bytelen-1) - str=inStr(pos:pos+bytelen-1); - pos=pos+bytelen; - else - error_pos('End of file while expecting end of inStr'); - end - -%%------------------------------------------------------------------------- - -function num = parse_number(varargin) - global pos inStr len isoct - id=strfind('iUIlLdD',inStr(pos)); - if(isempty(id)) - error_pos('expecting a number at position %d'); - end - type={'int8','uint8','int16','int32','int64','single','double'}; - bytelen=[1,1,2,4,8,4,8]; - if(isoct) - num=typecast(int8(inStr(pos+1:pos+bytelen(id))),type{id}); - else - num=typecast(uint8(inStr(pos+1:pos+bytelen(id))),type{id}); - end - pos = pos + bytelen(id)+1; - -%%------------------------------------------------------------------------- - -function val = parse_value(varargin) - global pos inStr len - true = 1; false = 0; - - switch(inStr(pos)) - case {'S','C'} - val = parseStr(varargin{:}); - return; - case '[' - val = parse_array(varargin{:}); - return; - case '{' - val = parse_object(varargin{:}); - return; - case {'i','U','I','l','L','d','D'} - val = parse_number(varargin{:}); - return; - case 'T' - val = true; - pos = pos + 1; - return; - case 'F' - val = false; - pos = pos + 1; - return; - case {'Z','N'} - val = []; - pos = pos + 1; - return; - end - error_pos('Value expected at position %d'); -%%------------------------------------------------------------------------- - -function error_pos(msg) - global pos inStr len - poShow = max(min([pos-15 pos-1 pos pos+20],len),1); - if poShow(3) == poShow(2) - poShow(3:4) = poShow(2)+[0 -1]; % display nothing after - end - msg = [sprintf(msg, pos) ': ' ... - inStr(poShow(1):poShow(2)) '<error>' inStr(poShow(3):poShow(4)) ]; - error( ['JSONparser:invalidFormat: ' msg] ); - -%%------------------------------------------------------------------------- - -function str = valid_field(str) -global isoct -% From MATLAB doc: field names must begin with a letter, which may be -% followed by any combination of letters, digits, and underscores. -% Invalid characters will be converted to underscores, and the prefix -% "x0x[Hex code]_" will be added if the first character is not a letter. - pos=regexp(str,'^[^A-Za-z]','once'); - if(~isempty(pos)) - if(~isoct) - str=regexprep(str,'^([^A-Za-z])','x0x${sprintf(''%X'',unicode2native($1))}_','once'); - else - str=sprintf('x0x%X_%s',char(str(1)),str(2:end)); - end - end - if(isempty(regexp(str,'[^0-9A-Za-z_]', 'once' ))) return; end - if(~isoct) - str=regexprep(str,'([^0-9A-Za-z_])','_0x${sprintf(''%X'',unicode2native($1))}_'); - else - pos=regexp(str,'[^0-9A-Za-z_]'); - if(isempty(pos)) return; end - str0=str; - pos0=[0 pos(:)' length(str)]; - str=''; - for i=1:length(pos) - str=[str str0(pos0(i)+1:pos(i)-1) sprintf('_0x%X_',str0(pos(i)))]; - end - if(pos(end)~=length(str)) - str=[str str0(pos0(end-1)+1:pos0(end))]; - end - end - %str(~isletter(str) & ~('0' <= str & str <= '9')) = '_'; - -%%------------------------------------------------------------------------- -function endpos = matching_quote(str,pos) -len=length(str); -while(pos<len) - if(str(pos)=='"') - if(~(pos>1 && str(pos-1)=='\')) - endpos=pos; - return; - end - end - pos=pos+1; -end -error('unmatched quotation mark'); -%%------------------------------------------------------------------------- -function [endpos e1l e1r maxlevel] = matching_bracket(str,pos) -global arraytoken -level=1; -maxlevel=level; -endpos=0; -bpos=arraytoken(arraytoken>=pos); -tokens=str(bpos); -len=length(tokens); -pos=1; -e1l=[]; -e1r=[]; -while(pos<=len) - c=tokens(pos); - if(c==']') - level=level-1; - if(isempty(e1r)) e1r=bpos(pos); end - if(level==0) - endpos=bpos(pos); - return - end - end - if(c=='[') - if(isempty(e1l)) e1l=bpos(pos); end - level=level+1; - maxlevel=max(maxlevel,level); - end - if(c=='"') - pos=matching_quote(tokens,pos+1); - end - pos=pos+1; -end -if(endpos==0) - error('unmatched "]"'); -end - diff --git a/cb-tools/jsonlab/mergestruct.m b/cb-tools/jsonlab/mergestruct.m deleted file mode 100644 index ce344ad2..00000000 --- a/cb-tools/jsonlab/mergestruct.m +++ /dev/null @@ -1,33 +0,0 @@ -function s=mergestruct(s1,s2) -% -% s=mergestruct(s1,s2) -% -% merge two struct objects into one -% -% authors:Qianqian Fang (fangq<at> nmr.mgh.harvard.edu) -% date: 2012/12/22 -% -% input: -% s1,s2: a struct object, s1 and s2 can not be arrays -% -% output: -% s: the merged struct object. fields in s1 and s2 will be combined in s. -% -% license: -% BSD or GPL version 3, see LICENSE_{BSD,GPLv3}.txt files for details -% -% -- this function is part of jsonlab toolbox (http://iso2mesh.sf.net/cgi-bin/index.cgi?jsonlab) -% - -if(~isstruct(s1) || ~isstruct(s2)) - error('input parameters contain non-struct'); -end -if(length(s1)>1 || length(s2)>1) - error('can not merge struct arrays'); -end -fn=fieldnames(s2); -s=s1; -for i=1:length(fn) - s=setfield(s,fn{i},getfield(s2,fn{i})); -end - diff --git a/cb-tools/jsonlab/origsavejson.m b/cb-tools/jsonlab/origsavejson.m deleted file mode 100644 index a4860ee0..00000000 --- a/cb-tools/jsonlab/origsavejson.m +++ /dev/null @@ -1,386 +0,0 @@ -function json=savejson(rootname,obj,varargin) -% -% json=savejson(rootname,obj,filename) -% or -% json=savejson(rootname,obj,opt) -% json=savejson(rootname,obj,'param1',value1,'param2',value2,...) -% -% convert a MATLAB object (cell, struct or array) into a JSON (JavaScript -% Object Notation) string -% -% author: Qianqian Fang (fangq<at> nmr.mgh.harvard.edu) -% created on 2011/09/09 -% -% $Id: savejson.m 394 2012-12-18 17:58:11Z fangq $ -% -% input: -% rootname: name of the root-object, if set to '', will use variable name -% obj: a MATLAB object (array, cell, cell array, struct, struct array) -% filename: a string for the file name to save the output JSON data -% opt: a struct for additional options, use [] if all use default -% opt can have the following fields (first in [.|.] is the default) -% -% opt.FileName [''|string]: a file name to save the output JSON data -% opt.FloatFormat ['%.10g'|string]: format to show each numeric element -% of a 1D/2D array; -% opt.ArrayIndent [1|0]: if 1, output explicit data array with -% precedent indentation; if 0, no indentation -% opt.ArrayToStruct[0|1]: when set to 0, savejson outputs 1D/2D -% array in JSON array format; if sets to 1, an -% array will be shown as a struct with fields -% "_ArrayType_", "_ArraySize_" and "_ArrayData_"; for -% sparse arrays, the non-zero elements will be -% saved to _ArrayData_ field in triplet-format i.e. -% (ix,iy,val) and "_ArrayIsSparse_" will be added -% with a value of 1; for a complex array, the -% _ArrayData_ array will include two columns -% (4 for sparse) to record the real and imaginary -% parts, and also "_ArrayIsComplex_":1 is added. -% opt.ParseLogical [0|1]: if this is set to 1, logical array elem -% will use true/false rather than 1/0. -% opt.NoRowBracket [1|0]: if this is set to 1, arrays with a single -% numerical element will be shown without a square -% bracket, unless it is the root object; if 0, square -% brackets are forced for any numerical arrays. -% opt.ForceRootName [0|1]: when set to 1 and rootname is empty, savejson -% will use the name of the passed obj variable as the -% root object name; if obj is an expression and -% does not have a name, 'root' will be used; if this -% is set to 0 and rootname is empty, the root level -% will be merged down to the lower level. -% opt.Inf ['"$1_Inf_"'|string]: a customized regular expression pattern -% to represent +/-Inf. The matched pattern is '([-+]*)Inf' -% and $1 represents the sign. For those who want to use -% 1e999 to represent Inf, they can set opt.Inf to '$11e999' -% opt.NaN ['"_NaN_"'|string]: a customized regular expression pattern -% to represent NaN -% opt.JSONP [''|string]: to generate a JSONP output (JSON with padding), -% for example, if opt.JSON='foo', the JSON data is -% wrapped inside a function call as 'foo(...);' -% opt.UnpackHex [1|0]: conver the 0x[hex code] output by loadjson -% back to the string form -% opt can be replaced by a list of ('param',value) pairs. The param -% string is equivallent to a field in opt. -% output: -% json: a string in the JSON format (see http://json.org) -% -% examples: -% a=struct('node',[1 9 10; 2 1 1.2], 'elem',[9 1;1 2;2 3],... -% 'face',[9 01 2; 1 2 3; NaN,Inf,-Inf], 'author','FangQ'); -% savejson('mesh',a) -% savejson('',a,'ArrayIndent',0,'FloatFormat','\t%.5g') -% -% license: -% BSD or GPL version 3, see LICENSE_{BSD,GPLv3}.txt files for details -% -% -- this function is part of jsonlab toolbox (http://iso2mesh.sf.net/cgi-bin/index.cgi?jsonlab) -% - -if(nargin==1) - varname=inputname(1); - obj=rootname; - if(isempty(varname)) - varname='root'; - end - rootname=varname; -else - varname=inputname(2); -end -if(length(varargin)==1 && ischar(varargin{1})) - opt=struct('FileName',varargin{1}); -else - opt=varargin2struct(varargin{:}); -end -opt.IsOctave=exist('OCTAVE_VERSION'); -rootisarray=0; -rootlevel=1; -forceroot=jsonopt('ForceRootName',0,opt); -if((isnumeric(obj) || islogical(obj) || ischar(obj) || isstruct(obj) || iscell(obj)) && isempty(rootname) && forceroot==0) - rootisarray=1; - rootlevel=0; -else - if(isempty(rootname)) - rootname=varname; - end -end -if((isstruct(obj) || iscell(obj))&& isempty(rootname) && forceroot) - rootname='root'; -end -json=obj2json(rootname,obj,rootlevel,opt); -if(rootisarray) - json=sprintf('%s\n',json); -else - json=sprintf('{\n%s\n}\n',json); -end - -jsonp=jsonopt('JSONP','',opt); -if(~isempty(jsonp)) - json=sprintf('%s(%s);\n',jsonp,json); -end - -% save to a file if FileName is set, suggested by Patrick Rapin -if(~isempty(jsonopt('FileName','',opt))) - fid = fopen(opt.FileName, 'wt'); - fwrite(fid,json,'char'); - fclose(fid); -end - -%%------------------------------------------------------------------------- -function txt=obj2json(name,item,level,varargin) - -if(iscell(item)) - txt=cell2json(name,item,level,varargin{:}); -elseif(isstruct(item)) - txt=struct2json(name,item,level,varargin{:}); -elseif(ischar(item)) - txt=str2json(name,item,level,varargin{:}); -else - txt=mat2json(name,item,level,varargin{:}); -end - -%%------------------------------------------------------------------------- -function txt=cell2json(name,item,level,varargin) -txt=''; -if(~iscell(item)) - error('input is not a cell'); -end - -dim=size(item); -len=numel(item); % let's handle 1D cell first -level -padding1=repmat(sprintf('\t'),1,level-1); -padding0=repmat(sprintf('\t'),1,level); -if(len>1) - if(~isempty(name)) - txt=sprintf('%s"%s": [\n',padding0, checkname(name,varargin{:})); name=''; - else - txt=sprintf('%s[\n',padding0); - end -elseif(len==0) - if(~isempty(name)) - txt=sprintf('%s"%s": null',padding0, checkname(name,varargin{:})); name=''; - else - txt=sprintf('%snull',padding0); - end -end -for i=1:len - txt=sprintf('%s%s%s',txt,padding1,obj2json(name,item{i},level+(len>1),varargin{:})); - if(i<len) txt=sprintf('%s%s',txt,sprintf(',\n')); end -end -if(len>1) txt=sprintf('%s\n%s]',txt,padding0); end - -%%------------------------------------------------------------------------- -function txt=struct2json(name,item,level,varargin) -txt=''; -if(~isstruct(item)) - error('input is not a struct'); -end -len=numel(item); -padding1=repmat(sprintf('\t'),1,level-1); -padding0=repmat(sprintf('\t'),1,level); -sep=','; - -if(~isempty(name)) - if(len>1) txt=sprintf('%s"%s": [\n',padding0,checkname(name,varargin{:})); end -else - if(len>1) txt=sprintf('%s[\n',padding0); end -end -for e=1:len - names = fieldnames(item(e)); - if(~isempty(name) && len==1) - txt=sprintf('%s%s"%s": {\n',txt,repmat(sprintf('\t'),1,level+(len>1)), checkname(name,varargin{:})); - else - txt=sprintf('%s%s{\n',txt,repmat(sprintf('\t'),1,level+(len>1))); - end - if(~isempty(names)) - for i=1:length(names) - txt=sprintf('%s%s',txt,obj2json(names{i},getfield(item(e),... - names{i}),level+1+(len>1),varargin{:})); - if(i<length(names)) txt=sprintf('%s%s',txt,','); end - txt=sprintf('%s%s',txt,sprintf('\n')); - end - end - txt=sprintf('%s%s}',txt,repmat(sprintf('\t'),1,level+(len>1))); - if(e==len) sep=''; end - if(e<len) txt=sprintf('%s%s',txt,sprintf(',\n')); end -end -if(len>1) txt=sprintf('%s\n%s]',txt,padding0); end - -%%------------------------------------------------------------------------- -function txt=str2json(name,item,level,varargin) -txt=''; -if(~ischar(item)) - error('input is not a string'); -end -item=reshape(item, max(size(item),[1 0])); -len=size(item,1); -sep=sprintf(',\n'); - -padding1=repmat(sprintf('\t'),1,level); -padding0=repmat(sprintf('\t'),1,level+1); - -if(~isempty(name)) - if(len>1) txt=sprintf('%s"%s": [\n',padding1,checkname(name,varargin{:})); end -else - if(len>1) txt=sprintf('%s[\n',padding1); end -end -isoct=jsonopt('IsOctave',0,varargin{:}); -for e=1:len - if(isoct) - val=regexprep(item(e,:),'\\','\\'); - val=regexprep(val,'"','\"'); - val=regexprep(val,'^"','\"'); - else - val=regexprep(item(e,:),'\\','\\\\'); - val=regexprep(val,'"','\\"'); - val=regexprep(val,'^"','\\"'); - end - if(len==1) - obj=['"' checkname(name,varargin{:}) '": ' '"',val,'"']; - if(isempty(name)) obj=['"',val,'"']; end - txt=sprintf('%s%s%s%s',txt,repmat(sprintf('\t'),1,level),obj); - else - txt=sprintf('%s%s%s%s',txt,repmat(sprintf('\t'),1,level+1),['"',val,'"']); - end - if(e==len) sep=''; end - txt=sprintf('%s%s',txt,sep); -end -if(len>1) txt=sprintf('%s\n%s%s',txt,padding1,']'); end - -%%------------------------------------------------------------------------- -function txt=mat2json(name,item,level,varargin) -if(~isnumeric(item) && ~islogical(item)) - error('input is not an array'); -end - -padding1=repmat(sprintf('\t'),1,level); -padding0=repmat(sprintf('\t'),1,level+1); - -if(length(size(item))>2 || issparse(item) || ~isreal(item) || ... - isempty(item) ||jsonopt('ArrayToStruct',0,varargin{:})) - if(isempty(name)) - txt=sprintf('%s{\n%s"_ArrayType_": "%s",\n%s"_ArraySize_": %s,\n',... - padding1,padding0,class(item),padding0,regexprep(mat2str(size(item)),'\s+',',') ); - else - txt=sprintf('%s"%s": {\n%s"_ArrayType_": "%s",\n%s"_ArraySize_": %s,\n',... - padding1,checkname(name,varargin{:}),padding0,class(item),padding0,regexprep(mat2str(size(item)),'\s+',',') ); - end -else - if(isempty(name)) - txt=sprintf('%s%s',padding1,matdata2json(item,level+1,varargin{:})); - else - if(numel(item)==1 && jsonopt('NoRowBracket',1,varargin{:})==1) - numtxt=regexprep(regexprep(matdata2json(item,level+1,varargin{:}),'^\[',''),']',''); - txt=sprintf('%s"%s": %s',padding1,checkname(name,varargin{:}),numtxt); - else - txt=sprintf('%s"%s": %s',padding1,checkname(name,varargin{:}),matdata2json(item,level+1,varargin{:})); - end - end - return; -end -dataformat='%s%s%s%s%s'; - -if(issparse(item)) - [ix,iy]=find(item); - data=full(item(find(item))); - if(~isreal(item)) - data=[real(data(:)),imag(data(:))]; - if(size(item,1)==1) - % Kludge to have data's 'transposedness' match item's. - % (Necessary for complex row vector handling below.) - data=data'; - end - txt=sprintf(dataformat,txt,padding0,'"_ArrayIsComplex_": ','1', sprintf(',\n')); - end - txt=sprintf(dataformat,txt,padding0,'"_ArrayIsSparse_": ','1', sprintf(',\n')); - if(size(item,1)==1) - % Row vector, store only column indices. - txt=sprintf(dataformat,txt,padding0,'"_ArrayData_": ',... - matdata2json([iy(:),data'],level+2,varargin{:}), sprintf('\n')); - elseif(size(item,2)==1) - % Column vector, store only row indices. - txt=sprintf(dataformat,txt,padding0,'"_ArrayData_": ',... - matdata2json([ix,data],level+2,varargin{:}), sprintf('\n')); - else - % General case, store row and column indices. - txt=sprintf(dataformat,txt,padding0,'"_ArrayData_": ',... - matdata2json([ix,iy,data],level+2,varargin{:}), sprintf('\n')); - end -else - if(isreal(item)) - txt=sprintf(dataformat,txt,padding0,'"_ArrayData_": ',... - matdata2json(item(:)',level+2,varargin{:}), sprintf('\n')); - else - txt=sprintf(dataformat,txt,padding0,'"_ArrayIsComplex_": ','1', sprintf(',\n')); - txt=sprintf(dataformat,txt,padding0,'"_ArrayData_": ',... - matdata2json([real(item(:)) imag(item(:))],level+2,varargin{:}), sprintf('\n')); - end -end -txt=sprintf('%s%s%s',txt,padding1,'}'); - -%%------------------------------------------------------------------------- -function txt=matdata2json(mat,level,varargin) -if(size(mat,1)==1) - pre=''; - post=''; - level=level-1; -else - pre=sprintf('[\n'); - post=sprintf('\n%s]',repmat(sprintf('\t'),1,level-1)); -end -if(isempty(mat)) - txt='null'; - return; -end -floatformat=jsonopt('FloatFormat','%.10g',varargin{:}); -formatstr=['[' repmat([floatformat ','],1,size(mat,2)-1) [floatformat sprintf('],\n')]]; - -if(nargin>=2 && size(mat,1)>1 && jsonopt('ArrayIndent',1,varargin{:})==1) - formatstr=[repmat(sprintf('\t'),1,level) formatstr]; -end -txt=sprintf(formatstr,mat'); -txt(end-1:end)=[]; -if(islogical(mat) && jsonopt('ParseLogical',0,varargin{:})==1) - txt=regexprep(txt,'1','true'); - txt=regexprep(txt,'0','false'); -end -%txt=regexprep(mat2str(mat),'\s+',','); -%txt=regexprep(txt,';',sprintf('],\n[')); -% if(nargin>=2 && size(mat,1)>1) -% txt=regexprep(txt,'\[',[repmat(sprintf('\t'),1,level) '[']); -% end -txt=[pre txt post]; -if(any(isinf(mat(:)))) - txt=regexprep(txt,'([-+]*)Inf',jsonopt('Inf','"$1_Inf_"',varargin{:})); -end -if(any(isnan(mat(:)))) - txt=regexprep(txt,'NaN',jsonopt('NaN','"_NaN_"',varargin{:})); -end - -%%------------------------------------------------------------------------- -function newname=checkname(name,varargin) -isunpack=jsonopt('UnpackHex',1,varargin{:}); -newname=name; -if(isempty(regexp(name,'0x([0-9a-fA-F]+)_','once'))) - return -end -if(isunpack) - isoct=jsonopt('IsOctave',0,varargin{:}); - if(~isoct) - newname=regexprep(name,'(^x|_){1}0x([0-9a-fA-F]+)_','${native2unicode(hex2dec($2))}'); - else - pos=regexp(name,'(^x|_){1}0x([0-9a-fA-F]+)_','start'); - pend=regexp(name,'(^x|_){1}0x([0-9a-fA-F]+)_','end'); - if(isempty(pos)) return; end - str0=name; - pos0=[0 pend(:)' length(name)]; - newname=''; - for i=1:length(pos) - newname=[newname str0(pos0(i)+1:pos(i)-1) char(hex2dec(str0(pos(i)+3:pend(i)-1)))]; - end - if(pos(end)~=length(name)) - newname=[newname str0(pos0(end-1)+1:pos0(end))]; - end - end -end - diff --git a/cb-tools/jsonlab/savejson.m b/cb-tools/jsonlab/savejson.m deleted file mode 100644 index 5fde6935..00000000 --- a/cb-tools/jsonlab/savejson.m +++ /dev/null @@ -1,435 +0,0 @@ -function json=savejson(rootname,obj,varargin) -% -% json=savejson(rootname,obj,filename) -% or -% json=savejson(rootname,obj,opt) -% json=savejson(rootname,obj,'param1',value1,'param2',value2,...) -% -% convert a MATLAB object (cell, struct or array) into a JSON (JavaScript -% Object Notation) string -% -% author: Qianqian Fang (fangq<at> nmr.mgh.harvard.edu) -% created on 2011/09/09 -% -% $Id: savejson.m 394 2012-12-18 17:58:11Z fangq $ -% -% input: -% rootname: name of the root-object, if set to '', will use variable name -% obj: a MATLAB object (array, cell, cell array, struct, struct array) -% filename: a string for the file name to save the output JSON data -% opt: a struct for additional options, use [] if all use default -% opt can have the following fields (first in [.|.] is the default) -% -% opt.FileName [''|string]: a file name to save the output JSON data -% opt.FloatFormat ['%.10g'|string]: format to show each numeric element -% of a 1D/2D array; -% opt.ArrayIndent [1|0]: if 1, output explicit data array with -% precedent indentation; if 0, no indentation -% opt.ArrayToStruct[0|1]: when set to 0, savejson outputs 1D/2D -% array in JSON array format; if sets to 1, an -% array will be shown as a struct with fields -% "_ArrayType_", "_ArraySize_" and "_ArrayData_"; for -% sparse arrays, the non-zero elements will be -% saved to _ArrayData_ field in triplet-format i.e. -% (ix,iy,val) and "_ArrayIsSparse_" will be added -% with a value of 1; for a complex array, the -% _ArrayData_ array will include two columns -% (4 for sparse) to record the real and imaginary -% parts, and also "_ArrayIsComplex_":1 is added. -% opt.ParseLogical [0|1]: if this is set to 1, logical array elem -% will use true/false rather than 1/0. -% opt.NoRowBracket [1|0]: if this is set to 1, arrays with a single -% numerical element will be shown without a square -% bracket, unless it is the root object; if 0, square -% brackets are forced for any numerical arrays. -% opt.ForceRootName [0|1]: when set to 1 and rootname is empty, savejson -% will use the name of the passed obj variable as the -% root object name; if obj is an expression and -% does not have a name, 'root' will be used; if this -% is set to 0 and rootname is empty, the root level -% will be merged down to the lower level. -% opt.Inf ['"$1_Inf_"'|string]: a customized regular expression pattern -% to represent +/-Inf. The matched pattern is '([-+]*)Inf' -% and $1 represents the sign. For those who want to use -% 1e999 to represent Inf, they can set opt.Inf to '$11e999' -% opt.NaN ['"_NaN_"'|string]: a customized regular expression pattern -% to represent NaN -% opt.JSONP [''|string]: to generate a JSONP output (JSON with padding), -% for example, if opt.JSON='foo', the JSON data is -% wrapped inside a function call as 'foo(...);' -% opt.UnpackHex [1|0]: conver the 0x[hex code] output by loadjson -% back to the string form -% opt can be replaced by a list of ('param',value) pairs. The param -% string is equivallent to a field in opt. -% output: -% json: a string in the JSON format (see http://json.org) -% -% examples: -% a=struct('node',[1 9 10; 2 1 1.2], 'elem',[9 1;1 2;2 3],... -% 'face',[9 01 2; 1 2 3; NaN,Inf,-Inf], 'author','FangQ'); -% savejson('mesh',a) -% savejson('',a,'ArrayIndent',0,'FloatFormat','\t%.5g') -% -% license: -% BSD or GPL version 3, see LICENSE_{BSD,GPLv3}.txt files for details -% -% -- this function is part of jsonlab toolbox (http://iso2mesh.sf.net/cgi-bin/index.cgi?jsonlab) -% - -if(nargin==1) - varname=inputname(1); - obj=rootname; - if(isempty(varname)) - varname='root'; - end - rootname=varname; -else - varname=inputname(2); -end -if(length(varargin)==1 && ischar(varargin{1})) - opt=struct('FileName',varargin{1}); -else - opt=varargin2struct(varargin{:}); -end -opt.IsOctave=exist('OCTAVE_VERSION', 'builtin'); -rootisarray=0; -rootlevel=1; -forceroot=jsonopt('ForceRootName',0,opt); - -tab = sprintf('\t'); -newline = sprintf('\n'); -floatformat=jsonopt('FloatFormat','%.10g',opt); -arrayIndent=jsonopt('ArrayIndent',1,opt)==1; -parseLogical=jsonopt('ParseLogical',0,opt)==1; -noRowBracket=jsonopt('NoRowBracket',1,opt)==1; -arrayToStruct=jsonopt('ArrayToStruct',0,opt); -unpackHex=jsonopt('UnpackHex',1,opt); -jsonp=jsonopt('JSONP','',opt); - -% speedup? -maxlevel = 25; % maximum object nesting degree to save padding for -pad = cell(maxlevel+1,1); % idx 1 is zero level padding -for l = 1:maxlevel - pad{l+1} = [pad{l} tab]; -end - -if((isnumeric(obj) || islogical(obj) || ischar(obj) || isstruct(obj) || iscell(obj)) && isempty(rootname) && forceroot==0) - rootisarray=1; - rootlevel=0; -else - if(isempty(rootname)) - rootname=varname; - end -end -if((isstruct(obj) || iscell(obj))&& isempty(rootname) && forceroot) - rootname='root'; -end - -json=obj2json(checkname(rootname),obj,rootlevel); -if(rootisarray) - json=sprintf('%s\n',json); -else - json=sprintf('{\n%s\n}\n',json); -end -if(~isempty(jsonp)) - json=sprintf('%s(%s);\n',jsonp,json); -end -% save to a file if FileName is set, suggested by Patrick Rapin -if ~isempty(jsonopt('FileName','',opt)) - fid = fopen(opt.FileName, 'wt'); - fwrite(fid,json,'char'); - fclose(fid); -end - - %%------------------------------------------------------------------------- - function txt=obj2json(name,item,level) - - if(iscell(item)) - txt=cell2json(name,item,level,opt); - elseif(isstruct(item)) - txt=struct2json(name,item,level); - elseif(ischar(item)) - txt=str2json(name,item,level,opt); - else - txt=mat2json(name,item,level); - end - end - - %%------------------------------------------------------------------------- - function txt=cell2json(name,item,level,varargin) - %% Warning i need some work, and checking that loadjson mirrors this well - txt=''; - if(~iscell(item)) - error('input is not a cell'); - end - - len=numel(item); % let's handle 1D cell first -% padding1=pad{level};%repmat(sprintf('\t'),1,level-1); ??? - padding0=pad{level+1};%repmat(sprintf('\t'),1,level); - if (~isempty(name)) - txt=sprintf('%s"%s": [',padding0, name); name=''; - else - txt=sprintf('%s[',padding0); - end - if len==0 - txt = [txt ']']; - else - for i=1:len - txt=[txt newline obj2json(name,item{i},level+1)]; - if(i<len) txt=[txt ',']; end - end - txt=sprintf('%s\n%s]',txt,padding0); - end -% if(len~=0) -% if(~isempty(name)) -% txt=sprintf('%s"%s": [\n',padding0, name); name=''; -% else -% txt=sprintf('%s[\n',padding0); -% end -% elseif(len==0) -% if(~isempty(name)) -% txt=sprintf('%s"%s": null',padding0, name); name=''; -% else -% txt=sprintf('%snull',padding0); -% end -% end - - end - - %%------------------------------------------------------------------------- - function txt=struct2json(name,item,level) - if ~isstruct(item) - error('input is not a struct'); - elseif isempty(item) - txt = ''; - return - end - len=numel(item); - bracelevel = level+(len>1); - names = fieldnames(item); - checkedNames = cellfun(@checkname, names, 'uni', false); - padding0=pad{level+1}; %padding for this level - padding1=pad{bracelevel+1}; %padding for braces, maybe deeper than this - values = struct2cell(item); % all structure values in a table - elemstxt = cell(size(item)); % array to store each elements object text - for e=1:len - elements = cell(1,numel(names)); - for f=1:numel(names) - elements{f} = obj2json(checkedNames{f},values{f,e},bracelevel+1); - end - elementstxt = sprintf('\n%s,', elements{:}); % format all fields together - elemstxt{e} = elementstxt(1:end-1); - end - linepad = [newline padding1]; - intxt = sprintf(['{%s\n' padding1 '},' linepad], elemstxt{:}); % format all elements - intxt = intxt(1:(end-numel(linepad)-1)); % snip off next element tokens/formatting - if(len==1) - if ~isempty(name) - txt=[padding0 '"' name '": ' intxt]; - else - txt=intxt; - end - else - txt=[padding0 '"' name '": [' linepad intxt newline padding0 ']']; - end - end - - %%------------------------------------------------------------------------- - function txt=str2json(name,item,level,varargin) - txt=''; - if(~ischar(item)) - error('input is not a string'); - end - item=reshape(item, max(size(item),[1 0])); - len=size(item,1); - sep=sprintf(',\n'); - - padding1=pad{level+1}; - - if(~isempty(name)) - if(len>1) txt=sprintf('%s"%s": [\n',padding1,name); end - else - if(len>1) txt=sprintf('%s[\n',padding1); end - end - isoct=jsonopt('IsOctave',0,varargin{:}); - for e=1:len - if(isoct) - val=regexprep(item(e,:),'\\','\\'); - val=regexprep(val,'"','\"'); - val=regexprep(val,'^"','\"'); - else - val=regexprep(item(e,:),'\\','\\\\'); - val=regexprep(val,'"','\\"'); - val=regexprep(val,'^"','\\"'); - end - if(len==1) - obj=['"' name '": ' '"',val,'"']; - if(isempty(name)) obj=['"',val,'"']; end - txt=sprintf('%s%s%s%s',txt,padding1,obj); - else - txt=[txt pad{level+2} '"',val,'"']; - end - if(e==len) sep=''; end - txt=sprintf('%s%s',txt,sep); - end - if(len>1) txt=sprintf('%s\n%s%s',txt,padding1,']'); end - end - - %%------------------------------------------------------------------------- - function txt=mat2json(name,item,level) - if(~isnumeric(item) && ~islogical(item)) - error('input is not an array'); - end - - padding1=pad{level+1};%repmat(tab,1,level); - padding0=pad{level+2};%repmat(tab,1,level+1); - - if(length(size(item))>2 || issparse(item) || ~isreal(item) || ... - isempty(item) || arrayToStruct) - if(isempty(name)) - txt=sprintf('%s{\n%s"_ArrayType_": "%s",\n%s"_ArraySize_": %s,\n',... - padding1,padding0,class(item),padding0,regexprep(mat2str(size(item)),'\s+',',') ); - else - txt=sprintf('%s"%s": {\n%s"_ArrayType_": "%s",\n%s"_ArraySize_": %s,\n',... - padding1,name,padding0,class(item),padding0,regexprep(mat2str(size(item)),'\s+',',') ); - end - else - if(isempty(name)) - %% warning i may have broken this part - txt=sprintf('%s%s',padding1,matdata2json(item,level+1)); -% txt=sprintf('%s[%s]',padding1,matdata2json(item,level+1)); - else - txt = [padding1 '"' name '": ' matdata2json(item,level+1)]; -% if(numel(item)==1 && ~noRowBracket) -% %numtxt=regexprep(regexprep(numtxt,'^\[',''),']',''); -% %assume square brackets first + last chars -% numtxt = numtxt(2:end-1); -% end -% txt=[padding1 '"' name '": ' numtxt]; - end - return; - end - dataformat='%s%s%s%s%s'; - - if(issparse(item)) - [ix,iy]=find(item); - data=full(item(find(item))); - if(~isreal(item)) - data=[real(data(:)),imag(data(:))]; - if(size(item,1)==1) - % Kludge to have data's 'transposedness' match item's. - % (Necessary for complex row vector handling below.) - data=data'; - end - txt=sprintf(dataformat,txt,padding0,'"_ArrayIsComplex_": ','1', sprintf(',\n')); - end - txt=sprintf(dataformat,txt,padding0,'"_ArrayIsSparse_": ','1', sprintf(',\n')); - if(size(item,1)==1) - % Row vector, store only column indices. - txt=sprintf(dataformat,txt,padding0,'"_ArrayData_": ',... - matdata2json([iy(:),data'],level+2), sprintf('\n')); - elseif(size(item,2)==1) - % Column vector, store only row indices. - txt=sprintf(dataformat,txt,padding0,'"_ArrayData_": ',... - matdata2json([ix,data],level+2), sprintf('\n')); - else - % General case, store row and column indices. - txt=sprintf(dataformat,txt,padding0,'"_ArrayData_": ',... - matdata2json([ix,iy,data],level+2), sprintf('\n')); - end - else - if(isreal(item)) - txt=sprintf(dataformat,txt,padding0,'"_ArrayData_": ',... - matdata2json(item(:)',level+2), sprintf('\n')); - else - txt=sprintf(dataformat,txt,padding0,'"_ArrayIsComplex_": ','1', sprintf(',\n')); - txt=sprintf(dataformat,txt,padding0,'"_ArrayData_": ',... - matdata2json([real(item(:)) imag(item(:))],level+2), sprintf('\n')); - end - end - txt=sprintf('%s%s%s',txt,padding1,'}'); - end - - %%------------------------------------------------------------------------- - function txt=matdata2json(mat,level) - % handle each case individually for maximum speed - if numel(mat) == 1 - txt = sprintf(floatformat, mat); - if ~noRowBracket - txt = ['[' txt ']']; - end - elseif isempty(mat) - txt = 'null'; - elseif isrow(mat) - txt = sprintf([floatformat ','], mat); - txt = ['[' txt(1:end-1) ']']; - elseif iscolumn(mat) - txt = sprintf([newline pad{level+1} '[' floatformat '],'], mat); - txt = ['[' txt(1:end-1) newline pad{level} ']']; - else % matrix with rows>1 & cols>1 - formatstr=['[' repmat([floatformat ','],1,size(mat,2)-1) floatformat '],' newline]; - if arrayIndent - formatstr = [pad{level+1} formatstr]; - end - txt = sprintf(formatstr,mat'); - txt = ['[' newline txt(1:end-2) newline pad{level} ']']; - end -% if(size(mat,1)==1) -% pre=''; -% post=''; -% level=level-1; -% else -% pre=sprintf('[\n'); -% post=sprintf('\n%s]',pad{level}); -% end -% if(isempty(mat)) -% txt='null'; -% return; -% end -% -% formatstr=['[' repmat([floatformat ','],1,size(mat,2)-1) floatformat '],' newline]; -% -% if(nargin>=2 && size(mat,1)>1 && arrayIndent) -% formatstr=[pad{level+1} formatstr]; -% end -% % intxt=sprintf(formatstr,mat'); -% txt=sprintf(formatstr,mat'); - if(parseLogical && islogical(mat)) - txt=regexprep(txt,'1','true'); - txt=regexprep(txt,'0','false'); - end - if(any(isinf(mat(:)))) - txt=regexprep(txt,'([-+]*)Inf',jsonopt('Inf','"$1_Inf_"',opt)); - end - if(any(isnan(mat(:)))) - txt=regexprep(txt,'NaN',jsonopt('NaN','"_NaN_"',opt)); - end - end - - %%------------------------------------------------------------------------- - function name=checkname(name) - if(isempty(regexp(name,'0x([0-9a-fA-F]+)_','once'))) - return - elseif unpackHex - isoct=jsonopt('IsOctave',0,opt); - if(~isoct) - name=regexprep(name,'(^x|_){1}0x([0-9a-fA-F]+)_','${native2unicode(hex2dec($2))}'); - else - pos=regexp(name,'(^x|_){1}0x([0-9a-fA-F]+)_','start'); - pend=regexp(name,'(^x|_){1}0x([0-9a-fA-F]+)_','end'); - if(isempty(pos)) return; end - str0=name; - pos0=[0 pend(:)' length(name)]; - name=''; - for i=1:length(pos) - name=[name str0(pos0(i)+1:pos(i)-1) char(hex2dec(str0(pos(i)+3:pend(i)-1)))]; - end - if(pos(end)~=length(name)) - name=[name str0(pos0(end-1)+1:pos0(end))]; - end - end - end - end - -end - diff --git a/cb-tools/jsonlab/saveubjson.m b/cb-tools/jsonlab/saveubjson.m deleted file mode 100644 index eb975de0..00000000 --- a/cb-tools/jsonlab/saveubjson.m +++ /dev/null @@ -1,490 +0,0 @@ -function json=saveubjson(rootname,obj,varargin) -% -% json=saveubjson(rootname,obj,filename) -% or -% json=saveubjson(rootname,obj,opt) -% json=saveubjson(rootname,obj,'param1',value1,'param2',value2,...) -% -% convert a MATLAB object (cell, struct or array) into a Universal -% Binary JSON (UBJSON) binary string -% -% author: Qianqian Fang (fangq<at> nmr.mgh.harvard.edu) -% created on 2013/08/17 -% -% $Id: saveubjson.m 410 2013-08-24 03:33:18Z fangq $ -% -% input: -% rootname: name of the root-object, if set to '', will use variable name -% obj: a MATLAB object (array, cell, cell array, struct, struct array) -% filename: a string for the file name to save the output JSON data -% opt: a struct for additional options, use [] if all use default -% opt can have the following fields (first in [.|.] is the default) -% -% opt.FileName [''|string]: a file name to save the output JSON data -% opt.ArrayToStruct[0|1]: when set to 0, saveubjson outputs 1D/2D -% array in JSON array format; if sets to 1, an -% array will be shown as a struct with fields -% "_ArrayType_", "_ArraySize_" and "_ArrayData_"; for -% sparse arrays, the non-zero elements will be -% saved to _ArrayData_ field in triplet-format i.e. -% (ix,iy,val) and "_ArrayIsSparse_" will be added -% with a value of 1; for a complex array, the -% _ArrayData_ array will include two columns -% (4 for sparse) to record the real and imaginary -% parts, and also "_ArrayIsComplex_":1 is added. -% opt.ParseLogical [1|0]: if this is set to 1, logical array elem -% will use true/false rather than 1/0. -% opt.NoRowBracket [1|0]: if this is set to 1, arrays with a single -% numerical element will be shown without a square -% bracket, unless it is the root object; if 0, square -% brackets are forced for any numerical arrays. -% opt.ForceRootName [0|1]: when set to 1 and rootname is empty, saveubjson -% will use the name of the passed obj variable as the -% root object name; if obj is an expression and -% does not have a name, 'root' will be used; if this -% is set to 0 and rootname is empty, the root level -% will be merged down to the lower level. -% opt.JSONP [''|string]: to generate a JSONP output (JSON with padding), -% for example, if opt.JSON='foo', the JSON data is -% wrapped inside a function call as 'foo(...);' -% opt.UnpackHex [1|0]: conver the 0x[hex code] output by loadjson -% back to the string form -% opt can be replaced by a list of ('param',value) pairs. The param -% string is equivallent to a field in opt. -% output: -% json: a string in the JSON format (see http://json.org) -% -% examples: -% a=struct('node',[1 9 10; 2 1 1.2], 'elem',[9 1;1 2;2 3],... -% 'face',[9 01 2; 1 2 3; NaN,Inf,-Inf], 'author','FangQ'); -% saveubjson('mesh',a) -% saveubjson('',a,'ArrayIndent',0,'FloatFormat','\t%.5g') -% -% license: -% BSD or GPL version 3, see LICENSE_{BSD,GPLv3}.txt files for details -% -% -- this function is part of jsonlab toolbox (http://iso2mesh.sf.net/cgi-bin/index.cgi?jsonlab) -% - -if(nargin==1) - varname=inputname(1); - obj=rootname; - if(isempty(varname)) - varname='root'; - end - rootname=varname; -else - varname=inputname(2); -end -if(length(varargin)==1 && ischar(varargin{1})) - opt=struct('FileName',varargin{1}); -else - opt=varargin2struct(varargin{:}); -end -opt.IsOctave=exist('OCTAVE_VERSION'); -rootisarray=0; -rootlevel=1; -forceroot=jsonopt('ForceRootName',0,opt); -if((isnumeric(obj) || islogical(obj) || ischar(obj) || isstruct(obj) || iscell(obj)) && isempty(rootname) && forceroot==0) - rootisarray=1; - rootlevel=0; -else - if(isempty(rootname)) - rootname=varname; - end -end -if((isstruct(obj) || iscell(obj))&& isempty(rootname) && forceroot) - rootname='root'; -end -json=obj2ubjson(rootname,obj,rootlevel,opt); -if(~rootisarray) - json=['{' json '}']; -end - -jsonp=jsonopt('JSONP','',opt); -if(~isempty(jsonp)) - json=[jsonp '(' json ')']; -end - -% save to a file if FileName is set, suggested by Patrick Rapin -if(~isempty(jsonopt('FileName','',opt))) - fid = fopen(opt.FileName, 'wt'); - fwrite(fid,json,'char'); - fclose(fid); -end - -%%------------------------------------------------------------------------- -function txt=obj2ubjson(name,item,level,varargin) - -if(iscell(item)) - txt=cell2ubjson(name,item,level,varargin{:}); -elseif(isstruct(item)) - txt=struct2ubjson(name,item,level,varargin{:}); -elseif(ischar(item)) - txt=str2ubjson(name,item,level,varargin{:}); -else - txt=mat2ubjson(name,item,level,varargin{:}); -end - -%%------------------------------------------------------------------------- -function txt=cell2ubjson(name,item,level,varargin) -txt=''; -if(~iscell(item)) - error('input is not a cell'); -end - -dim=size(item); -len=numel(item); % let's handle 1D cell first -padding1=''; -padding0=''; -if(len>1) - if(~isempty(name)) - txt=[S_(checkname(name,varargin{:})) '[']; name=''; - else - txt='['; - end -elseif(len==0) - if(~isempty(name)) - txt=[S_(checkname(name,varargin{:})) 'Z']; name=''; - else - txt='Z'; - end -end -for i=1:len - txt=[txt obj2ubjson(name,item{i},level+(len>1),varargin{:})]; -end -if(len>1) txt=[txt ']']; end - -%%------------------------------------------------------------------------- -function txt=struct2ubjson(name,item,level,varargin) -txt=''; -if(~isstruct(item)) - error('input is not a struct'); -end -len=numel(item); -padding1=''; -padding0=''; -sep=','; - -if(~isempty(name)) - if(len>1) txt=[S_(checkname(name,varargin{:})) '[']; end -else - if(len>1) txt='['; end -end -for e=1:len - names = fieldnames(item(e)); - if(~isempty(name) && len==1) - txt=[txt S_(checkname(name,varargin{:})) '{']; - else - txt=[txt '{']; - end - if(~isempty(names)) - for i=1:length(names) - txt=[txt obj2ubjson(names{i},getfield(item(e),... - names{i}),level+1+(len>1),varargin{:})]; - end - end - txt=[txt '}']; - if(e==len) sep=''; end -end -if(len>1) txt=[txt ']']; end - -%%------------------------------------------------------------------------- -function txt=str2ubjson(name,item,level,varargin) -txt=''; -if(~ischar(item)) - error('input is not a string'); -end -item=reshape(item, max(size(item),[1 0])); -len=size(item,1); -sep=''; - -padding1=''; -padding0=''; - -if(~isempty(name)) - if(len>1) txt=[S_(checkname(name,varargin{:})) '[']; end -else - if(len>1) txt='['; end -end -isoct=jsonopt('IsOctave',0,varargin{:}); -for e=1:len - val=item(e,:); - if(len==1) - obj=['' S_(checkname(name,varargin{:})) '' '',S_(val),'']; - if(isempty(name)) obj=['',S_(val),'']; end - txt=[txt,'',obj]; - else - txt=[txt,'',['',S_(val),'']]; - end - if(e==len) sep=''; end - txt=[txt sep]; -end -if(len>1) txt=[txt ']']; end - -%%------------------------------------------------------------------------- -function txt=mat2ubjson(name,item,level,varargin) -if(~isnumeric(item) && ~islogical(item)) - error('input is not an array'); -end - -padding1=''; -padding0=''; - -if(length(size(item))>2 || issparse(item) || ~isreal(item) || ... - isempty(item) || jsonopt('ArrayToStruct',0,varargin{:})) - cid=I_(uint32(max(size(item)))); - if(isempty(name)) - txt=['{' S_('_ArrayType_'),S_(class(item)),padding0,S_('_ArraySize_'),I_a(size(item),cid(1)) ]; - else - txt=[S_(checkname(name,varargin{:})),'{',S_('_ArrayType_'),S_(class(item)),padding0,S_('_ArraySize_'),I_a(size(item),cid(1))]; - end -else - if(isempty(name)) - txt=matdata2ubjson(item,level+1,varargin{:}); - else - if(numel(item)==1 && jsonopt('NoRowBracket',1,varargin{:})==1) - numtxt=regexprep(regexprep(matdata2ubjson(item,level+1,varargin{:}),'^\[',''),']',''); - txt=[S_(checkname(name,varargin{:})) numtxt]; - else - txt=[S_(checkname(name,varargin{:})),matdata2ubjson(item,level+1,varargin{:})]; - end - end - return; -end -dataformat='%s%s%s%s%s'; - -if(issparse(item)) - [ix,iy]=find(item); - data=full(item(find(item))); - if(~isreal(item)) - data=[real(data(:)),imag(data(:))]; - if(size(item,1)==1) - % Kludge to have data's 'transposedness' match item's. - % (Necessary for complex row vector handling below.) - data=data'; - end - txt=[txt,S_('_ArrayIsComplex_'),'T']; - end - txt=[txt,S_('_ArrayIsSparse_'),'T']; - if(size(item,1)==1) - % Row vector, store only column indices. - txt=[txt,S_('_ArrayData_'),... - matdata2ubjson([iy(:),data'],level+2,varargin{:})]; - elseif(size(item,2)==1) - % Column vector, store only row indices. - txt=[txt,S_('_ArrayData_'),... - matdata2ubjson([ix,data],level+2,varargin{:})]; - else - % General case, store row and column indices. - txt=[txt,S_('_ArrayData_'),... - matdata2ubjson([ix,iy,data],level+2,varargin{:})]; - end -else - if(isreal(item)) - txt=[txt,S_('_ArrayData_'),... - matdata2ubjson(item(:)',level+2,varargin{:})]; - else - txt=[txt,S_('_ArrayIsComplex_'),'T']; - txt=[txt,S_('_ArrayData_'),... - matdata2ubjson([real(item(:)) imag(item(:))],level+2,varargin{:})]; - end -end -txt=[txt,'}']; - -%%------------------------------------------------------------------------- -function txt=matdata2ubjson(mat,level,varargin) -if(isempty(mat)) - txt='Z'; - return; -end -if(size(mat,1)==1) - level=level-1; -end -type=''; -hasnegtive=find(mat<0); -if(isa(mat,'integer') || (isfloat(mat) && all(mod(mat(:),1) == 0))) - if(isempty(hasnegtive)) - if(max(mat(:))<=2^8) - type='U'; - end - end - if(isempty(type)) - % todo - need to consider negative ones separately - id= histc(abs(max(mat(:))),[0 2^7 2^15 2^31 2^63]); - if(isempty(find(id))) - error('high-precision data is not yet supported'); - end - key='iIlL'; - type=key(find(id)); - end - txt=[I_a(mat(:),type,size(mat))]; -elseif(islogical(mat)) - logicalval='FT'; - if(numel(mat)==1) - txt=logicalval(mat+1); - else - txt=['[$U#' I_a(size(mat),'l') typecast(uint8(mat(:)'),'uint8')]; - end -else - if(numel(mat)==1) - txt=['[' D_(mat) ']']; - else - txt=D_a(mat(:),'D',size(mat)); - end -end - -%txt=regexprep(mat2str(mat),'\s+',','); -%txt=regexprep(txt,';',sprintf('],[')); -% if(nargin>=2 && size(mat,1)>1) -% txt=regexprep(txt,'\[',[repmat(sprintf('\t'),1,level) '[']); -% end -if(any(isinf(mat(:)))) - txt=regexprep(txt,'([-+]*)Inf',jsonopt('Inf','"$1_Inf_"',varargin{:})); -end -if(any(isnan(mat(:)))) - txt=regexprep(txt,'NaN',jsonopt('NaN','"_NaN_"',varargin{:})); -end - -%%------------------------------------------------------------------------- -function newname=checkname(name,varargin) -isunpack=jsonopt('UnpackHex',1,varargin{:}); -newname=name; -if(isempty(regexp(name,'0x([0-9a-fA-F]+)_','once'))) - return -end -if(isunpack) - isoct=jsonopt('IsOctave',0,varargin{:}); - if(~isoct) - newname=regexprep(name,'(^x|_){1}0x([0-9a-fA-F]+)_','${native2unicode(hex2dec($2))}'); - else - pos=regexp(name,'(^x|_){1}0x([0-9a-fA-F]+)_','start'); - pend=regexp(name,'(^x|_){1}0x([0-9a-fA-F]+)_','end'); - if(isempty(pos)) return; end - str0=name; - pos0=[0 pend(:)' length(name)]; - newname=''; - for i=1:length(pos) - newname=[newname str0(pos0(i)+1:pos(i)-1) char(hex2dec(str0(pos(i)+3:pend(i)-1)))]; - end - if(pos(end)~=length(name)) - newname=[newname str0(pos0(end-1)+1:pos0(end))]; - end - end -end -%%------------------------------------------------------------------------- -function val=S_(str) -if(length(str)==1) - val=['C' str]; -else - val=['S' I_(int32(length(str))) str]; -end -%%------------------------------------------------------------------------- -function val=I_(num) -if(~isinteger(num)) - error('input is not an integer'); -end -if(num>=0 && num<255) - val=['U' data2byte(cast(num,'uint8'),'uint8')]; - return; -end -key='iIlL'; -cid={'int8','int16','int32','int64'}; -for i=1:4 - if((num>0 && num<2^(i*8-1)) || (num<0 && num>=-2^(i*8-1))) - val=[key(i) data2byte(cast(num,cid{i}),'uint8')]; - return; - end -end -error('unsupported integer'); - -%%------------------------------------------------------------------------- -function val=D_(num) -if(~isfloat(num)) - error('input is not a float'); -end - -if(isa(num,'single')) - val=['d' data2byte(num,'uint8')]; -else - val=['D' data2byte(num,'uint8')]; -end -%%------------------------------------------------------------------------- -function data=I_a(num,type,dim,format) -id=find(ismember('iUIlL',type)); - -if(id==0) - error('unsupported integer array'); -end - -if(id==1) - data=data2byte(int8(num),'uint8'); - blen=1; -elseif(id==2) - data=data2byte(uint8(num),'uint8'); - blen=1; -elseif(id==3) - data=data2byte(int16(num),'uint8'); - blen=2; -elseif(id==4) - data=data2byte(int32(num),'uint8'); - blen=4; -elseif(id==5) - data=data2byte(int64(num),'uint8'); - blen=8; -end - -if(nargin>=3 && length(dim)>=2 && prod(dim)~=dim(2)) - format='opt'; -end -if((nargin<4 || strcmp(format,'opt')) && numel(num)>1) - if(nargin>=3 && (length(dim)==1 || (length(dim)>=2 && prod(dim)~=dim(2)))) - cid=I_(uint32(max(dim))); - data=['$' type '#' I_a(dim,cid(1)) data(:)']; - else - data=['$' type '#' I_(int32(numel(data)/blen)) data(:)']; - end - data=['[' data(:)']; -else - data=reshape(data,blen,numel(data)/blen); - data(2:blen+1,:)=data; - data(1,:)=type; - data=data(:)'; - data=['[' data(:)' ']']; -end -%%------------------------------------------------------------------------- -function data=D_a(num,type,dim,format) -id=find(ismember('dD',type)); - -if(id==0) - error('unsupported float array'); -end - -if(id==1) - data=data2byte(single(num),'uint8'); -elseif(id==2) - data=data2byte(double(num),'uint8'); -end - -if(nargin>=3 && length(dim)>=2 && prod(dim)~=dim(2)) - format='opt'; -end -if((nargin<4 || strcmp(format,'opt')) && numel(num)>1) - if(nargin>=3 && (length(dim)==1 || (length(dim)>=2 && prod(dim)~=dim(2)))) - cid=I_(uint32(max(dim))); - data=['$' type '#' I_a(dim,cid(1)) data(:)']; - else - data=['$' type '#' I_(int32(numel(data)/(id*4))) data(:)']; - end - data=['[' data]; -else - data=reshape(data,(id*4),length(data)/(id*4)); - data(2:(id*4+1),:)=data; - data(1,:)=type; - data=data(:)'; - data=['[' data(:)' ']']; -end -%%------------------------------------------------------------------------- -function bytes=data2byte(varargin) -bytes=typecast(varargin{:}); -bytes=bytes(:)'; diff --git a/cb-tools/jsonlab/varargin2struct.m b/cb-tools/jsonlab/varargin2struct.m deleted file mode 100644 index 43c27af4..00000000 --- a/cb-tools/jsonlab/varargin2struct.m +++ /dev/null @@ -1,40 +0,0 @@ -function opt=varargin2struct(varargin) -% -% opt=varargin2struct('param1',value1,'param2',value2,...) -% or -% opt=varargin2struct(...,optstruct,...) -% -% convert a series of input parameters into a structure -% -% authors:Qianqian Fang (fangq<at> nmr.mgh.harvard.edu) -% date: 2012/12/22 -% -% input: -% 'param', value: the input parameters should be pairs of a string and a value -% optstruct: if a parameter is a struct, the fields will be merged to the output struct -% -% output: -% opt: a struct where opt.param1=value1, opt.param2=value2 ... -% -% license: -% BSD or GPL version 3, see LICENSE_{BSD,GPLv3}.txt files for details -% -% -- this function is part of jsonlab toolbox (http://iso2mesh.sf.net/cgi-bin/index.cgi?jsonlab) -% - -len=length(varargin); -opt=struct; -if(len==0) return; end -i=1; -while(i<=len) - if(isstruct(varargin{i})) - opt=mergestruct(opt,varargin{i}); - elseif(ischar(varargin{i}) && i<len) - opt=setfield(opt,varargin{i},varargin{i+1}); - i=i+1; - else - error('input must be in the form of ...,''name'',value,... pairs or structs'); - end - i=i+1; -end - diff --git a/cb-tools/urlread2/http_createHeader.m b/cb-tools/urlread2/http_createHeader.m deleted file mode 100644 index 0e80241f..00000000 --- a/cb-tools/urlread2/http_createHeader.m +++ /dev/null @@ -1,11 +0,0 @@ -function header = http_createHeader(name,value) -%http_createHeader Simple function for creating input header to urlread2 -% -% header = http_createHeader(name,value) -% -% CODE: header = struct('name',name,'value',value); -% -% See Also: -% urlread2 - -header = struct('name',name,'value',value); \ No newline at end of file diff --git a/cb-tools/urlread2/http_paramsToString.m b/cb-tools/urlread2/http_paramsToString.m deleted file mode 100644 index 376a0fa1..00000000 --- a/cb-tools/urlread2/http_paramsToString.m +++ /dev/null @@ -1,62 +0,0 @@ -function [str,header] = http_paramsToString(params,encodeOption) -%http_paramsToString Creates string for a POST or GET requests -% -% [queryString,header] = http_paramsToString(params, *encodeOption) -% -% INPUTS -% ======================================================================= -% params: cell array of property/value pairs -% NOTE: If the input is in a 2 column matrix, then first column -% entries are properties and the second column entries are -% values, however this is NOT necessary (generally linear) -% encodeOption: (default 1) -% 1 - the typical URL encoding scheme (Java call) -% -% OUTPUTS -% ======================================================================= -% queryString: querystring to add onto URL (LACKS "?", see example) -% header : the header that should be attached for post requests when -% using urlread2 -% -% EXAMPLE: -% ============================================================== -% params = {'cmd' 'search' 'db' 'pubmed' 'term' 'wtf batman'}; -% queryString = http_paramsToString(params); -% queryString => cmd=search&db=pubmed&term=wtf+batman -% -% IMPORTANT: This function does not filter parameters, sort them, -% or remove empty inputs (if necessary), this must be done before hand - -if ~exist('encodeOption','var') - encodeOption = 1; -end - -if size(params,2) == 2 && size(params,1) > 1 - params = params'; - params = params(:); -end - -str = ''; -for i=1:2:length(params) - if (i == 1), separator = ''; else separator = '&'; end - switch encodeOption - case 1 - param = urlencode(params{i}); - value = urlencode(params{i+1}); -% case 2 -% param = oauth.percentEncodeString(params{i}); -% value = oauth.percentEncodeString(params{i+1}); -% header = http_getContentTypeHeader(1); - otherwise - error('Case not used') - end - str = [str separator param '=' value]; %#ok<AGROW> -end - -switch encodeOption - case 1 - header = http_createHeader('Content-Type','application/x-www-form-urlencoded'); -end - - -end \ No newline at end of file diff --git a/cb-tools/urlread2/license.txt b/cb-tools/urlread2/license.txt deleted file mode 100644 index 1d783b74..00000000 --- a/cb-tools/urlread2/license.txt +++ /dev/null @@ -1,24 +0,0 @@ -Copyright (c) 2012, Jim Hokanson -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the distribution - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. diff --git a/cb-tools/urlread2/urlread2.m b/cb-tools/urlread2/urlread2.m deleted file mode 100644 index b552861c..00000000 --- a/cb-tools/urlread2/urlread2.m +++ /dev/null @@ -1,371 +0,0 @@ -function [output,extras] = urlread2(urlChar,method,body,headersIn,varargin) -%urlread2 Makes HTTP requests and processes response -% -% [output,extras] = urlread2(urlChar, *method, *body, *headersIn, varargin) -% -% * indicates optional inputs that must be entered in place -% -% UNDOCUMENTED MATLAB VERSION -% -% EXAMPLE CALLING FORMS -% ... = urlread2(urlChar) -% ... = urlread2(urlChar,'GET','',[],prop1,value1,prop2,value2,etc) -% ... = urlread2(urlChar,'POST',body,headers) -% -% FEATURES -% ======================================================================= -% 1) Allows specification of any HTTP method -% 2) Allows specification of any header. Very little is hard-coded -% in for header handling. -% 3) Returns response status and headers -% 4) Should handle unicode properly ... -% -% OUTPUTS -% ======================================================================= -% output : body of the response, either text or binary depending upon -% CAST_OUTPUT property -% extras : (structure) -% .allHeaders - stucture, fields have cellstr values, HTTP headers may -% may be repeated but will have a single field entry, with each -% repeat's value another being another entry in the cellstr, for -% example: -% .Set_Cookie = {'first_value' 'second_value'} -% .firstHeaders - (structure), variable fields, contains the first -% string entry for each field in allHeaders, this -% structure can be used to avoid dereferencing a cell -% for fields you expect not to be repeated ... -% EXAMPLE: -% .Response : 'HTTP/1.1 200 OK'} -% .Server : 'nginx' -% .Date : 'Tue, 29 Nov 2011 02:23:16 GMT' -% .Content_Type : 'text/html; charset=UTF-8' -% .Content_Length : '109155' -% .Connection : 'keep-alive' -% .Vary : 'Accept-Encoding, User-Agent' -% .Cache_Control : 'max-age=60, private' -% .Set_Cookie : 'first_value' -% .status - (structure) -% .value : numeric value of status, ex. 200 -% .msg : message that goes along with status, ex. 'OK' -% .url - eventual url that led to output, this can change from -% the input with redirects, see FOLLOW_REDIRECTS -% .isGood - (logical) I believe this is an indicator of the presence of 400 -% or 500 status codes (see status.value) but more -% testing is needed. In other words, true if status.value < 400. -% In code, set true if the response was obtainable without -% resorting to checking the error stream. -% -% INPUTS -% ======================================================================= -% urlChar : The full url, must include scheme (http, https) -% method : examples: 'GET' 'POST' etc -% body : (vector)(char, uint8 or int8) body to write, generally used -% with POST or PUT, use of uint8 or int8 ensures that the -% body input is not manipulated before sending, char is sent -% via unicode2native function with ENCODING input (see below) -% headersIn : (structure array), use empty [] or '' if no headers are needed -% but varargin property/value pairs are, multiple headers -% may be passed in as a structure array -% .name - (string), name of the header, a name property is used -% instead of a field because the name must match a valid -% header -% .value - (string), value to use -% -% OPTIONAL INPUTS (varargin, property/value pairs) -% ======================================================================= -% CAST_OUTPUT : (default true) output is uint8, useful if the body -% of the response is not text -% ENCODING : (default ''), ENCODING input to function unicode2native -% FOLLOW_REDIRECTS : (default true), if false 3xx status codes will -% be returned and need to be handled by the user, -% note this does not handle javascript or meta tag -% redirects, just server based ones -% READ_TIMEOUT : (default 0), 0 means no timeout, value is in -% milliseconds -% -% EXAMPLES -% ======================================================================= -% GET: -% -------------------------------------------- -% url = 'http://www.mathworks.com/matlabcentral/fileexchange/'; -% query = 'urlread2'; -% params = {'term' query}; -% queryString = http_paramsToString(params,1); -% url = [url '?' queryString]; -% [output,extras] = urlread2(url); -% -% POST: -% -------------------------------------------- -% url = 'http://posttestserver.com/post.php'; -% params = {'testChars' char([2500 30000]) 'new code' '?'}; -% [paramString,header] = http_paramsToString(params,1); -% [output,extras] = urlread2(url,'POST',paramString,header); -% -% From behind a firewall, use the Preferences to set your proxy server. -% -% See Also: -% http_paramsToString -% unicode2native -% native2unicode -% -% Subfunctions: -% fixHeaderCasing - small subfunction to fix case errors encountered in real -% world, requires updating when casing doesn't match expected form, like -% if someone sent the header content-Encoding instead of -% Content-Encoding -% -% Based on original urlread code by Matthew J. Simoneau -% -% VERSION = 1.1 - -in.CAST_OUTPUT = true; -in.FOLLOW_REDIRECTS = true; -in.READ_TIMEOUT = 0; -in.ENCODING = ''; - -%Input handling -%--------------------------------------- -if ~isempty(varargin) - for i = 1:2:numel(varargin) - prop = upper(varargin{i}); - value = varargin{i+1}; - if isfield(in,prop) - in.(prop) = value; - else - error('Unrecognized input to function: %s',prop) - end - end -end - -if ~exist('method','var') || isempty(method), method = 'GET'; end -if ~exist('body','var'), body = ''; end -if ~exist('headersIn','var'), headersIn = []; end - -assert(usejava('jvm'),'Function requires Java') - -import com.mathworks.mlwidgets.io.InterruptibleStreamCopier; -com.mathworks.mlwidgets.html.HTMLPrefs.setProxySettings %Proxy settings need to be set - -%Create a urlConnection. -%----------------------------------- -urlConnection = getURLConnection(urlChar); -%For HTTP uses sun.net.www.protocol.http.HttpURLConnection -%Might use ice.net.HttpURLConnection but this has more overhead - -%SETTING PROPERTIES -%------------------------------------------------------- -urlConnection.setRequestMethod(upper(method)); -urlConnection.setFollowRedirects(in.FOLLOW_REDIRECTS); -urlConnection.setReadTimeout(in.READ_TIMEOUT); - -for iHeader = 1:length(headersIn) - curHeader = headersIn(iHeader); - urlConnection.setRequestProperty(curHeader.name,curHeader.value); -end - -if ~isempty(body) - %Ensure vector? - if size(body,1) > 1 - if size(body,2) > 1 - error('Input parameter to function: body, must be a vector') - else - body = body'; - end - end - - if ischar(body) - %NOTE: '' defaults to Matlab's default encoding scheme - body = unicode2native(body,in.ENCODING); - elseif ~(strcmp(class(body),'uint8') || strcmp(class(body),'int8')) - error('Function input: body, should be of class char, uint8, or int8, detected: %s',class(body)) - end - - urlConnection.setRequestProperty('Content-Length',int2str(length(body))); - urlConnection.setDoOutput(true); - outputStream = urlConnection.getOutputStream; - outputStream.write(body); - outputStream.close; -else - urlConnection.setRequestProperty('Content-Length','0'); -end - -%========================================================================== -% Read the data from the connection. -%========================================================================== -%This should be done first because it tells us if things are ok or not -%NOTE: If there is an error, functions below using urlConnection, notably -%getResponseCode, will fail as well -try - inputStream = urlConnection.getInputStream; - isGood = true; -catch ME - isGood = false; -%NOTE: HTTP error codes will throw an error here, we'll allow those for now -%We might also get another error in which case the inputStream will be -%undefined, those we will throw here - inputStream = urlConnection.getErrorStream; - - if isempty(inputStream) - msg = ME.message; - I = strfind(msg,char([13 10 9])); %see example by setting timeout to 1 - %Should remove the barf of the stack, at ... at ... at ... etc - %Likely that this could be improved ... (generate link with full msg) - if ~isempty(I) - msg = msg(1:I(1)-1); - end - fprintf(2,'Response stream is undefined\n below is a Java Error dump (truncated):\n'); - error(msg) - end -end - -%POPULATING HEADERS -%-------------------------------------------------------------------------- -allHeaders = struct; -allHeaders.Response = {char(urlConnection.getHeaderField(0))}; -done = false; -headerIndex = 0; - -while ~done - headerIndex = headerIndex + 1; - headerValue = char(urlConnection.getHeaderField(headerIndex)); - if ~isempty(headerValue) - headerName = char(urlConnection.getHeaderFieldKey(headerIndex)); - headerName = fixHeaderCasing(headerName); %NOT YET FINISHED - - %Important, for name safety all hyphens are replace with underscores - headerName(headerName == '-') = '_'; - if isfield(allHeaders,headerName) - allHeaders.(headerName) = [allHeaders.(headerName) headerValue]; - else - allHeaders.(headerName) = {headerValue}; - end - else - done = true; - end -end - -firstHeaders = struct; -fn = fieldnames(allHeaders); -for iHeader = 1:length(fn) - curField = fn{iHeader}; - firstHeaders.(curField) = allHeaders.(curField){1}; -end - -status = struct(... - 'value', urlConnection.getResponseCode(),... - 'msg', char(urlConnection.getResponseMessage)); - -%PROCESSING OF OUTPUT -%---------------------------------------------------------- -byteArrayOutputStream = java.io.ByteArrayOutputStream; -% This StreamCopier is unsupported and may change at any time. OH GREAT :/ -isc = InterruptibleStreamCopier.getInterruptibleStreamCopier; -isc.copyStream(inputStream,byteArrayOutputStream); -inputStream.close; -byteArrayOutputStream.close; - -if in.CAST_OUTPUT - charset = ''; - - %Extraction of character set from Content-Type header if possible - if isfield(firstHeaders,'Content_Type') - text = firstHeaders.Content_Type; - %Always open to regexp improvements - charset = regexp(text,'(?<=charset=)[^\s]*','match','once'); - end - - if ~isempty(charset) - output = native2unicode(typecast(byteArrayOutputStream.toByteArray','uint8'),charset); - else - output = char(typecast(byteArrayOutputStream.toByteArray','uint8')); - end -else - %uint8 is more useful for later charecter conversions - %uint8 or int8 is somewhat arbitary at this point - output = typecast(byteArrayOutputStream.toByteArray','uint8'); -end - -extras = struct; -extras.allHeaders = allHeaders; -extras.firstHeaders = firstHeaders; -extras.status = status; -%Gets eventual url even with redirection -extras.url = char(urlConnection.getURL); -extras.isGood = isGood; - - - -end - -function headerNameOut = fixHeaderCasing(headerName) -%fixHeaderCasing Forces standard casing of headers -% -% headerNameOut = fixHeaderCasing(headerName) -% -% This is important for field access in a structure which -% is case sensitive -% -% Not yet finished. -% I've been adding to this function as problems come along - - switch lower(headerName) - case 'location' - headerNameOut = 'Location'; - case 'content_type' - headerNameOut = 'Content_Type'; - otherwise - headerNameOut = headerName; - end -end - -%========================================================================== -%========================================================================== -%========================================================================== - -function urlConnection = getURLConnection(urlChar) -%getURLConnection -% -% urlConnection = getURLConnection(urlChar) - -% Determine the protocol (before the ":"). -protocol = urlChar(1:find(urlChar==':',1)-1); - - -% Try to use the native handler, not the ice.* classes. -try - switch protocol - case 'http' - %http://www.docjar.com/docs/api/sun/net/www/protocol/http/HttpURLConnection.html - handler = sun.net.www.protocol.http.Handler; - case 'https' - handler = sun.net.www.protocol.https.Handler; - end -catch ME - handler = []; -end - -% Create the URL object. -try - if isempty(handler) - url = java.net.URL(urlChar); - else - url = java.net.URL([],urlChar,handler); - end -catch ME - error('Failure to parse URL or protocol not supported for:\nURL: %s',urlChar); -end - -% Get the proxy information using MathWorks facilities for unified proxy -% preference settings. -mwtcp = com.mathworks.net.transport.MWTransportClientPropertiesFactory.create(); -proxy = mwtcp.getProxy(); - -% Open a connection to the URL. -if isempty(proxy) - urlConnection = url.openConnection; -else - urlConnection = url.openConnection(proxy); -end - - -end diff --git a/cb-tools/urlread2/urlread_notes.txt b/cb-tools/urlread2/urlread_notes.txt deleted file mode 100644 index 8257f092..00000000 --- a/cb-tools/urlread2/urlread_notes.txt +++ /dev/null @@ -1,86 +0,0 @@ -========================================================================== - Unicode & Matlab -========================================================================== -native2unicode - works with uint8, fails with char - -Taking a unicode character and encoding as bytes: -unicode2native(char(1002),'UTF-8') -back to the character: -native2unicode(uint8([207 170]),'UTF-8') -this doesn't work: -native2unicode(char([207 170]),'UTF-8') -in documentation: If BYTES is a CHAR vector, it is returned unchanged. - -Java - only supports int8 -Matlab to Java -> uint8 or int8 to bytes -Java to Matlab -> bytes to int8 -char - 16 bit - -Maintenance of underlying bytes: -typecast(java.lang.String(uint8(250)).getBytes,'uint8') = 250 -see documentation: Handling Data Returned from a Java Method - -Command Window difficulty --------------------------------------------------------------------- -The typical font in the Matlab command window will often fail to render -unicode properly. I often can see unicode better in the variable editor -although this may be fixed if you change your font preferences ... -Copying unicode from the command window often results in the -generations of the value 26 (aka substitute) - -More documentation on input/output to urlread2 to follow eventually ... - -small notes to self: -for output -native2unicode(uint8(output),encoding) - -========================================================================== - HTTP Headers -========================================================================== -Handling of repeated http readers is a bit of a tricky situation. Most -headers are not repeated although sometimes http clients will assume this -for too many headers which can result in a problem if you want to see -duplicated headers. I've passed the problem onto the user who can decide -to handle it how they wish instead of providing the right solution, which -after some brief searching, I am not sure exists. - -========================================================================== - PROBLEMS -========================================================================== -1) Page requires following a redirect: -%------------------------------------------- -ref: http://www.mathworks.com/matlabcentral/newsreader/view_thread/302571 -fix: FOLLOW_REDIRECTS is enabled by default, you're fine. - -2) Basic authentication required: -%------------------------------------------ -Create and pass in the following header: -user = 'test'; -password = 'test'; -encoder = sun.misc.BASE64Encoder(); -str = java.lang.String([user ':' password]) %NOTE: format may be -%different for your server -header = http_createHeader('Authorization',char(encoder.encode(str.getBytes()))) -NOTE: Ideally you would make this a function - -3) The text returned doesn't make sense. -%----------------------------------------- -The text may not be encoded correctly. Requires native2unicode function. -See Unicode & Matlab section above. - -4) I get a different result in my web browser than I do in Matlab -%----------------------------------------- -This is generally seen for two reasons. -1 - The easiest and silly reason is user agent filtering. -When you make a request you identify yourself -as being a particular "broswer" or "user agent". Setting a header -with the user agent of the browser may fix the problem. -See: http://en.wikipedia.org/wiki/User_agent -See: http://whatsmyuseragent.com -value = '' -header = http_createHeader('User-Agent',value); -2 - You are not processing cookies and the server is not sending -you information because you haven't sent it cookies (everyone likes em!) -I've implemented cookie support but it requires some extra files that -I need to clean up. Feel free to email me if you'd really like to have them. - diff --git a/cb-tools/urlread2/urlread_todos.txt b/cb-tools/urlread2/urlread_todos.txt deleted file mode 100644 index 0434bcea..00000000 --- a/cb-tools/urlread2/urlread_todos.txt +++ /dev/null @@ -1,13 +0,0 @@ -========================================================================== - IMPROVEMENTS -========================================================================== -1) The function could be improved to support file streaming both in sending -and in receiving (saving) to reduce memory usage but this is very low priority - -2) Implement better casing handling - -3) Choose a better function name than urlread2 -> sorry Chris :) - -4) Support multipart/form-data, this ideally would be handled by a helper function - -5) Allow for throwing an error if the HTTP status code is an error (400) and 500 as well? \ No newline at end of file diff --git a/cb-tools/urlread2/urlread_versionInfo.txt b/cb-tools/urlread2/urlread_versionInfo.txt deleted file mode 100644 index a8448f49..00000000 --- a/cb-tools/urlread2/urlread_versionInfo.txt +++ /dev/null @@ -1,13 +0,0 @@ -===================== -Version 1.1 -3/25/2012 - -Summary: Bug fixes related to Matlab throwing errors when it shouldn't have been. Thanks to Shane Lin for pointing out the problems. - -Bug Fix: Modified code so that http status errors wouldn't throw errors in the code, but rather would be passed on to the user to process - -Bug Fix: Provided GET example code apparently doesn't work for all users, changed to a different example. - -===================== -Version 1 -3/17/2012 \ No newline at end of file diff --git a/+dat/findNextSeqNum.m b/cortexlab/+dat/findNextSeqNum.m similarity index 100% rename from +dat/findNextSeqNum.m rename to cortexlab/+dat/findNextSeqNum.m diff --git a/+dat/subjectSelector.m b/cortexlab/+dat/subjectSelector.m similarity index 100% rename from +dat/subjectSelector.m rename to cortexlab/+dat/subjectSelector.m diff --git a/+hw/DaqLever.m b/cortexlab/+hw/DaqLever.m similarity index 100% rename from +hw/DaqLever.m rename to cortexlab/+hw/DaqLever.m diff --git a/+hw/DaqLick.m b/cortexlab/+hw/DaqLick.m similarity index 100% rename from +hw/DaqLick.m rename to cortexlab/+hw/DaqLick.m diff --git a/+hw/DaqPiezo.m b/cortexlab/+hw/DaqPiezo.m similarity index 100% rename from +hw/DaqPiezo.m rename to cortexlab/+hw/DaqPiezo.m diff --git a/cortexlab/+hw/daqControllerForValve.m b/cortexlab/+hw/daqControllerForValve.m deleted file mode 100644 index 0418cc4c..00000000 --- a/cortexlab/+hw/daqControllerForValve.m +++ /dev/null @@ -1,28 +0,0 @@ -function daqController = daqControllerForValve(daqRewardValve, calibrations, addLaser) -%UNTITLED Summary of this function goes here -% Detailed explanation goes here - -daqController = hw.DaqController; -daqController.ChannelNames = {'rewardValve'}; -daqController.DaqIds = 'Dev1'; -daqController.DaqChannelIds = {daqRewardValve.DaqChannelId}; -daqController.SignalGenerators = hw.RewardValveControl; -daqController.SignalGenerators.ClosedValue = daqRewardValve.ClosedValue; -daqController.SignalGenerators.DefaultValue = daqRewardValve.ClosedValue; -daqController.SignalGenerators.OpenValue = daqRewardValve.OpenValue; -daqController.SignalGenerators.Calibrations = calibrations; -daqController.SignalGenerators.DefaultCommand = daqRewardValve.DefaultRewardSize; - -if nargin > 2 && addLaser - daqController.DaqChannelIds{2} = 'ao1'; - daqController.ChannelNames{2} = {'laserShutter'}; - daqController.SignalGenerators(2) = hw.PulseSwitcher; - daqController.SignalGenerators(2).ClosedValue = 0; - daqController.SignalGenerators(2).DefaultValue = 0; - daqController.SignalGenerators(2).OpenValue = 5; - daqController.SignalGenerators(2).DefaultCommand = 10; - daqController.SignalGenerators(2).ParamsFun = @(sz) deal(10/1000, sz, 25); -end - - -end \ No newline at end of file diff --git a/cortexlab/+srv/RemoteMPEPService.m b/cortexlab/+srv/RemoteMPEPService.m index e71acd0d..457428f4 100644 --- a/cortexlab/+srv/RemoteMPEPService.m +++ b/cortexlab/+srv/RemoteMPEPService.m @@ -114,8 +114,8 @@ function delete(obj) end function obj = addListener(obj, name, listenPort, callback) - if nargin<3; callback = @nop; end - if listenPort==obj.ListenPorts + if nargin<4; callback = @nop; end + if any(listenPort==obj.ListenPorts{:}) error('Listen port already added'); end idx = length(obj.Sockets)+1; @@ -172,18 +172,19 @@ function bind(obj, names) names = ensureCell(names); hosts = arrayfun(@(s)s.Tag, obj.Sockets); idx = cellfun(@(n)find(strcmp(n,hosts)), names); - arrayfun(@fopen, obj.Sockets(idx)) + cellfun(@fopen, obj.Sockets(idx)) end - log('Polling for UDP messages'); + obj.log('Polling for UDP messages'); end function start(obj, ref) % Send start message to remotehost and await confirmation - [expRef, AlyxInstance] = parseAlyxInstance(ref); +% [expRef, AlyxInstance] = parseAlyxInstance(ref); % Convert expRef to MPEP style - [subject, seriesNum, expNum] = dat.expRefToMpep(expRef); +% [subject, seriesNum, expNum] = dat.expRefToMpep(expRef); % Build start message - msg = sprintf('ExpStart %s %d %d', subject, seriesNum, expNum); +% msg = sprintf('ExpStart %s %d %d', subject, seriesNum, expNum); + msg = sprintf('GOGO%s*%s', ref, hostname); % Send the start message obj.confirmedSend(msg, obj.RemoteHost); % Wait for response @@ -248,7 +249,7 @@ function echo(obj, src, ~) fopen(src); fprintf(obj.Socket, obj.LastReceivedMessage); % Send message obj.LastSentMessage = obj.LastReceivedMessage; % Save a copy of the message - disp(['Echo''d message to ' src.Tag]) % Display success + log(obj,'Echo''d message to %s', src.Tag) % Display success end end @@ -273,7 +274,7 @@ function processMsg(obj, src, ~) case {'expstart', 'gogo'} try % Start Timeline - log('Received start request') + log(obj, 'Received start request') obj.LocalStatus = 'starting'; obj.Timeline.start(dat.parseAlyxInstance(msg.expRef)) obj.LocalStatus = 'running'; @@ -297,7 +298,7 @@ function processMsg(obj, src, ~) obj.sendUDP() otherwise % TODO RemoteHost - log(['Received ''' obj.LastReceivedMessage ''' from ' obj.RemoteHost]) + log(obj, ['Received ''' obj.LastReceivedMessage ''' from ' obj.RemoteHost]) end end From 87887288728b2a16dcc3b02e3139ca5939aa7171 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 25 Jan 2018 11:40:30 +0000 Subject: [PATCH 034/393] Update to mat2DStrTo1D This function was never used until now. A couple of the functions called within it have since been renamed. --- cb-tools/burgbox/mat2DStrTo1D.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cb-tools/burgbox/mat2DStrTo1D.m b/cb-tools/burgbox/mat2DStrTo1D.m index e8789e0b..ab79761a 100644 --- a/cb-tools/burgbox/mat2DStrTo1D.m +++ b/cb-tools/burgbox/mat2DStrTo1D.m @@ -9,7 +9,7 @@ % 2013-09 CB created if iscellstr(str) - str = mapToCellArray(@matStr2Lines, str); + str = mapToCell(@mat2DStrTo1D, str); else str = strJoin(deblank(num2cell(str, 2)), '\n'); end From 92cfabd976abee5ec0b5867d4eb9b332be2c1388 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 25 Jan 2018 15:28:52 +0000 Subject: [PATCH 035/393] Fixed clocking pulse bug in Timeline * Clocking pulse now functions correctly * rewardController (obsolete) now removed from code. * DaqSession release added as temp fix for DAQ samples bug --- +hw/DaqController.m | 2 ++ +hw/Timeline.m | 5 +++-- +srv/expServer.m | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/+hw/DaqController.m b/+hw/DaqController.m index cca9f947..4a480fcf 100644 --- a/+hw/DaqController.m +++ b/+hw/DaqController.m @@ -161,6 +161,8 @@ function command(obj, varargin) else startBackground(obj.DaqSession); end + readyWait(obj); + obj.DaqSession.release; elseif any(~analogueChannelsIdx) waveforms = waveforms(~analogueChannelsIdx); for n = 1:length(waveforms) diff --git a/+hw/Timeline.m b/+hw/Timeline.m index 49302b58..30169385 100644 --- a/+hw/Timeline.m +++ b/+hw/Timeline.m @@ -508,11 +508,12 @@ function init(obj) case 'clock' obj.Sessions('clock') = daq.createSession(obj.DaqVendor); - obj.Sessions('clock').IsContinuous = true; + clockSess = obj.Sessions('clock'); + clockSess.IsContinuous = true; clocked = obj.Sessions('clock').addCounterOutputChannel(obj.DaqIds, out.daqChannelID, out.type); clocked.Frequency = obj.ClockOutputFrequency; clocked.DutyCycle = obj.ClockOutputDutyCycle; - clocked.InitialDelay = out.delay; + clocked.InitialDelay = out.initialDelay; end end %%Create channels for each input diff --git a/+srv/expServer.m b/+srv/expServer.m index d954527c..4e4bec05 100644 --- a/+srv/expServer.m +++ b/+srv/expServer.m @@ -335,7 +335,7 @@ function setClock(user, clock) end rig.clock = clock; cellfun(@(user) setClock(user, clock),... - {'mouseInput', 'rewardController', 'lickDetector'}); + {'mouseInput', 'lickDetector'}); t = rig.timeline.UseTimeline; if enable From e56802fc2659661847649b8cafd5bd63e47ec74e Mon Sep 17 00:00:00 2001 From: nsteinme <nick.steinmetz@gmail.com> Date: Thu, 25 Jan 2018 17:21:10 +0000 Subject: [PATCH 036/393] mpepdatahosts bug fix --- cortexlab/+io/MpepUDPDataHosts.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cortexlab/+io/MpepUDPDataHosts.m b/cortexlab/+io/MpepUDPDataHosts.m index 821b215f..76fae0c7 100644 --- a/cortexlab/+io/MpepUDPDataHosts.m +++ b/cortexlab/+io/MpepUDPDataHosts.m @@ -201,10 +201,10 @@ function expEnded(obj) obj.ExpRef = []; end - function start(obj, ref) + function start(obj, ref) [expRef, ai] = dat.parseAlyxInstance(ref); obj.AlyxInstance = ai; - [subject, seriesNum, expNum] = dat.expRefToMpep(obj.ExpRef); + [subject, seriesNum, expNum] = dat.expRefToMpep(expRef); alyxmsg = sprintf('alyx %s %d %d %s', subject, seriesNum, expNum, ref); confirmedBroadcast(obj, alyxmsg); % equivalent to startExp(expRef) From c2819f0304ba5d84b94dbfba255ddac7fafcb4aa Mon Sep 17 00:00:00 2001 From: nsteinme <nick.steinmetz@gmail.com> Date: Thu, 25 Jan 2018 18:56:40 +0000 Subject: [PATCH 037/393] first commit with working chrono and acqLive classes --- +hw/Timeline.m | 268 ++++++++++++++++++++++++------------------ +hw/tlOutput.m | 36 ++++++ +hw/tlOutputAcqLive.m | 52 ++++++++ +hw/tlOutputChrono.m | 89 ++++++++++++++ 4 files changed, 332 insertions(+), 113 deletions(-) create mode 100644 +hw/tlOutput.m create mode 100644 +hw/tlOutputAcqLive.m create mode 100644 +hw/tlOutputChrono.m diff --git a/+hw/Timeline.m b/+hw/Timeline.m index 3739c1bf..0e69607a 100644 --- a/+hw/Timeline.m +++ b/+hw/Timeline.m @@ -13,12 +13,13 @@ % (see tl.time() and tl.ptbSecsToTimeline()). In is assumed that the % time between sending the chrono pulse and recieving it is negligible. % -% There are two other available clocking signals: 'acqLive' and 'clock'. +% There are other available clocking signals, for instance: 'acqLive' and 'clock'. % The former outputs a high (+5V) signal the entire time tl is aquiring % (0V otherwise), and can be used to trigger devices with a TTL input. % The 'clock' output is a regular pulse at a frequency of % ClockOutputFrequency and duty cycle of ClockOutputDutyCycle. This can -% be used to trigger a camera at a specific frame rate. +% be used to trigger a camera at a specific frame rate. See "properties" below for +% further details on output configurations. % % Besides the chrono signal, tl can aquire any number of inputs and % record their values on the same clock. For example a photodiode to @@ -68,23 +69,50 @@ DaqIds = 'Dev1' % Device ID can be found with daq.getDevices() DaqSampleRate = 1000 % rate at which daq aquires data in Hz, see Rate DaqSamplesPerNotify % determines the number of data samples to be processed each time, see Timeline.process(), constructor and NotifyWhenDataAvailableExceeds - Outputs % structure of outputs with their type, delays and ports, see constructor + + Outputs % structure of outputs with their names and configuration information + % All outputs require the following fields: + % - name: a string identifier + % - type: one of the supported modes that we know how to + % handle, see below + % - params, the required parameters for the selected type + % + % Available types: + % - chrono: a default type that Timeline uses to monitor that + % acquisition is proceeding normally during a recording. + % Requires: daqChannelID + % + % - acqLive: a digital output that is turned on at the start + % of the timeline recording, turned off at the end + % Requires: daqChannelID, initialDelay + % + % - clock: a regular pulse at a specified frequency and duty + % cycle. Can be used to trigger camera frames, e.g. + % Requires: daqChannelID, initialDelay, frequency, dutyCycle + % + % - startStopSync: a brief pulse at the beginning and at the + % end of a recording. Can be used for synchronization + % - Requires: daqChannelID, initialDelay, pulseDuration + Inputs = struct('name', 'chrono',... 'arrayColumn', -1,... % -1 is default indicating unused, this is update when the channels are added during tl.start() 'daqChannelID', 'ai0',... 'measurement', 'Voltage',... 'terminalConfig', 'SingleEnded') UseInputs = {'chrono'} % array of inputs to record while tl is running - UseOutputs = {'chrono'} % array of output pulses to use while tl is running + %UseOutputs = {'chrono'} % array of output pulses to use while tl is running StopDelay = 2 % currently pauses for at least 2 secs as 'hack' before stopping main DAQ session - MaxExpectedDuration = 2*60*60 % expected experiment time so data structure is initialised to sensible size (in secs) - ClockOutputFrequency = 60 % if using 'clock' output, this specifies the frequency of pulses (Hz) - ClockOutputDutyCycle = 0.2 % if using 'clock' output, this specifies the duty cycle (as a fraction) + MaxExpectedDuration = 2*60*60 % expected experiment time so data structure is initialised to sensible size (in secs) AquiredDataType = 'double' % default data type for the acquired data array (i.e. Data.rawDAQData) UseTimeline = false % used by expServer. If true, timeline is started by default (otherwise can be toggled with the t key) LivePlot = false % if true the data are plotted as the data are aquired LivePlotParams = []; WriteBufferToDisk = false % if true the data buffer is written to disk as they're aquired NB: in the future this will happen by default + + % moved these here so chrono class can access - NS + CurrSysTimeTimelineOffset % difference between the system time when the last chrono flip occured and the timestamp recorded by the DAQ, see tl.process() + LastTimestamp % the last timestamp returned from the daq during the DataAvailable event. Used to check sampling continuity, see tl.process() + LastClockSentSysTime % the mean of the system time before and after the last chrono flip. Used to calculate CurrSysTimeTimelineOffset, see tl.process() end properties (Dependent) @@ -94,11 +122,7 @@ properties (Transient, Access = protected) Listener % holds the listener for 'DataAvailable', see DataAvailable and Timeline.process() - Sessions = containers.Map % map of daq sessions and their channels, created at tl.start() - CurrSysTimeTimelineOffset % difference between the system time when the last chrono flip occured and the timestamp recorded by the DAQ, see tl.process() - LastTimestamp % the last timestamp returned from the daq during the DataAvailable event. Used to check sampling continuity, see tl.process() - LastClockSentSysTime % the mean of the system time before and after the last chrono flip. Used to calculate CurrSysTimeTimelineOffset, see tl.process() - NextChronoSign = 1 % the value to output on the chrono channel, the sign is changed each 'DataAvailable' event (DaqSamplesPerNotify) + Sessions = containers.Map % map of daq sessions and their channels, created at tl.start() Ref % the expRef string. See tl.start() AlyxInstance % a struct contraining the Alyx token, user and url for ile registration. See tl.start() Data % A structure containing timeline data @@ -112,22 +136,43 @@ % Adds chrono, aquireLive and clock to the outputs list, % along with default ports and delays obj.DaqSamplesPerNotify = 1/obj.SamplingInterval; % calculate DaqSamplesPerNotify - defaultOutputs = {'chrono', 'acqLive', 'clock';... % names of each output - 'port1/line0', 'port0/line1', 'ctr3';... % their default ports - 'OutputOnly', 'OutputOnly', 'PulseGeneration'; % default output type - 0, 0, 0}; % the initial delay (useful for ensure all systems are ready) - obj.Outputs = cell2struct(defaultOutputs, {'name', 'daqChannelID', 'type', 'initialDelay'}); + +% defaultOutputs = struct('name', 'type', 'params'); +% defaultOutputs(1).name = 'chrono'; +% defaultOutputs(1).type = 'chrono'; +% defaultOutputs(1).params.daqChannelID = 'port1/line0'; +% +% defaultOutputs(2).name = 'acqLive'; +% defaultOutputs(2).type = 'acqLive'; +% defaultOutputs(2).params.daqChannelID = 'port0/line1'; +% defaultOutputs(2).params.initialDelay = 0; +% +% defaultOutputs(3).name = 'clock'; +% defaultOutputs(3).type = 'clock'; +% defaultOutputs(3).params.daqChannelID = 'ctr3'; +% defaultOutputs(3).params.initialDelay = 0; +% defaultOutputs(3).params.frequency = 60; +% defaultOutputs(3).params.dutyCycle = 0.2; +% +% defaultOutputs(4).name = 'camSync'; +% defaultOutputs(4).type = 'startStopSync'; +% defaultOutputs(4).params.daqChannelID = 'port0/line3'; +% defaultOutputs(4).params.initialDelay = 0; +% defaultOutputs(4).params.pulseDuration = 0.2; + +% obj.Outputs = defaultOutputs; + if nargin % if old tl hardware struct provided, use these to populate properties obj.Inputs = hw.inputs; obj.DaqVendor = hw.daqVendor; obj.DaqIds = hw.daqDevice; obj.DaqSampleRate = hw.daqSampleRate; obj.DaqSamplesPerNotify = hw.daqSamplesPerNotify; - obj.Outputs(1).daqChannelID = hw.chronoOutDaqChannelID; - obj.Outputs(2).daqChannelID = hw.acqLiveDaqChannelID; - obj.Outputs(3).daqChannelID = hw.clockOutputDaqChannelID; - obj.ClockOutputFrequency = hw.clockOutputFrequency; - obj.ClockOutputDutyCycle = hw.clockOutputDutyCycle; +% obj.Outputs(1).daqChannelID = hw.chronoOutDaqChannelID; +% obj.Outputs(2).daqChannelID = hw.acqLiveDaqChannelID; +% obj.Outputs(3).daqChannelID = hw.clockOutputDaqChannelID; +% obj.ClockOutputFrequency = hw.clockOutputFrequency; +% obj.ClockOutputDutyCycle = hw.clockOutputDutyCycle; end end @@ -142,16 +187,7 @@ function start(obj, expRef, Alyx) end obj.Ref = expRef; % set the current experiment ref obj.AlyxInstance = Alyx; % set the current instance of Alyx - init(obj); % start the relevent sessions and add channels - - %%Send a test pulse low, then high to clocking channel & check we read it back - idx = cellfun(@(s2)strcmp('chrono',s2), {obj.Inputs.name}); - outputSingleScan(obj.Sessions('chrono'), false) - x1 = obj.Sessions('main').inputSingleScan; - outputSingleScan(obj.Sessions('chrono'), true) - x2 = obj.Sessions('main').inputSingleScan; - assert(x1(obj.Inputs(idx).arrayColumn) < 2.5 && x2(obj.Inputs(idx).arrayColumn) > 2.5,... - 'The clocking pulse test could not be read back'); + init(obj); % start the relevent sessions and add channels obj.Listener = obj.Sessions('main').addlistener('DataAvailable', @obj.process); % add listener @@ -181,7 +217,6 @@ function start(obj, expRef, Alyx) obj.Data.startDateTimeStr = datestr(obj.Data.startDateTime); %%Start the DAQ acquiring - outputSingleScan(obj.Sessions('chrono'), false) % make sure chrono is low %LastTimestamp is the timestamp of the last acquisition sample, which is %saved to ensure continuity of acquisition. Here it is initialised as if a %previous acquisition had been made in negative time, since the first @@ -189,27 +224,20 @@ function start(obj, expRef, Alyx) obj.LastTimestamp = -obj.SamplingInterval; startBackground(obj.Sessions('main')); % start aquisition - %%Output clocking pulse and wait for first acquisition to complete - % output first clocking high pulse - t = GetSecs; %system time before outputting chrono flip - outputSingleScan(obj.Sessions('chrono'), obj.NextChronoSign > 0); % flip chrono signal - obj.LastClockSentSysTime = (t + GetSecs)/2; % log mean before/after system time - % wait for first acquisition processing to begin while ~obj.IsRunning pause(5e-3); end - if isKey(obj.Sessions, 'acqLive') % is acqLive being used? - % set acquisition live signal to true - pause(obj.Outputs(cellfun(@(s2)strcmp('chrono',s2), {obj.Outputs.name})).initialDelay); - outputSingleScan(obj.Sessions('acqLive'), true); - end - if isKey(obj.Sessions, 'clock') % is the clock output being used? - % start session to send timing output pulses - startBackground(obj.Sessions('clock')); + for outidx = 1:numel(obj.Outputs) + obj.Outputs(outidx).onStart(obj); end +% if isKey(obj.Sessions, 'clock') % is the clock output being used? +% % start session to send timing output pulses +% startBackground(obj.Sessions('clock')); +% end + % Report success fprintf('Timeline started successfully for ''%s''.\n', expRef); end @@ -385,12 +413,11 @@ function stop(obj) return end % kill acquisition output signals - if isKey(obj.Sessions, 'acqLive') - outputSingleScan(obj.Sessions('acqLive'), false); % live -> false - end - for i = 1:length(obj.UseOutputs) - name = obj.UseOutputs{i}; - stop(obj.Sessions(name)); +% if isKey(obj.Sessions, 'acqLive') +% outputSingleScan(obj.Sessions('acqLive'), false); % live -> false +% end + for outidx = 1:numel(obj.Outputs) + obj.Outputs(outidx).onStop(obj); end pause(obj.StopDelay) @@ -400,14 +427,7 @@ function stop(obj) % wait before deleting the listener to ensure most recent samples are % collected pause(1.5); - delete(obj.Listener) % now delete the data listener - - % release hardware resources - sessions = keys(obj.Sessions); % find names of all current sessions - for i = 1:length(sessions) - name = sessions{i}; - release(obj.Sessions(name)); - end + delete(obj.Listener) % now delete the data listener % only keep the used part of the daq input array obj.Data.rawDAQData((obj.Data.rawDAQSampleCount + 1):end,:) = []; @@ -419,15 +439,38 @@ function stop(obj) % replicate old tl data struct for legacy code idx = cellfun(@(s2)strcmp('chrono',s2), {obj.Inputs.name}); arrayChronoColumn = obj.Inputs(idx).arrayColumn; + inputsIdx = cellfun(@(x)find(strcmp({obj.Inputs.name}, x),1), obj.UseInputs); + + % this block finds the daqChannelID for chrono and acqLive if + % they exist + outputClasses = arrayfun(@class, obj.Outputs, 'uni', false); + chronoChan = []; nextChrono = []; acqLiveChan = []; useClock = false; clockF = []; clockD = []; + chronoOutputIdx = find(strcmp(outputClasses, 'hw.tlOutputChrono'),1); + if ~isempty(chronoOutputIdx) + chronoChan = obj.Outputs(chronoOutputIdx).daqChannelID; + nextChrono = obj.Outputs(chronoOutputIdx).NextChronoSign; + end + acqLiveOutputIdx = find(strcmp(outputClasses, 'hw.tlOutputAcqLive'),1); + if ~isempty(acqLiveOutputIdx) + acqLiveChan = obj.Outputs(acqLiveOutputIdx).daqChannelID; + end + clockOutputIdx = find(strcmp(outputClasses, 'hw.tlOutputClock'),1); + if ~isempty(clockOutputIdx) + useClock = true; + clockF = obj.Outputs(clockOutputIdx).frequency; + clockD = obj.Outputs(clockOutputIdx).dutyCycle; + end + obj.Data.hw = struct('daqVendor', obj.DaqVendor, 'daqDevice', obj.DaqIds,... 'daqSampleRate', obj.DaqSampleRate, 'daqSamplesPerNotify', obj.DaqSamplesPerNotify,... - 'chronoOutDaqChannelID', obj.Outputs(1).daqChannelID, 'acqLiveOutDaqChannelID', obj.Outputs(2).daqChannelID,... - 'useClockOutput', any(strcmp('clock', obj.UseOutputs)), 'clockOutputFrequency', obj.ClockOutputFrequency,... - 'clockOutputDutyCycle', obj.ClockOutputDutyCycle, 'samplingInterval', obj.SamplingInterval,... - 'inputs', obj.Inputs(sign([obj.Inputs.arrayColumn])==1), 'arrayChronoColumn', arrayChronoColumn); + 'chronoOutDaqChannelID', chronoChan, 'acqLiveOutDaqChannelID', acqLiveChan,... + 'useClockOutput', useClock, 'clockOutputFrequency', clockF,... + 'clockOutputDutyCycle', clockD, 'samplingInterval', obj.SamplingInterval,... + 'inputs', obj.Inputs(inputsIdx), ... % find the correct inputs, in the correct order + 'arrayChronoColumn', arrayChronoColumn); obj.Data.expRef = obj.Ref; % save experiment ref obj.Data.isRunning = obj.IsRunning; - obj.Data.nextChronoSign = obj.NextChronoSign; + obj.Data.nextChronoSign = nextChrono; obj.Data.lastTimestamp = obj.LastTimestamp; obj.Data.lastClockSentSysTime = obj.LastClockSentSysTime; obj.Data.currSysTimeTimelineOffset = obj.CurrSysTimeTimelineOffset; @@ -480,6 +523,13 @@ function stop(obj) % Report successful stop fprintf('Timeline for ''%s'' stopped and saved successfully.\n', obj.Ref); end + + function s = getSessions(obj, name) + % returns the Sessions property. Some things (e.g. output + % classes) need this. + s = obj.Sessions(name); + end + end methods (Access = private) @@ -490,30 +540,33 @@ function init(obj) % Also add a 'main' session to which all input channels are % added. See daq.createSession - %%Create session objects for chrono and other outputs - [use, idx] = intersect({obj.Outputs.name}, obj.UseOutputs); % find which outputs to use -% assert(numel(idx) == numel(obj.UseOutputs), 'Not all outputs were recognised'); - for i = 1:length(use) - out = obj.Outputs(idx(i)); % get channel info, etc. - switch use{i} - case 'chrono' - obj.Sessions('chrono') = daq.createSession(obj.DaqVendor); - obj.Sessions('chrono').addDigitalChannel(obj.DaqIds, out.daqChannelID, out.type); - - case 'acqLive' - obj.Sessions('acqLive') = daq.createSession(obj.DaqVendor); - obj.Sessions('acqLive').addDigitalChannel(obj.DaqIds, out.daqChannelID, out.type); - outputSingleScan(obj.Sessions('acqLive'), false); % ensure acq live is false - - case 'clock' - obj.Sessions('clock') = daq.createSession(obj.DaqVendor); - obj.Sessions('clock').IsContinuous = true; - clocked = obj.Sessions('clock').addCounterOutputChannel(obj.DaqIds, out.daqChannelID, out.type); - clocked.Frequency = obj.ClockOutputFrequency; - clocked.DutyCycle = obj.ClockOutputDutyCycle; - clocked.InitialDelay = out.delay; - end - end +% %%Create session objects for chrono and other outputs +% [use, idx] = intersect({obj.Outputs.name}, obj.UseOutputs); % find which outputs to use +% % assert(numel(idx) == numel(obj.UseOutputs), 'Not all outputs were recognised'); +% for i = 1:length(use) +% out = obj.Outputs(idx(i)); % get channel info, etc. +% switch use{i} +% case 'chrono' +% obj.Sessions('chrono') = daq.createSession(obj.DaqVendor); +% obj.Sessions('chrono').addDigitalChannel(obj.DaqIds, out.daqChannelID, out.type); +% +% case 'acqLive' +% obj.Sessions('acqLive') = daq.createSession(obj.DaqVendor); +% obj.Sessions('acqLive').addDigitalChannel(obj.DaqIds, out.daqChannelID, out.type); +% outputSingleScan(obj.Sessions('acqLive'), false); % ensure acq live is false +% +% case 'clock' +% obj.Sessions('clock') = daq.createSession(obj.DaqVendor); +% obj.Sessions('clock').IsContinuous = true; +% clocked = obj.Sessions('clock').addCounterOutputChannel(obj.DaqIds, out.daqChannelID, out.type); +% clocked.Frequency = obj.ClockOutputFrequency; +% clocked.DutyCycle = obj.ClockOutputDutyCycle; +% clocked.InitialDelay = out.delay; +% end +% end + + + %%Create channels for each input [use, idx] = intersect({obj.Inputs.name}, obj.UseInputs);% find which inputs to use assert(numel(idx) == numel(obj.UseInputs), 'Not all inputs were recognised'); @@ -524,7 +577,6 @@ function init(obj) obj.Sessions('main') = inputSession; for i = 1:length(use) in = obj.Inputs(strcmp({obj.Inputs.name}, obj.UseInputs(i))); -% in = obj.Inputs(idx(i)); % get channel info, etc. fprintf(1, 'adding channel %s on %s\n', in.name, in.daqChannelID); switch in.measurement @@ -542,6 +594,11 @@ function init(obj) end obj.Inputs(strcmp({obj.Inputs.name}, obj.UseInputs(i))).arrayColumn = i; end + + % Initialize outputs + for outidx = 1:numel(obj.Outputs) + obj.Outputs(outidx).onInit(obj); + end end function process(obj, ~, event) @@ -565,25 +622,10 @@ function process(obj, ~, event) 'Discontinuity of DAQ acquistion detected: last timestamp was %f and this one is %f',... obj.LastTimestamp, event.TimeStamps(1)); - %%% The chrono "out" value is flipped at a recorded time, and - %%% the sample index that this flip is measured is noted - % First, find the index of the flip in the latest chunk of data - idx = elementByName(obj.Inputs, 'chrono'); - clockChangeIdx = find(sign(event.Data(:,obj.Inputs(idx).arrayColumn) - 2.5) == obj.NextChronoSign, 1); - - %Ensure the clocking pulse was detected - if ~isempty(clockChangeIdx) - clockChangeTimestamp = event.TimeStamps(clockChangeIdx); - obj.CurrSysTimeTimelineOffset = obj.LastClockSentSysTime - clockChangeTimestamp; - else - warning('Rigging:Timeline:timing', 'clocking pulse not detected - probably lagging more than one data chunk'); + % CALL ONPROCESS METHODS HERE + for outidx = 1:numel(obj.Outputs) + obj.Outputs(outidx).onProcess(obj, event); end - - %Now send the next clock pulse - obj.NextChronoSign = -obj.NextChronoSign; % flip next chrono - t = GetSecs; % system time before output - outputSingleScan(obj.Sessions('chrono'), obj.NextChronoSign > 0); % send next chrono flip - obj.LastClockSentSysTime = (t + GetSecs)/2; % record mean before/after system time %%% Store new samples into the timeline array prevSampleCount = obj.Data.rawDAQSampleCount; @@ -624,8 +666,9 @@ function livePlot(obj, data) end end - % get the names of the inputs being recorded - names = pick({obj.Inputs.name}, find([obj.Inputs.arrayColumn] > -1), 'cell'); + % get the names of the inputs being recorded (in the correct + % order) + names = pick({obj.Inputs.name}, cellfun(@(x)find(strcmp({obj.Inputs.name}, x),1), obj.UseInputs), 'cell'); nSamps = size(data,1); % Get the number of samples in this chunck nChans = size(data,2); % Get the number of channels traceSep = 7; % unit is Volts - for most channels the max is 5V so this is a good separation @@ -651,8 +694,7 @@ function livePlot(obj, data) % get the measurement type of each channel, since Position-type % inputs are plotted differently. - meas = {obj.Inputs.measurement}; - meas = meas(ismember({obj.Inputs.name}, obj.UseInputs)); + meas = pick({obj.Inputs.measurement}, cellfun(@(x)find(strcmp({obj.Inputs.name}, x),1), obj.UseInputs), 'cell'); for t = 1:length(traces) if strcmp(meas{t}, 'Position') diff --git a/+hw/tlOutput.m b/+hw/tlOutput.m new file mode 100644 index 00000000..faf2b3d6 --- /dev/null +++ b/+hw/tlOutput.m @@ -0,0 +1,36 @@ +classdef tlOutput < matlab.mixin.Heterogeneous & handle + %hw.tlOutput Code to specify an output channel for timeline + % This is an abstract class. + % + % Below is a list of some subclasses and their functions: + % hw.tlOutputClock - clocked output on a counter channel + % hw.tlOutputChrono - the default, flip/flip status check output + % hw.tlOutputAcqLive - a digital channel that is on for the duration + % of the recording + % hw.tlOutputStartStopSync - a digital channel that turns on only at + % the beginning and end of the recording + % + % The timeline object will call the onLoad, onStart, and onStop + % methods. + % + % Part of Rigbox + + % 2018-01 NS created + + properties + name + session + end + + methods (Abstract) + onInit(obj, timeline) + onStart(obj, timeline) + onProcess(obj, timeline, event) + onStop(obj, timeline) + %s = propertiesAsStruct(obj) % recommend we have a method that does this, + % so that we can save out all the properties in a json file. Incl a + % version number? + end + +end + diff --git a/+hw/tlOutputAcqLive.m b/+hw/tlOutputAcqLive.m new file mode 100644 index 00000000..8c1492ad --- /dev/null +++ b/+hw/tlOutputAcqLive.m @@ -0,0 +1,52 @@ +classdef tlOutputAcqLive < hw.tlOutput + %hw.tlOutputAcqLive A digital signal that goes up when the recording starts, + % down when it ends. + % See also hw.tlOutput and hw.Timeline + % + % Part of Rigbox + % 2018-01 NS + + properties + daqDeviceID + daqChannelID + daqVendor = 'ni' + initialDelay = 0 + end + + methods + function obj = tlOutputAcqLive(name, daqDeviceID, daqChannelID) + obj.name = name; + obj.daqDeviceID = daqDeviceID; + obj.daqChannelID = daqChannelID; + end + + function onInit(obj, ~) + fprintf(1, 'initialize acqLive\n'); + obj.session = daq.createSession(obj.daqVendor); + obj.session.addDigitalChannel(obj.daqDeviceID, obj.daqChannelID, 'OutputOnly'); + outputSingleScan(obj.session, false); + end + + function onStart(obj, ~) + fprintf(1, 'start acqLive\n'); + + pause(obj.initialDelay); + outputSingleScan(obj.session, true); + + end + + function onProcess(~, ~, ~) + fprintf(1, 'process acqLive\n'); + + end + + function onStop(obj,~) + fprintf(1, 'stop chrono\n'); + stop(obj.session); + release(obj.session); + end + + end + +end + diff --git a/+hw/tlOutputChrono.m b/+hw/tlOutputChrono.m new file mode 100644 index 00000000..3aa0cc5d --- /dev/null +++ b/+hw/tlOutputChrono.m @@ -0,0 +1,89 @@ +classdef tlOutputChrono < hw.tlOutput + %hw.tlOutputChrono Timeline uses this to monitor that + % acquisition is proceeding normally during a recording. + % See also hw.tlOutput and hw.Timeline + % + % Part of Rigbox + % 2018-01 NS + + properties + daqDeviceID + daqChannelID + daqVendor = 'ni' + NextChronoSign = 1 % the value to output on the chrono channel, the sign is changed each 'Process' event + end + + methods + function obj = tlOutputChrono(name, daqDeviceID, daqChannelID) + obj.name = name; + obj.daqDeviceID = daqDeviceID; + obj.daqChannelID = daqChannelID; + end + + function onInit(obj, timeline) + fprintf(1, 'initialize chrono\n'); + obj.session = daq.createSession(obj.daqVendor); + obj.session.addDigitalChannel(obj.daqDeviceID, obj.daqChannelID, 'OutputOnly'); + + tls = timeline.getSessions('main'); + + %%Send a test pulse low, then high to clocking channel & check we read it back + idx = cellfun(@(s2)strcmp('chrono',s2), {timeline.Inputs.name}); + outputSingleScan(obj.session, false) + x1 = tls.inputSingleScan; + outputSingleScan(obj.session, true) + x2 = tls.inputSingleScan; + assert(x1(timeline.Inputs(idx).arrayColumn) < 2.5 && x2(timeline.Inputs(idx).arrayColumn) > 2.5,... + 'The clocking pulse test could not be read back'); + end + + function onStart(~, ~) + fprintf(1, 'start chrono\n'); + + end + + function onProcess(obj, timeline, event) + fprintf(1, 'process chrono\n'); + + % sign of the chrono signal is + % flipped on each call (at LastClockSentSysTime), and the + % time of the previous flip is found in the data and its + % timestamp noted. This is used by tl.time() to convert + % between system time and acquisition time. + % + % LastTimestamp is the time of the last scan in the previous + % data chunk, and is used to ensure no data samples have been + % lost. + + %%% The chrono "out" value is flipped at a recorded time, and + %%% the sample index that this flip is measured is noted + % First, find the index of the flip in the latest chunk of data + idx = elementByName(timeline.Inputs, 'chrono'); + clockChangeIdx = find(sign(event.Data(:,timeline.Inputs(idx).arrayColumn) - 2.5) == obj.NextChronoSign, 1); + + %Ensure the clocking pulse was detected + if ~isempty(clockChangeIdx) + clockChangeTimestamp = event.TimeStamps(clockChangeIdx); + timeline.CurrSysTimeTimelineOffset = timeline.LastClockSentSysTime - clockChangeTimestamp; + else + warning('Rigging:Timeline:timing', 'clocking pulse not detected - probably lagging more than one data chunk'); + end + + %Now send the next clock pulse + obj.NextChronoSign = -obj.NextChronoSign; % flip next chrono + t = GetSecs; % system time before output + outputSingleScan(obj.session, obj.NextChronoSign > 0); % send next chrono flip + timeline.LastClockSentSysTime = (t + GetSecs)/2; % record mean before/after system time + + end + + function onStop(obj,~) + fprintf(1, 'stop chrono\n'); + stop(obj.session); + release(obj.session); + end + + end + +end + From 92953eecdec7ff3c7aa39618d75937ad3c26f01c Mon Sep 17 00:00:00 2001 From: nsteinme <nick.steinmetz@gmail.com> Date: Thu, 25 Jan 2018 19:28:53 +0000 Subject: [PATCH 038/393] finished and tested outputs as classes --- +hw/Timeline.m | 101 +++--------------------------------- +hw/tlOutput.m | 2 +- +hw/tlOutputAcqLive.m | 3 +- +hw/tlOutputChrono.m | 1 + +hw/tlOutputClock.m | 60 +++++++++++++++++++++ +hw/tlOutputStartStopSync.m | 65 +++++++++++++++++++++++ 6 files changed, 137 insertions(+), 95 deletions(-) create mode 100644 +hw/tlOutputClock.m create mode 100644 +hw/tlOutputStartStopSync.m diff --git a/+hw/Timeline.m b/+hw/Timeline.m index 0e69607a..4d0caf1a 100644 --- a/+hw/Timeline.m +++ b/+hw/Timeline.m @@ -70,29 +70,8 @@ DaqSampleRate = 1000 % rate at which daq aquires data in Hz, see Rate DaqSamplesPerNotify % determines the number of data samples to be processed each time, see Timeline.process(), constructor and NotifyWhenDataAvailableExceeds - Outputs % structure of outputs with their names and configuration information - % All outputs require the following fields: - % - name: a string identifier - % - type: one of the supported modes that we know how to - % handle, see below - % - params, the required parameters for the selected type - % - % Available types: - % - chrono: a default type that Timeline uses to monitor that - % acquisition is proceeding normally during a recording. - % Requires: daqChannelID - % - % - acqLive: a digital output that is turned on at the start - % of the timeline recording, turned off at the end - % Requires: daqChannelID, initialDelay - % - % - clock: a regular pulse at a specified frequency and duty - % cycle. Can be used to trigger camera frames, e.g. - % Requires: daqChannelID, initialDelay, frequency, dutyCycle - % - % - startStopSync: a brief pulse at the beginning and at the - % end of a recording. Can be used for synchronization - % - Requires: daqChannelID, initialDelay, pulseDuration + Outputs % array of output classes, defining any signals you desire to be sent from the daq. + % see hw.tlOutput, and, e.g. hw.tlOutputClock. Inputs = struct('name', 'chrono',... 'arrayColumn', -1,... % -1 is default indicating unused, this is update when the channels are added during tl.start() @@ -100,7 +79,6 @@ 'measurement', 'Voltage',... 'terminalConfig', 'SingleEnded') UseInputs = {'chrono'} % array of inputs to record while tl is running - %UseOutputs = {'chrono'} % array of output pulses to use while tl is running StopDelay = 2 % currently pauses for at least 2 secs as 'hack' before stopping main DAQ session MaxExpectedDuration = 2*60*60 % expected experiment time so data structure is initialised to sensible size (in secs) AquiredDataType = 'double' % default data type for the acquired data array (i.e. Data.rawDAQData) @@ -135,44 +113,14 @@ % Constructor method % Adds chrono, aquireLive and clock to the outputs list, % along with default ports and delays - obj.DaqSamplesPerNotify = 1/obj.SamplingInterval; % calculate DaqSamplesPerNotify - -% defaultOutputs = struct('name', 'type', 'params'); -% defaultOutputs(1).name = 'chrono'; -% defaultOutputs(1).type = 'chrono'; -% defaultOutputs(1).params.daqChannelID = 'port1/line0'; -% -% defaultOutputs(2).name = 'acqLive'; -% defaultOutputs(2).type = 'acqLive'; -% defaultOutputs(2).params.daqChannelID = 'port0/line1'; -% defaultOutputs(2).params.initialDelay = 0; -% -% defaultOutputs(3).name = 'clock'; -% defaultOutputs(3).type = 'clock'; -% defaultOutputs(3).params.daqChannelID = 'ctr3'; -% defaultOutputs(3).params.initialDelay = 0; -% defaultOutputs(3).params.frequency = 60; -% defaultOutputs(3).params.dutyCycle = 0.2; -% -% defaultOutputs(4).name = 'camSync'; -% defaultOutputs(4).type = 'startStopSync'; -% defaultOutputs(4).params.daqChannelID = 'port0/line3'; -% defaultOutputs(4).params.initialDelay = 0; -% defaultOutputs(4).params.pulseDuration = 0.2; - -% obj.Outputs = defaultOutputs; - + obj.DaqSamplesPerNotify = 1/obj.SamplingInterval; % calculate DaqSamplesPerNotify + obj.Outputs = hw.tlOutputChrono('chrono', obj.DaqIds, 'port0/line1'); if nargin % if old tl hardware struct provided, use these to populate properties obj.Inputs = hw.inputs; obj.DaqVendor = hw.daqVendor; obj.DaqIds = hw.daqDevice; obj.DaqSampleRate = hw.daqSampleRate; obj.DaqSamplesPerNotify = hw.daqSamplesPerNotify; -% obj.Outputs(1).daqChannelID = hw.chronoOutDaqChannelID; -% obj.Outputs(2).daqChannelID = hw.acqLiveDaqChannelID; -% obj.Outputs(3).daqChannelID = hw.clockOutputDaqChannelID; -% obj.ClockOutputFrequency = hw.clockOutputFrequency; -% obj.ClockOutputDutyCycle = hw.clockOutputDutyCycle; end end @@ -233,11 +181,6 @@ function start(obj, expRef, Alyx) obj.Outputs(outidx).onStart(obj); end -% if isKey(obj.Sessions, 'clock') % is the clock output being used? -% % start session to send timing output pulses -% startBackground(obj.Sessions('clock')); -% end - % Report success fprintf('Timeline started successfully for ''%s''.\n', expRef); end @@ -367,9 +310,10 @@ function wiringInfo(obj, name) fprintf('PFI4-7 = port1/line0-3\n') fprintf('ctr0-3 = port1/line0-3\n') else + outputClasses = arrayfun(@class, obj.Outputs, 'uni', false); if strcmp(name, 'chrono') % Chrono wiring info idI = cellfun(@(s2)strcmp('chrono',s2), {obj.Inputs.name}); - idO = cellfun(@(s2)strcmp('chrono',s2), {obj.Outputs.name}); + idO = find(cellfun(@(s2)strcmp('tlOutputChrono',s2), outputClasses),1); fprintf('Bridge terminals %s and %s\n',... obj.Outputs(idO).daqChannelID, obj.Inputs(idI).daqChannelID) elseif any(strcmp(name, {obj.Outputs.name})) % Output wiring info @@ -412,10 +356,8 @@ function stop(obj) warning('Nothing to do, Timeline is not running!') return end + % kill acquisition output signals -% if isKey(obj.Sessions, 'acqLive') -% outputSingleScan(obj.Sessions('acqLive'), false); % live -> false -% end for outidx = 1:numel(obj.Outputs) obj.Outputs(outidx).onStop(obj); end @@ -538,34 +480,7 @@ function init(obj) % TL.INIT() creates all the DAQ sessions % and stores them in the Sessions map by their Outputs name. % Also add a 'main' session to which all input channels are - % added. See daq.createSession - -% %%Create session objects for chrono and other outputs -% [use, idx] = intersect({obj.Outputs.name}, obj.UseOutputs); % find which outputs to use -% % assert(numel(idx) == numel(obj.UseOutputs), 'Not all outputs were recognised'); -% for i = 1:length(use) -% out = obj.Outputs(idx(i)); % get channel info, etc. -% switch use{i} -% case 'chrono' -% obj.Sessions('chrono') = daq.createSession(obj.DaqVendor); -% obj.Sessions('chrono').addDigitalChannel(obj.DaqIds, out.daqChannelID, out.type); -% -% case 'acqLive' -% obj.Sessions('acqLive') = daq.createSession(obj.DaqVendor); -% obj.Sessions('acqLive').addDigitalChannel(obj.DaqIds, out.daqChannelID, out.type); -% outputSingleScan(obj.Sessions('acqLive'), false); % ensure acq live is false -% -% case 'clock' -% obj.Sessions('clock') = daq.createSession(obj.DaqVendor); -% obj.Sessions('clock').IsContinuous = true; -% clocked = obj.Sessions('clock').addCounterOutputChannel(obj.DaqIds, out.daqChannelID, out.type); -% clocked.Frequency = obj.ClockOutputFrequency; -% clocked.DutyCycle = obj.ClockOutputDutyCycle; -% clocked.InitialDelay = out.delay; -% end -% end - - + % added. See daq.createSession %%Create channels for each input [use, idx] = intersect({obj.Inputs.name}, obj.UseInputs);% find which inputs to use diff --git a/+hw/tlOutput.m b/+hw/tlOutput.m index faf2b3d6..fdc2fc79 100644 --- a/+hw/tlOutput.m +++ b/+hw/tlOutput.m @@ -10,7 +10,7 @@ % hw.tlOutputStartStopSync - a digital channel that turns on only at % the beginning and end of the recording % - % The timeline object will call the onLoad, onStart, and onStop + % The timeline object will call the onInit, onStart, onProcess, and onStop % methods. % % Part of Rigbox diff --git a/+hw/tlOutputAcqLive.m b/+hw/tlOutputAcqLive.m index 8c1492ad..7a023147 100644 --- a/+hw/tlOutputAcqLive.m +++ b/+hw/tlOutputAcqLive.m @@ -41,9 +41,10 @@ function onProcess(~, ~, ~) end function onStop(obj,~) - fprintf(1, 'stop chrono\n'); + fprintf(1, 'stop acqLive\n'); stop(obj.session); release(obj.session); + obj.session = []; end end diff --git a/+hw/tlOutputChrono.m b/+hw/tlOutputChrono.m index 3aa0cc5d..4742afa1 100644 --- a/+hw/tlOutputChrono.m +++ b/+hw/tlOutputChrono.m @@ -81,6 +81,7 @@ function onStop(obj,~) fprintf(1, 'stop chrono\n'); stop(obj.session); release(obj.session); + obj.session = []; end end diff --git a/+hw/tlOutputClock.m b/+hw/tlOutputClock.m new file mode 100644 index 00000000..dc65a171 --- /dev/null +++ b/+hw/tlOutputClock.m @@ -0,0 +1,60 @@ +classdef tlOutputClock < hw.tlOutput + %hw.tlOutputClock A a regular pulse at a specified frequency and duty + % cycle. Can be used to trigger camera frames, e.g. + % See also hw.tlOutput and hw.Timeline + % + % Part of Rigbox + % 2018-01 NS + + properties + daqDeviceID + daqChannelID + daqVendor = 'ni' + initialDelay = 0 + frequency = 60; + dutyCycle = 0.2; + clockChan + end + + methods + function obj = tlOutputClock(name, daqDeviceID, daqChannelID) + obj.name = name; + obj.daqDeviceID = daqDeviceID; + obj.daqChannelID = daqChannelID; + end + + function onInit(obj, ~) + fprintf(1, 'initialize Clock\n'); + obj.session = daq.createSession(obj.daqVendor); + obj.session.IsContinuous = true; + clocked = obj.session.addCounterOutputChannel(obj.daqDeviceID, obj.daqChannelID, 'PulseGeneration'); + clocked.Frequency = obj.frequency; + clocked.DutyCycle = obj.dutyCycle; + clocked.InitialDelay = obj.initialDelay; + obj.clockChan = clocked; + + end + + function onStart(obj, ~) + fprintf(1, 'start Clock\n'); + + startBackground(obj.session); + end + + function onProcess(~, ~, ~) + fprintf(1, 'process Clock\n'); + + end + + function onStop(obj,~) + fprintf(1, 'stop Clock\n'); + + stop(obj.session); + release(obj.session); + obj.session = []; + end + + end + +end + diff --git a/+hw/tlOutputStartStopSync.m b/+hw/tlOutputStartStopSync.m new file mode 100644 index 00000000..577b942e --- /dev/null +++ b/+hw/tlOutputStartStopSync.m @@ -0,0 +1,65 @@ +classdef tlOutputStartStopSync < hw.tlOutput + %hw.tlOutputStartStopSync A digital signal that goes up when the recording starts, + % but just briefly, then down again at the end. + % See also hw.tlOutput and hw.Timeline + % + % Part of Rigbox + % 2018-01 NS + + properties + daqDeviceID + daqChannelID + daqVendor = 'ni' + initialDelay = 0 + pulseDuration = 0.2; + end + + methods + function obj = tlOutputStartStopSync(name, daqDeviceID, daqChannelID) + obj.name = name; + obj.daqDeviceID = daqDeviceID; + obj.daqChannelID = daqChannelID; + end + + function onInit(obj, ~) + fprintf(1, 'initialize StartStopSync\n'); + obj.session = daq.createSession(obj.daqVendor); + obj.session.addDigitalChannel(obj.daqDeviceID, obj.daqChannelID, 'OutputOnly'); + outputSingleScan(obj.session, false); % ensure that it starts down + % by the way, if you use this to control a light for + % synchronization, note that you can configure in nidaqMX a + % "default" value for the channel, so for example it will stay + % "false" at all times even if the computer reboots. + end + + function onStart(obj, ~) + fprintf(1, 'start StartStopSync\n'); + + pause(obj.initialDelay); + outputSingleScan(obj.session, true); + pause(obj.pulseDuration); + outputSingleScan(obj.session, false); + + end + + function onProcess(~, ~, ~) + fprintf(1, 'process StartStopSync\n'); + + end + + function onStop(obj,~) + fprintf(1, 'stop StartStopSync\n'); + + outputSingleScan(obj.session, true); + pause(obj.pulseDuration); + outputSingleScan(obj.session, false); + + stop(obj.session); + release(obj.session); + obj.session = []; + end + + end + +end + From de19f8a0ff94034dc1ea9e3d09d1249e564590c2 Mon Sep 17 00:00:00 2001 From: nsteinme <nick.steinmetz@gmail.com> Date: Fri, 26 Jan 2018 12:57:36 +0000 Subject: [PATCH 039/393] update output classes with toStr, enable, transients --- +hw/Timeline.m | 11 ++-- +hw/tlOutput.m | 21 +++--- +hw/tlOutputAcqLive.m | 52 +++++++++------ +hw/tlOutputChrono.m | 123 ++++++++++++++++++++---------------- +hw/tlOutputClock.m | 66 +++++++++++-------- +hw/tlOutputStartStopSync.m | 71 ++++++++++++--------- 6 files changed, 200 insertions(+), 144 deletions(-) diff --git a/+hw/Timeline.m b/+hw/Timeline.m index 4d0caf1a..2318ae54 100644 --- a/+hw/Timeline.m +++ b/+hw/Timeline.m @@ -178,7 +178,7 @@ function start(obj, expRef, Alyx) end for outidx = 1:numel(obj.Outputs) - obj.Outputs(outidx).onStart(obj); + obj.Outputs(outidx).start(obj); end % Report success @@ -359,7 +359,7 @@ function stop(obj) % kill acquisition output signals for outidx = 1:numel(obj.Outputs) - obj.Outputs(outidx).onStop(obj); + obj.Outputs(outidx).stop(obj); end pause(obj.StopDelay) @@ -512,7 +512,7 @@ function init(obj) % Initialize outputs for outidx = 1:numel(obj.Outputs) - obj.Outputs(outidx).onInit(obj); + obj.Outputs(outidx).init(obj); end end @@ -536,10 +536,9 @@ function process(obj, ~, event) assert(abs(event.TimeStamps(1) - obj.LastTimestamp - obj.SamplingInterval) < 1e-8,... 'Discontinuity of DAQ acquistion detected: last timestamp was %f and this one is %f',... obj.LastTimestamp, event.TimeStamps(1)); - - % CALL ONPROCESS METHODS HERE + for outidx = 1:numel(obj.Outputs) - obj.Outputs(outidx).onProcess(obj, event); + obj.Outputs(outidx).process(obj, event); end %%% Store new samples into the timeline array diff --git a/+hw/tlOutput.m b/+hw/tlOutput.m index fdc2fc79..e178fd9d 100644 --- a/+hw/tlOutput.m +++ b/+hw/tlOutput.m @@ -18,18 +18,21 @@ % 2018-01 NS created properties - name - session + name + enable = true % will not do anything with it unless this is true + verbose = false % output status updates. Initialization message outputs regardless of verbose. + end + + properties (Transient) + session end methods (Abstract) - onInit(obj, timeline) - onStart(obj, timeline) - onProcess(obj, timeline, event) - onStop(obj, timeline) - %s = propertiesAsStruct(obj) % recommend we have a method that does this, - % so that we can save out all the properties in a json file. Incl a - % version number? + init(obj, timeline) + start(obj, timeline) + process(obj, timeline, event) + stop(obj, timeline) + s = toStr(obj) % a string that describes the object succintly end end diff --git a/+hw/tlOutputAcqLive.m b/+hw/tlOutputAcqLive.m index 7a023147..a8fae92a 100644 --- a/+hw/tlOutputAcqLive.m +++ b/+hw/tlOutputAcqLive.m @@ -20,31 +20,45 @@ obj.daqChannelID = daqChannelID; end - function onInit(obj, ~) - fprintf(1, 'initialize acqLive\n'); - obj.session = daq.createSession(obj.daqVendor); - obj.session.addDigitalChannel(obj.daqDeviceID, obj.daqChannelID, 'OutputOnly'); - outputSingleScan(obj.session, false); + function init(obj, ~) + if obj.enable + fprintf(1, 'initializing %s\n', obj.toStr); + obj.session = daq.createSession(obj.daqVendor); + obj.session.addDigitalChannel(obj.daqDeviceID, obj.daqChannelID, 'OutputOnly'); + outputSingleScan(obj.session, false); + end end - function onStart(obj, ~) - fprintf(1, 'start acqLive\n'); - - pause(obj.initialDelay); - outputSingleScan(obj.session, true); - + function start(obj, ~) + if obj.enable + if obj.verbose + fprintf(1, 'start %s\n', obj.name); + end + + pause(obj.initialDelay); + outputSingleScan(obj.session, true); + end end - function onProcess(~, ~, ~) - fprintf(1, 'process acqLive\n'); - + function process(~, ~, ~) + %fprintf(1, 'process acqLive\n'); + % -- pass end - function onStop(obj,~) - fprintf(1, 'stop acqLive\n'); - stop(obj.session); - release(obj.session); - obj.session = []; + function stop(obj,~) + if obj.enable + if obj.verbose + fprintf(1, 'stop %s\n', obj.name); + end + stop(obj.session); + release(obj.session); + obj.session = []; + end + end + + function s = toStr(obj) + s = sprintf('"%s" on %s/%s (acqLive, initial delay %.2f)', obj.name, ... + obj.daqDeviceID, obj.daqChannelID, obj.initialDelay); end end diff --git a/+hw/tlOutputChrono.m b/+hw/tlOutputChrono.m index 4742afa1..ad16ff3c 100644 --- a/+hw/tlOutputChrono.m +++ b/+hw/tlOutputChrono.m @@ -20,68 +20,81 @@ obj.daqChannelID = daqChannelID; end - function onInit(obj, timeline) - fprintf(1, 'initialize chrono\n'); - obj.session = daq.createSession(obj.daqVendor); - obj.session.addDigitalChannel(obj.daqDeviceID, obj.daqChannelID, 'OutputOnly'); - - tls = timeline.getSessions('main'); - - %%Send a test pulse low, then high to clocking channel & check we read it back - idx = cellfun(@(s2)strcmp('chrono',s2), {timeline.Inputs.name}); - outputSingleScan(obj.session, false) - x1 = tls.inputSingleScan; - outputSingleScan(obj.session, true) - x2 = tls.inputSingleScan; - assert(x1(timeline.Inputs(idx).arrayColumn) < 2.5 && x2(timeline.Inputs(idx).arrayColumn) > 2.5,... - 'The clocking pulse test could not be read back'); + function init(obj, timeline) + if obj.enable + fprintf(1, 'initializing %s\n', obj.toStr); + obj.session = daq.createSession(obj.daqVendor); + obj.session.addDigitalChannel(obj.daqDeviceID, obj.daqChannelID, 'OutputOnly'); + + tls = timeline.getSessions('main'); + + %%Send a test pulse low, then high to clocking channel & check we read it back + idx = cellfun(@(s2)strcmp('chrono',s2), {timeline.Inputs.name}); + outputSingleScan(obj.session, false) + x1 = tls.inputSingleScan; + outputSingleScan(obj.session, true) + x2 = tls.inputSingleScan; + assert(x1(timeline.Inputs(idx).arrayColumn) < 2.5 && x2(timeline.Inputs(idx).arrayColumn) > 2.5,... + 'The clocking pulse test could not be read back'); + end + end + + function start(~, ~) + %fprintf(1, 'start chrono\n'); + % -- pass end - function onStart(~, ~) - fprintf(1, 'start chrono\n'); - + function process(obj, timeline, event) + if obj.enable + if obj.verbose + fprintf(1, 'process %s\n', obj.name); + end + % sign of the chrono signal is + % flipped on each call (at LastClockSentSysTime), and the + % time of the previous flip is found in the data and its + % timestamp noted. This is used by tl.time() to convert + % between system time and acquisition time. + % + % LastTimestamp is the time of the last scan in the previous + % data chunk, and is used to ensure no data samples have been + % lost. + + %%% The chrono "out" value is flipped at a recorded time, and + %%% the sample index that this flip is measured is noted + % First, find the index of the flip in the latest chunk of data + idx = elementByName(timeline.Inputs, 'chrono'); + clockChangeIdx = find(sign(event.Data(:,timeline.Inputs(idx).arrayColumn) - 2.5) == obj.NextChronoSign, 1); + + %Ensure the clocking pulse was detected + if ~isempty(clockChangeIdx) + clockChangeTimestamp = event.TimeStamps(clockChangeIdx); + timeline.CurrSysTimeTimelineOffset = timeline.LastClockSentSysTime - clockChangeTimestamp; + else + warning('Rigging:Timeline:timing', 'clocking pulse not detected - probably lagging more than one data chunk'); + end + + %Now send the next clock pulse + obj.NextChronoSign = -obj.NextChronoSign; % flip next chrono + t = GetSecs; % system time before output + outputSingleScan(obj.session, obj.NextChronoSign > 0); % send next chrono flip + timeline.LastClockSentSysTime = (t + GetSecs)/2; % record mean before/after system time + end end - function onProcess(obj, timeline, event) - fprintf(1, 'process chrono\n'); - - % sign of the chrono signal is - % flipped on each call (at LastClockSentSysTime), and the - % time of the previous flip is found in the data and its - % timestamp noted. This is used by tl.time() to convert - % between system time and acquisition time. - % - % LastTimestamp is the time of the last scan in the previous - % data chunk, and is used to ensure no data samples have been - % lost. - - %%% The chrono "out" value is flipped at a recorded time, and - %%% the sample index that this flip is measured is noted - % First, find the index of the flip in the latest chunk of data - idx = elementByName(timeline.Inputs, 'chrono'); - clockChangeIdx = find(sign(event.Data(:,timeline.Inputs(idx).arrayColumn) - 2.5) == obj.NextChronoSign, 1); - - %Ensure the clocking pulse was detected - if ~isempty(clockChangeIdx) - clockChangeTimestamp = event.TimeStamps(clockChangeIdx); - timeline.CurrSysTimeTimelineOffset = timeline.LastClockSentSysTime - clockChangeTimestamp; - else - warning('Rigging:Timeline:timing', 'clocking pulse not detected - probably lagging more than one data chunk'); + function stop(obj,~) + if enable + if obj.verbose + fprintf(1, 'stop %s\n', obj.name); + end + stop(obj.session); + release(obj.session); + obj.session = []; end - - %Now send the next clock pulse - obj.NextChronoSign = -obj.NextChronoSign; % flip next chrono - t = GetSecs; % system time before output - outputSingleScan(obj.session, obj.NextChronoSign > 0); % send next chrono flip - timeline.LastClockSentSysTime = (t + GetSecs)/2; % record mean before/after system time - end - function onStop(obj,~) - fprintf(1, 'stop chrono\n'); - stop(obj.session); - release(obj.session); - obj.session = []; + function s = toStr(obj) + s = sprintf('"%s" on %s/%s (chrono)', obj.name, ... + obj.daqDeviceID, obj.daqChannelID); end end diff --git a/+hw/tlOutputClock.m b/+hw/tlOutputClock.m index dc65a171..f5e206fb 100644 --- a/+hw/tlOutputClock.m +++ b/+hw/tlOutputClock.m @@ -12,8 +12,11 @@ daqVendor = 'ni' initialDelay = 0 frequency = 60; - dutyCycle = 0.2; - clockChan + dutyCycle = 0.2; + end + + properties (Transient) + clockChan end methods @@ -23,37 +26,50 @@ obj.daqChannelID = daqChannelID; end - function onInit(obj, ~) - fprintf(1, 'initialize Clock\n'); - obj.session = daq.createSession(obj.daqVendor); - obj.session.IsContinuous = true; - clocked = obj.session.addCounterOutputChannel(obj.daqDeviceID, obj.daqChannelID, 'PulseGeneration'); - clocked.Frequency = obj.frequency; - clocked.DutyCycle = obj.dutyCycle; - clocked.InitialDelay = obj.initialDelay; - obj.clockChan = clocked; - + function init(obj, ~) + if obj.enable + fprintf(1, 'initializing %s\n', obj.toStr); + + obj.session = daq.createSession(obj.daqVendor); + obj.session.IsContinuous = true; + clocked = obj.session.addCounterOutputChannel(obj.daqDeviceID, obj.daqChannelID, 'PulseGeneration'); + clocked.Frequency = obj.frequency; + clocked.DutyCycle = obj.dutyCycle; + clocked.InitialDelay = obj.initialDelay; + obj.clockChan = clocked; + end end - function onStart(obj, ~) - fprintf(1, 'start Clock\n'); - - startBackground(obj.session); + function start(obj, ~) + if obj.enable + if obj.verbose + fprintf(1, 'start %s\n', obj.name); + end + startBackground(obj.session); + end end - function onProcess(~, ~, ~) - fprintf(1, 'process Clock\n'); - + function process(~, ~, ~) + %fprintf(1, 'process Clock\n'); + % -- pass end - function onStop(obj,~) - fprintf(1, 'stop Clock\n'); - - stop(obj.session); - release(obj.session); - obj.session = []; + function stop(obj,~) + if obj.enable + if obj.verbose + fprintf(1, 'stop %s\n', obj.name); + end + + stop(obj.session); + release(obj.session); + obj.session = []; + end end + function s = toStr(obj) + s = sprintf('"%s" on %s/%s (clock, %dHz, %.2f duty cycle)', obj.name, ... + obj.daqDeviceID, obj.daqChannelID, obj.frequency, obj.dutyCycle); + end end end diff --git a/+hw/tlOutputStartStopSync.m b/+hw/tlOutputStartStopSync.m index 577b942e..3222ab46 100644 --- a/+hw/tlOutputStartStopSync.m +++ b/+hw/tlOutputStartStopSync.m @@ -21,42 +21,53 @@ obj.daqChannelID = daqChannelID; end - function onInit(obj, ~) - fprintf(1, 'initialize StartStopSync\n'); - obj.session = daq.createSession(obj.daqVendor); - obj.session.addDigitalChannel(obj.daqDeviceID, obj.daqChannelID, 'OutputOnly'); - outputSingleScan(obj.session, false); % ensure that it starts down - % by the way, if you use this to control a light for - % synchronization, note that you can configure in nidaqMX a - % "default" value for the channel, so for example it will stay - % "false" at all times even if the computer reboots. + function init(obj, ~) + if obj.enable + fprintf(1, 'initializing %s\n', obj.toStr); + obj.session = daq.createSession(obj.daqVendor); + obj.session.addDigitalChannel(obj.daqDeviceID, obj.daqChannelID, 'OutputOnly'); + outputSingleScan(obj.session, false); % ensure that it starts down + % by the way, if you use this to control a light for + % synchronization, note that you can configure in nidaqMX a + % "default" value for the channel, so for example it will stay + % "false" at all times even if the computer reboots. + end end - function onStart(obj, ~) - fprintf(1, 'start StartStopSync\n'); - - pause(obj.initialDelay); - outputSingleScan(obj.session, true); - pause(obj.pulseDuration); - outputSingleScan(obj.session, false); - + function start(obj, ~) + if obj.enable + if obj.verbose + fprintf(1, 'start %s\n', obj.name); + end + pause(obj.initialDelay); + outputSingleScan(obj.session, true); + pause(obj.pulseDuration); + outputSingleScan(obj.session, false); + end end - function onProcess(~, ~, ~) - fprintf(1, 'process StartStopSync\n'); - + function process(~, ~, ~) + %fprintf(1, 'process StartStopSync\n'); + % -- pass end - function onStop(obj,~) - fprintf(1, 'stop StartStopSync\n'); - - outputSingleScan(obj.session, true); - pause(obj.pulseDuration); - outputSingleScan(obj.session, false); - - stop(obj.session); - release(obj.session); - obj.session = []; + function stop(obj,~) + if obj.enable + fprintf(1, 'stop %s\n', obj.name); + + outputSingleScan(obj.session, true); + pause(obj.pulseDuration); + outputSingleScan(obj.session, false); + + stop(obj.session); + release(obj.session); + obj.session = []; + end + end + + function s = toStr(obj) + s = sprintf('"%s" on %s/%s (StartStopSync, pulse duration %.2f)', obj.name, ... + obj.daqDeviceID, obj.daqChannelID, obj.pulseDuration); end end From 8b36d7cf5bcabbd2f6e820a75a8a0f448bca3dd5 Mon Sep 17 00:00:00 2001 From: nsteinme <nick.steinmetz@gmail.com> Date: Fri, 26 Jan 2018 13:44:55 +0000 Subject: [PATCH 040/393] add saving out Outputs as struct --- +hw/Timeline.m | 17 +++++++++++++---- +hw/tlOutputChrono.m | 15 ++++++++++----- +hw/tlOutputStartStopSync.m | 4 +++- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/+hw/Timeline.m b/+hw/Timeline.m index 2318ae54..25c0781f 100644 --- a/+hw/Timeline.m +++ b/+hw/Timeline.m @@ -70,7 +70,8 @@ DaqSampleRate = 1000 % rate at which daq aquires data in Hz, see Rate DaqSamplesPerNotify % determines the number of data samples to be processed each time, see Timeline.process(), constructor and NotifyWhenDataAvailableExceeds - Outputs % array of output classes, defining any signals you desire to be sent from the daq. + Outputs = hw.tlOutputChrono('chrono', 'Dev1', 'port0/line1') + % array of output classes, defining any signals you desire to be sent from the daq. % see hw.tlOutput, and, e.g. hw.tlOutputClock. Inputs = struct('name', 'chrono',... @@ -79,6 +80,7 @@ 'measurement', 'Voltage',... 'terminalConfig', 'SingleEnded') UseInputs = {'chrono'} % array of inputs to record while tl is running + StopDelay = 2 % currently pauses for at least 2 secs as 'hack' before stopping main DAQ session MaxExpectedDuration = 2*60*60 % expected experiment time so data structure is initialised to sensible size (in secs) AquiredDataType = 'double' % default data type for the acquired data array (i.e. Data.rawDAQData) @@ -86,8 +88,11 @@ LivePlot = false % if true the data are plotted as the data are aquired LivePlotParams = []; WriteBufferToDisk = false % if true the data buffer is written to disk as they're aquired NB: in the future this will happen by default - - % moved these here so chrono class can access - NS + + end + + properties (Transient) + % moved these here (i.e. unprotected) so chrono class can access - NS CurrSysTimeTimelineOffset % difference between the system time when the last chrono flip occured and the timestamp recorded by the DAQ, see tl.process() LastTimestamp % the last timestamp returned from the daq during the DataAvailable event. Used to check sampling continuity, see tl.process() LastClockSentSysTime % the mean of the system time before and after the last chrono flip. Used to calculate CurrSysTimeTimelineOffset, see tl.process() @@ -114,7 +119,6 @@ % Adds chrono, aquireLive and clock to the outputs list, % along with default ports and delays obj.DaqSamplesPerNotify = 1/obj.SamplingInterval; % calculate DaqSamplesPerNotify - obj.Outputs = hw.tlOutputChrono('chrono', obj.DaqIds, 'port0/line1'); if nargin % if old tl hardware struct provided, use these to populate properties obj.Inputs = hw.inputs; obj.DaqVendor = hw.daqVendor; @@ -122,6 +126,7 @@ obj.DaqSampleRate = hw.daqSampleRate; obj.DaqSamplesPerNotify = hw.daqSamplesPerNotify; end + end function start(obj, expRef, Alyx) @@ -417,6 +422,10 @@ function stop(obj) obj.Data.lastClockSentSysTime = obj.LastClockSentSysTime; obj.Data.currSysTimeTimelineOffset = obj.CurrSysTimeTimelineOffset; + for outIdx = 1:numel(obj.Outputs) + obj.Data.hw.Outputs{outIdx} = struct(obj.Outputs(outIdx)); + end + % save tl to all paths superSave(obj.Data.savePaths, struct('Timeline', obj.Data)); diff --git a/+hw/tlOutputChrono.m b/+hw/tlOutputChrono.m index ad16ff3c..766a6f39 100644 --- a/+hw/tlOutputChrono.m +++ b/+hw/tlOutputChrono.m @@ -39,13 +39,18 @@ function init(obj, timeline) end end - function start(~, ~) - %fprintf(1, 'start chrono\n'); - % -- pass + function start(obj, ~) + if obj.enable + if obj.verbose + fprintf(1, 'start %s\n', obj.name); + end + + outputSingleScan(obj.session, false) % this will be the clocking pulse detected the first time process is called + end end function process(obj, timeline, event) - if obj.enable + if obj.enable && timeline.IsRunning && ~isempty(obj.session) if obj.verbose fprintf(1, 'process %s\n', obj.name); end @@ -82,7 +87,7 @@ function process(obj, timeline, event) end function stop(obj,~) - if enable + if obj.enable if obj.verbose fprintf(1, 'stop %s\n', obj.name); end diff --git a/+hw/tlOutputStartStopSync.m b/+hw/tlOutputStartStopSync.m index 3222ab46..15b975ff 100644 --- a/+hw/tlOutputStartStopSync.m +++ b/+hw/tlOutputStartStopSync.m @@ -53,7 +53,9 @@ function process(~, ~, ~) function stop(obj,~) if obj.enable - fprintf(1, 'stop %s\n', obj.name); + if obj.verbose + fprintf(1, 'stop %s\n', obj.name); + end outputSingleScan(obj.session, true); pause(obj.pulseDuration); From dbbcd8a7207ddeb0317f8728bde11f12efd4c299 Mon Sep 17 00:00:00 2001 From: ArminLak <arminlak@gmail.com> Date: Mon, 29 Jan 2018 11:46:47 +0000 Subject: [PATCH 041/393] Updated paths server 1 now 'zubjects' --- +dat/paths.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/+dat/paths.m b/+dat/paths.m index 9d69d38e..94c564f8 100644 --- a/+dat/paths.m +++ b/+dat/paths.m @@ -13,7 +13,7 @@ rig = thishost; end -server1Name = '\\zserver.cortexlab.net'; +server1Name = '\\zubjects.cortexlab.net'; % server2Name = '\\zserver2.cortexlab.net'; % server3Name = '\\zserver3.cortexlab.net'; % 2017-02-18 MW - Currently % unused by Rigbox @@ -29,7 +29,7 @@ p.localRepository = 'C:\LocalExpData'; % for all data types, under the new system of having data grouped by mouse % rather than data type -p.mainRepository = fullfile(server1Name, 'Data2', 'Subjects'); +p.mainRepository = fullfile(server1Name, 'Subjects'); % Repository for info about experiments, i.e. stimulus, behavioural, % Timeline etc p.expInfoRepository = p.mainRepository; From bed3a79eb67edc4836687fdfbc09001f1ef2322e Mon Sep 17 00:00:00 2001 From: ArminLak <arminlak@gmail.com> Date: Mon, 29 Jan 2018 11:49:36 +0000 Subject: [PATCH 042/393] Changed dat.paths server 1 name now 'zubjects' --- +dat/paths.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/+dat/paths.m b/+dat/paths.m index 222cbf5e..a9bb73fe 100644 --- a/+dat/paths.m +++ b/+dat/paths.m @@ -13,7 +13,7 @@ rig = thishost; end -server1Name = '\\zserver.cortexlab.net'; +server1Name = '\\zubjects.cortexlab.net'; % server2Name = '\\zserver2.cortexlab.net'; % server3Name = '\\zserver3.cortexlab.net'; % 2017-02-18 MW - Currently % unused by Rigbox @@ -27,7 +27,7 @@ p.localRepository = 'C:\LocalExpData'; % for all data types, under the new system of having data grouped by mouse % rather than data type -p.mainRepository = fullfile(server1Name, 'Data2', 'Subjects'); +p.mainRepository = fullfile(server1Name, 'Subjects'); % Repository for info about experiments, i.e. stimulus, behavioural, % Timeline etc p.expInfoRepository = p.mainRepository; From 64dc8d462a070c917d8be4e9ef8659ed832a388f Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Mon, 29 Jan 2018 11:59:14 +0000 Subject: [PATCH 043/393] new paths --- +dat/paths.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/+dat/paths.m b/+dat/paths.m index 9d69d38e..94c564f8 100644 --- a/+dat/paths.m +++ b/+dat/paths.m @@ -13,7 +13,7 @@ rig = thishost; end -server1Name = '\\zserver.cortexlab.net'; +server1Name = '\\zubjects.cortexlab.net'; % server2Name = '\\zserver2.cortexlab.net'; % server3Name = '\\zserver3.cortexlab.net'; % 2017-02-18 MW - Currently % unused by Rigbox @@ -29,7 +29,7 @@ p.localRepository = 'C:\LocalExpData'; % for all data types, under the new system of having data grouped by mouse % rather than data type -p.mainRepository = fullfile(server1Name, 'Data2', 'Subjects'); +p.mainRepository = fullfile(server1Name, 'Subjects'); % Repository for info about experiments, i.e. stimulus, behavioural, % Timeline etc p.expInfoRepository = p.mainRepository; From a9afa12ee75244fce0cb98094985809b5ed02360 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Mon, 29 Jan 2018 12:51:56 +0000 Subject: [PATCH 044/393] fix timeline synchronization with expServer --- +hw/Timeline.m | 8 ++++---- +hw/tlOutputChrono.m | 17 +++++++++++++++-- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/+hw/Timeline.m b/+hw/Timeline.m index 25c0781f..de73332b 100644 --- a/+hw/Timeline.m +++ b/+hw/Timeline.m @@ -93,7 +93,7 @@ properties (Transient) % moved these here (i.e. unprotected) so chrono class can access - NS - CurrSysTimeTimelineOffset % difference between the system time when the last chrono flip occured and the timestamp recorded by the DAQ, see tl.process() + CurrSysTimeTimelineOffset = 0 % difference between the system time when the last chrono flip occured and the timestamp recorded by the DAQ, see tl.process() LastTimestamp % the last timestamp returned from the daq during the DataAvailable event. Used to check sampling continuity, see tl.process() LastClockSentSysTime % the mean of the system time before and after the last chrono flip. Used to calculate CurrSysTimeTimelineOffset, see tl.process() end @@ -422,9 +422,9 @@ function stop(obj) obj.Data.lastClockSentSysTime = obj.LastClockSentSysTime; obj.Data.currSysTimeTimelineOffset = obj.CurrSysTimeTimelineOffset; - for outIdx = 1:numel(obj.Outputs) - obj.Data.hw.Outputs{outIdx} = struct(obj.Outputs(outIdx)); - end +% for outIdx = 1:numel(obj.Outputs) +% obj.Data.hw.Outputs{outIdx} = struct(obj.Outputs(outIdx)); +% end % save tl to all paths superSave(obj.Data.savePaths, struct('Timeline', obj.Data)); diff --git a/+hw/tlOutputChrono.m b/+hw/tlOutputChrono.m index 766a6f39..e98d74a0 100644 --- a/+hw/tlOutputChrono.m +++ b/+hw/tlOutputChrono.m @@ -36,16 +36,19 @@ function init(obj, timeline) x2 = tls.inputSingleScan; assert(x1(timeline.Inputs(idx).arrayColumn) < 2.5 && x2(timeline.Inputs(idx).arrayColumn) > 2.5,... 'The clocking pulse test could not be read back'); + + timeline.CurrSysTimeTimelineOffset = GetSecs; end end - function start(obj, ~) + function start(obj, timeline) if obj.enable if obj.verbose fprintf(1, 'start %s\n', obj.name); end - + t = GetSecs; % system time before output outputSingleScan(obj.session, false) % this will be the clocking pulse detected the first time process is called + timeline.LastClockSentSysTime = (t + GetSecs)/2; end end @@ -70,6 +73,11 @@ function process(obj, timeline, event) idx = elementByName(timeline.Inputs, 'chrono'); clockChangeIdx = find(sign(event.Data(:,timeline.Inputs(idx).arrayColumn) - 2.5) == obj.NextChronoSign, 1); + if obj.verbose + fprintf(1, ' CurrOffset=%.2f, LastClock=%.2f\n', ... + timeline.CurrSysTimeTimelineOffset, timeline.LastClockSentSysTime); + end + %Ensure the clocking pulse was detected if ~isempty(clockChangeIdx) clockChangeTimestamp = event.TimeStamps(clockChangeIdx); @@ -83,6 +91,11 @@ function process(obj, timeline, event) t = GetSecs; % system time before output outputSingleScan(obj.session, obj.NextChronoSign > 0); % send next chrono flip timeline.LastClockSentSysTime = (t + GetSecs)/2; % record mean before/after system time + if obj.verbose + fprintf(1, ' CurrOffset=%.2f, LastClock=%.2f\n', ... + timeline.CurrSysTimeTimelineOffset, timeline.LastClockSentSysTime); + end + end end From 405f2ba10bddff4b6de321a067e5cabe061eea24 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Mon, 29 Jan 2018 13:32:33 +0000 Subject: [PATCH 045/393] Fix'd MPEP UDP Listeners error --- cortexlab/+io/MpepUDPDataHosts.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cortexlab/+io/MpepUDPDataHosts.m b/cortexlab/+io/MpepUDPDataHosts.m index 821b215f..0c239943 100644 --- a/cortexlab/+io/MpepUDPDataHosts.m +++ b/cortexlab/+io/MpepUDPDataHosts.m @@ -204,12 +204,12 @@ function expEnded(obj) function start(obj, ref) [expRef, ai] = dat.parseAlyxInstance(ref); obj.AlyxInstance = ai; - [subject, seriesNum, expNum] = dat.expRefToMpep(obj.ExpRef); + [subject, seriesNum, expNum] = dat.expRefToMpep(expRef); alyxmsg = sprintf('alyx %s %d %d %s', subject, seriesNum, expNum, ref); confirmedBroadcast(obj, alyxmsg); + % equivalent to startExp(expRef) expStarted(obj, expRef); - end function stop(obj) From de92c0cac77b0dd75c2f7c2797ddbbf010288a61 Mon Sep 17 00:00:00 2001 From: nsteinme <nick.steinmetz@gmail.com> Date: Mon, 29 Jan 2018 14:06:53 +0000 Subject: [PATCH 046/393] add lots of comments --- +hw/Timeline.m | 7 ++++++- +hw/tlOutput.m | 14 +++++++------- +hw/tlOutputAcqLive.m | 12 ++++++++---- +hw/tlOutputChrono.m | 14 ++++++++++---- +hw/tlOutputClock.m | 10 +++++++--- +hw/tlOutputStartStopSync.m | 8 ++++++-- 6 files changed, 44 insertions(+), 21 deletions(-) diff --git a/+hw/Timeline.m b/+hw/Timeline.m index ed705fea..d259c25c 100644 --- a/+hw/Timeline.m +++ b/+hw/Timeline.m @@ -182,6 +182,7 @@ function start(obj, expRef, Alyx) pause(5e-3); end + % Start each output for outidx = 1:numel(obj.Outputs) obj.Outputs(outidx).start(obj); end @@ -389,7 +390,8 @@ function stop(obj) inputsIdx = cellfun(@(x)find(strcmp({obj.Inputs.name}, x),1), obj.UseInputs); % this block finds the daqChannelID for chrono and acqLive if - % they exist + % they exist, plus some clock parameters - all for legacy + % metadata saving, see below outputClasses = arrayfun(@class, obj.Outputs, 'uni', false); chronoChan = []; nextChrono = []; acqLiveChan = []; useClock = false; clockF = []; clockD = []; chronoOutputIdx = find(strcmp(outputClasses, 'hw.tlOutputChrono'),1); @@ -408,6 +410,7 @@ function stop(obj) clockD = obj.Outputs(clockOutputIdx).dutyCycle; end + % legacy metadata obj.Data.hw = struct('daqVendor', obj.DaqVendor, 'daqDevice', obj.DaqIds,... 'daqSampleRate', obj.DaqSampleRate, 'daqSamplesPerNotify', obj.DaqSamplesPerNotify,... 'chronoOutDaqChannelID', chronoChan, 'acqLiveOutDaqChannelID', acqLiveChan,... @@ -422,6 +425,7 @@ function stop(obj) obj.Data.lastClockSentSysTime = obj.LastClockSentSysTime; obj.Data.currSysTimeTimelineOffset = obj.CurrSysTimeTimelineOffset; + % saving hardware metadata for each output % for outIdx = 1:numel(obj.Outputs) % obj.Data.hw.Outputs{outIdx} = struct(obj.Outputs(outIdx)); % end @@ -547,6 +551,7 @@ function process(obj, ~, event) 'Discontinuity of DAQ acquistion detected: last timestamp was %f and this one is %f',... obj.LastTimestamp, event.TimeStamps(1)); + % process methods for outputs for outidx = 1:numel(obj.Outputs) obj.Outputs(outidx).process(obj, event); end diff --git a/+hw/tlOutput.m b/+hw/tlOutput.m index e178fd9d..697b2041 100644 --- a/+hw/tlOutput.m +++ b/+hw/tlOutput.m @@ -4,13 +4,13 @@ % % Below is a list of some subclasses and their functions: % hw.tlOutputClock - clocked output on a counter channel - % hw.tlOutputChrono - the default, flip/flip status check output + % hw.tlOutputChrono - the default, flip/flop status check output % hw.tlOutputAcqLive - a digital channel that is on for the duration % of the recording % hw.tlOutputStartStopSync - a digital channel that turns on only at % the beginning and end of the recording % - % The timeline object will call the onInit, onStart, onProcess, and onStop + % The timeline object will call the init, start, process, and stop % methods. % % Part of Rigbox @@ -18,7 +18,7 @@ % 2018-01 NS created properties - name + name % user choice, text enable = true % will not do anything with it unless this is true verbose = false % output status updates. Initialization message outputs regardless of verbose. end @@ -28,10 +28,10 @@ end methods (Abstract) - init(obj, timeline) - start(obj, timeline) - process(obj, timeline, event) - stop(obj, timeline) + init(obj, timeline) % called when timeline is initialized (see hw.Timeline/init), e.g. to open daq session and set parameters + start(obj, timeline) % called when timeline is started (see hw.Timeline/start), e.g. to start outputs + process(obj, timeline, event) % called every time Timeline processes a chunk of data, in case output needs to react to it + stop(obj, timeline) % called when timeline is stopped (see hw.Timeline/stop), to close and clean up s = toStr(obj) % a string that describes the object succintly end diff --git a/+hw/tlOutputAcqLive.m b/+hw/tlOutputAcqLive.m index a8fae92a..089e1710 100644 --- a/+hw/tlOutputAcqLive.m +++ b/+hw/tlOutputAcqLive.m @@ -10,7 +10,7 @@ daqDeviceID daqChannelID daqVendor = 'ni' - initialDelay = 0 + initialDelay = 0 % sec, time to wait before starting end methods @@ -21,31 +21,35 @@ end function init(obj, ~) + % called when timeline is initialized (see hw.Timeline/init) if obj.enable fprintf(1, 'initializing %s\n', obj.toStr); obj.session = daq.createSession(obj.daqVendor); obj.session.addDigitalChannel(obj.daqDeviceID, obj.daqChannelID, 'OutputOnly'); - outputSingleScan(obj.session, false); + outputSingleScan(obj.session, false); % start in the off/false state end end function start(obj, ~) + % called when timeline is started (see hw.Timeline/start) if obj.enable if obj.verbose fprintf(1, 'start %s\n', obj.name); end - pause(obj.initialDelay); - outputSingleScan(obj.session, true); + pause(obj.initialDelay); % wait for some duration before starting + outputSingleScan(obj.session, true); % set digital output true: acquisition is "live" end end function process(~, ~, ~) + % called every time Timeline processes a chunk of data %fprintf(1, 'process acqLive\n'); % -- pass end function stop(obj,~) + % called when timeline is stopped (see hw.Timeline/stop) if obj.enable if obj.verbose fprintf(1, 'stop %s\n', obj.name); diff --git a/+hw/tlOutputChrono.m b/+hw/tlOutputChrono.m index e98d74a0..f0c9f230 100644 --- a/+hw/tlOutputChrono.m +++ b/+hw/tlOutputChrono.m @@ -1,6 +1,8 @@ classdef tlOutputChrono < hw.tlOutput %hw.tlOutputChrono Timeline uses this to monitor that - % acquisition is proceeding normally during a recording. + % acquisition is proceeding normally during a recording and to update + % the synchronization between the system time and the timeline time (to + % prevent drift between daq and computer clock). % See also hw.tlOutput and hw.Timeline % % Part of Rigbox @@ -21,6 +23,7 @@ end function init(obj, timeline) + % called when timeline is initialized (see hw.Timeline/init) if obj.enable fprintf(1, 'initializing %s\n', obj.toStr); obj.session = daq.createSession(obj.daqVendor); @@ -37,22 +40,24 @@ function init(obj, timeline) assert(x1(timeline.Inputs(idx).arrayColumn) < 2.5 && x2(timeline.Inputs(idx).arrayColumn) > 2.5,... 'The clocking pulse test could not be read back'); - timeline.CurrSysTimeTimelineOffset = GetSecs; + timeline.CurrSysTimeTimelineOffset = GetSecs; % to initialize this, will be a bit off but fixed after the first pulse end end - function start(obj, timeline) + function start(obj, timeline) + % called when timeline is started (see hw.Timeline/start) if obj.enable if obj.verbose fprintf(1, 'start %s\n', obj.name); end t = GetSecs; % system time before output outputSingleScan(obj.session, false) % this will be the clocking pulse detected the first time process is called - timeline.LastClockSentSysTime = (t + GetSecs)/2; + timeline.LastClockSentSysTime = (t + GetSecs)/2; end end function process(obj, timeline, event) + % called every time Timeline processes a chunk of data if obj.enable && timeline.IsRunning && ~isempty(obj.session) if obj.verbose fprintf(1, 'process %s\n', obj.name); @@ -100,6 +105,7 @@ function process(obj, timeline, event) end function stop(obj,~) + % called when timeline is stopped (see hw.Timeline/stop) if obj.enable if obj.verbose fprintf(1, 'stop %s\n', obj.name); diff --git a/+hw/tlOutputClock.m b/+hw/tlOutputClock.m index f5e206fb..6c26685b 100644 --- a/+hw/tlOutputClock.m +++ b/+hw/tlOutputClock.m @@ -10,9 +10,9 @@ daqDeviceID daqChannelID daqVendor = 'ni' - initialDelay = 0 - frequency = 60; - dutyCycle = 0.2; + initialDelay = 0 % delay from session start to clock output + frequency = 60; % Hz, of the clocking pulse + dutyCycle = 0.2; % proportion of each cycle that the pulse is "true" end properties (Transient) @@ -27,6 +27,7 @@ end function init(obj, ~) + % called when timeline is initialized (see hw.Timeline/init) if obj.enable fprintf(1, 'initializing %s\n', obj.toStr); @@ -41,6 +42,7 @@ function init(obj, ~) end function start(obj, ~) + % called when timeline is started (see hw.Timeline/start) if obj.enable if obj.verbose fprintf(1, 'start %s\n', obj.name); @@ -50,11 +52,13 @@ function start(obj, ~) end function process(~, ~, ~) + % called every time Timeline processes a chunk of data %fprintf(1, 'process Clock\n'); % -- pass end function stop(obj,~) + % called when timeline is stopped (see hw.Timeline/stop) if obj.enable if obj.verbose fprintf(1, 'stop %s\n', obj.name); diff --git a/+hw/tlOutputStartStopSync.m b/+hw/tlOutputStartStopSync.m index 15b975ff..cd47c20a 100644 --- a/+hw/tlOutputStartStopSync.m +++ b/+hw/tlOutputStartStopSync.m @@ -10,8 +10,8 @@ daqDeviceID daqChannelID daqVendor = 'ni' - initialDelay = 0 - pulseDuration = 0.2; + initialDelay = 0 % sec, time between start of acquisition and onset of this pulse + pulseDuration = 0.2; % sec, time that the pulse is on at beginning and end end methods @@ -22,6 +22,7 @@ end function init(obj, ~) + % called when timeline is initialized (see hw.Timeline/init) if obj.enable fprintf(1, 'initializing %s\n', obj.toStr); obj.session = daq.createSession(obj.daqVendor); @@ -35,6 +36,7 @@ function init(obj, ~) end function start(obj, ~) + % called when timeline is started (see hw.Timeline/start) if obj.enable if obj.verbose fprintf(1, 'start %s\n', obj.name); @@ -47,11 +49,13 @@ function start(obj, ~) end function process(~, ~, ~) + % called every time Timeline processes a chunk of data %fprintf(1, 'process StartStopSync\n'); % -- pass end function stop(obj,~) + % called when timeline is stopped (see hw.Timeline/stop) if obj.enable if obj.verbose fprintf(1, 'stop %s\n', obj.name); From ee0b8b23263d6e9e844a269d1efa0bc9da5945a7 Mon Sep 17 00:00:00 2001 From: nsteinme <nick.steinmetz@gmail.com> Date: Mon, 29 Jan 2018 16:05:02 +0000 Subject: [PATCH 047/393] add saving of metadata for Output channels in Timeline into json --- +hw/Timeline.m | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/+hw/Timeline.m b/+hw/Timeline.m index d259c25c..1ec8a36d 100644 --- a/+hw/Timeline.m +++ b/+hw/Timeline.m @@ -426,9 +426,13 @@ function stop(obj) obj.Data.currSysTimeTimelineOffset = obj.CurrSysTimeTimelineOffset; % saving hardware metadata for each output -% for outIdx = 1:numel(obj.Outputs) -% obj.Data.hw.Outputs{outIdx} = struct(obj.Outputs(outIdx)); -% end + warning('off', 'MATLAB:structOnObject'); % sorry, don't care + for outIdx = 1:numel(obj.Outputs) + s = struct(obj.Outputs(outIdx)); + s.class = class(obj.Outputs(outIdx)); + obj.Data.hw.Outputs{outIdx} = s; + end + warning('on', 'MATLAB:structOnObject'); % save tl to all paths superSave(obj.Data.savePaths, struct('Timeline', obj.Data)); @@ -438,7 +442,7 @@ function stop(obj) % save local copy savejson('hw', obj.Data.hw, fullfile(fileparts(obj.Data.savePaths{1}), 'TimelineHW.json')); % save server copy - savejson('hw', obj.Data.hw, fullfile(fileparts(obj.Data.savePaths{1}), 'TimelineHW.json')); + savejson('hw', obj.Data.hw, fullfile(fileparts(obj.Data.savePaths{2}), 'TimelineHW.json')); else warning('JSONlab not found - hardware information not saved to ALF') end From 7d074d531a366868d560f42d26a7540d04ef546c Mon Sep 17 00:00:00 2001 From: nsteinme <nick.steinmetz@gmail.com> Date: Mon, 29 Jan 2018 16:16:27 +0000 Subject: [PATCH 048/393] update TODO re: timeline alyx --- +hw/Timeline.m | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/+hw/Timeline.m b/+hw/Timeline.m index 1ec8a36d..a836df0c 100644 --- a/+hw/Timeline.m +++ b/+hw/Timeline.m @@ -465,8 +465,7 @@ function stop(obj) warning('couldnt register files to alyx'); end end - %TODO: Register ALF components to alyx, instead of the main - %Timeline.mat file + %TODO: Register ALF components to alyx, incl TimelineHW.json % delete data from memory, tl is now officially no longer running obj.Data = []; From 783247ca56ce44abe44cafcebf57cc3a4729a9be Mon Sep 17 00:00:00 2001 From: nsteinme <nick.steinmetz@gmail.com> Date: Mon, 29 Jan 2018 16:27:52 +0000 Subject: [PATCH 049/393] update file format when registering timeline file to alyx --- +hw/Timeline.m | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/+hw/Timeline.m b/+hw/Timeline.m index 1ec8a36d..a2414421 100644 --- a/+hw/Timeline.m +++ b/+hw/Timeline.m @@ -453,16 +453,18 @@ function stop(obj) if ~isempty(which('alf.timelineToALF'))&&~isempty(which('writeNPY')) alf.timelineToALF(obj.Data, [],... fileparts(dat.expFilePath(obj.Data.expRef, 'timeline', 'master'))) + else + warning('did not write files into alf format. Check that alyx-matlab and npy-matlab repositories are in path'); end % register Timeline.mat file to Alyx database [subject,~,~] = dat.parseExpRef(obj.Data.expRef); if ~isempty(obj.AlyxInstance) && ~strcmp(subject,'default') try - alyx.registerFile(obj.Data.savePaths{end}, 'alf',... + alyx.registerFile(obj.Data.savePaths{end}, 'mat',... obj.AlyxInstance.subsessionURL, 'Timeline', [], obj.AlyxInstance); catch - warning('couldnt register files to alyx'); + warning('couldn''t register files to alyx'); end end %TODO: Register ALF components to alyx, instead of the main From dad17bffb0d15553aa1fbf4b674bed0a107b2ac7 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Mon, 29 Jan 2018 17:23:43 +0000 Subject: [PATCH 050/393] Fix for water calibrations reverted change Nick had previously made in hw.PulseSwitcher where only the first index of input array was used. --- +hw/+ptb/Window.m | 7 ++-- +hw/PulseSwitcher.m | 2 +- cortexlab/+tl/bindMpepServer.m | 60 +++++++++++++++++++++++++++++----- 3 files changed, 56 insertions(+), 13 deletions(-) diff --git a/+hw/+ptb/Window.m b/+hw/+ptb/Window.m index 294d2628..8cab6568 100644 --- a/+hw/+ptb/Window.m +++ b/+hw/+ptb/Window.m @@ -571,8 +571,7 @@ function applyCalibration(obj, cal) list = find(diff(thisTable) <= 0, 1); if ~isempty(list) - announce = sprintf('Gamma table %d NOT MONOTONIC. We are adjusting.',igun); - disp(announce) + fprintf('Gamma table %d NOT MONOTONIC. We are adjusting.',igun); % We assume that the non-monotonic points only differ due to noise % and so we can resort them without any consequences @@ -606,12 +605,12 @@ function applyCalibration(obj, cal) interp1(monTable,posLocs-1,(0:(numEntries-1))/(numEntries-1))'; end - if any(isnan(c.monitorGamInv)), + if any(isnan(c.monitorGamInv)) msgbox('Warning: NaNs in inverse gamma table -- may need to recalibrate.'); end end - function storeDaqData(obj, src, event) + function storeDaqData(obj, ~, event) n = length(event.TimeStamps); ii = obj.DaqData.nSamples+(1:n); obj.DaqData.timeStamps(ii) = event.TimeStamps; diff --git a/+hw/PulseSwitcher.m b/+hw/PulseSwitcher.m index 4321bf60..e5b0e5f3 100644 --- a/+hw/PulseSwitcher.m +++ b/+hw/PulseSwitcher.m @@ -21,7 +21,7 @@ end function samples = waveform(obj, sampleRate, command) - [dt, npulses, f] = obj.ParamsFun(command(1)); + [dt, npulses, f] = obj.ParamsFun(command); wavelength = 1/f; duty = dt/wavelength; assert(duty <= (1 + 1e-3), 'Pulse width larger than wavelength (duty=%.2f)', duty); diff --git a/cortexlab/+tl/bindMpepServer.m b/cortexlab/+tl/bindMpepServer.m index 3ddd033e..37e67188 100644 --- a/cortexlab/+tl/bindMpepServer.m +++ b/cortexlab/+tl/bindMpepServer.m @@ -19,8 +19,8 @@ end mpepSendPort = 1103; % send responses back to this remote port - quitKey = KbName('esc'); +manualStartKey = KbName('m'); %% Start UDP communication listeners = struct(... @@ -36,6 +36,13 @@ tls.close = @closeConns; tls.process = @process; tls.listen = @listen; +tls.AlyxInstance = []; + + +%% Initialize timeline +rig = hw.devices([], false); +tlObj = rig.timeline; +tls.tlObj = tlObj; %% Helper functions @@ -52,10 +59,10 @@ function process() function processListener(listener) sz = pnet(listener.socket, 'readpacket', 1000, 'noblock'); if sz > 0 - t = tl.time(false); % save the time we got the UDP packet + t = tlObj.time(false); % save the time we got the UDP packet msg = pnet(listener.socket, 'read'); - if tl.running - tl.record([listener.name 'UDP'], msg, t); % record the UDP event in Timeline + if tlObj.IsRunning + tlObj.record([listener.name 'UDP'], msg, t); % record the UDP event in Timeline end listener.callback(listener, msg); % call special handling function end @@ -71,23 +78,28 @@ function processMpep(listener, msg) failed = false; % flag for preventing UDP echo %% Experiment-level events start/stop timeline switch lower(info.instruction) + case 'alyx' + fprintf(1, 'received alyx token message\n'); + idx = find(msg==' ', 1, 'last'); + [~, ai] = dat.parseAlyxInstance(msg(idx+1:end)); + tls.AlyxInstance = ai; case 'expstart' % create a file path & experiment ref based on experiment info try % start Timeline - tl.start(info.expRef); + tlObj.start(info.expRef, tls.AlyxInstance); % re-record the UDP event in Timeline since it wasn't started % when we tried earlier. Treat it as having arrived at time zero. - tl.record('mpepUDP', msg, 0); + tlObj.record('mpepUDP', msg, 0); catch ex % flag up failure so we do not echo the UDP message back below failed = true; disp(getReport(ex)); end case 'expend' - tl.stop(); % stop Timeline + tlObj.stop(); % stop Timeline case 'expinterrupt' - tl.stop(); % stop Timeline + tlObj.stop(); % stop Timeline end if ~failed %% echo the UDP message back to the sender @@ -106,6 +118,7 @@ function listen() % listen to keyboard events KbQueueCreate(); KbQueueStart(); + newExpRef = []; cleanup1 = onCleanup(@KbQueueRelease); log('Polling for UDP messages. PRESS <%s> TO QUIT', KbName(quitKey)); running = true; @@ -116,6 +129,37 @@ function listen() if firstPress(quitKey) running = false; end + if firstPress(manualStartKey) && ~tlObj.IsRunning + + if isempty(tls.AlyxInstance) + % first get an alyx instance + ai = alyx.loginWindow(); + else + ai = tls.AlyxInstance; + end + + [mouseName, ~] = dat.subjectSelector([],ai); + + if ~isempty(mouseName) + clear expParams; + expParams.experimentType = 'timelineManualStart'; + [newExpRef, ~, subsessionURL] = dat.newExp(mouseName, now, expParams, ai); + ai.subsessionURL = subsessionURL; + tls.AlyxInstance = ai; + + %[subjectRef, expDate, expSequence] = dat.parseExpRef(newExpRef); + %newExpRef = dat.constructExpRef(mouseName, now, expNum); + communicator.send('AlyxSend', {tls.AlyxInstance}); + communicator.send('status', { 'starting', newExpRef}); + tlObj.start(newExpRef, ai); + end + KbQueueFlush; + elseif firstPress(manualStartKey) && tlObj.IsRunning && ~isempty(newExpRef) + fprintf(1, 'stopping timeline\n'); + tlObj.stop(); + communicator.send('status', { 'completed', newExpRef}); + newExpRef = []; + end if toc(tid) > 0.2 pause(1e-3); % allow timeline aquisition every so often tid = tic; From b63ec9fa6bb3ca22dad8c636a779a85672d5ae38 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 30 Jan 2018 18:48:32 +0000 Subject: [PATCH 051/393] bindMpepServer echo port bindMpepServer now echos messages to port of origin --- cortexlab/+tl/bindMpepServer.m | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/cortexlab/+tl/bindMpepServer.m b/cortexlab/+tl/bindMpepServer.m index 37e67188..ccbc953f 100644 --- a/cortexlab/+tl/bindMpepServer.m +++ b/cortexlab/+tl/bindMpepServer.m @@ -18,9 +18,9 @@ mpepListenPort = 1001; % listen for commands on this port end -mpepSendPort = 1103; % send responses back to this remote port +% mpepSendPort = 1103; % send responses back to this remote port quitKey = KbName('esc'); -manualStartKey = KbName('m'); +manualStartKey = KbName('t'); %% Start UDP communication listeners = struct(... @@ -109,7 +109,7 @@ function processMpep(listener, msg) % connected = true; % end pnet(listener.socket, 'write', msg); - pnet(listener.socket, 'writepacket', ipstr, mpepSendPort); + pnet(listener.socket, 'writepacket', ipstr, port); end end @@ -118,7 +118,6 @@ function listen() % listen to keyboard events KbQueueCreate(); KbQueueStart(); - newExpRef = []; cleanup1 = onCleanup(@KbQueueRelease); log('Polling for UDP messages. PRESS <%s> TO QUIT', KbName(quitKey)); running = true; @@ -154,11 +153,10 @@ function listen() tlObj.start(newExpRef, ai); end KbQueueFlush; - elseif firstPress(manualStartKey) && tlObj.IsRunning && ~isempty(newExpRef) + elseif firstPress(manualStartKey) && tlObj.IsRunning fprintf(1, 'stopping timeline\n'); tlObj.stop(); communicator.send('status', { 'completed', newExpRef}); - newExpRef = []; end if toc(tid) > 0.2 pause(1e-3); % allow timeline aquisition every so often From 6317ccbeed0276edef749b8d6f3f9180c2922cc0 Mon Sep 17 00:00:00 2001 From: Michael <mkrumin@gmail.com> Date: Wed, 31 Jan 2018 10:22:24 +0000 Subject: [PATCH 052/393] Removed irrelevent code Code was copied from bindMpepServerWithWS and isn't relevent here --- cortexlab/+tl/bindMpepServer.m | 6 ------ 1 file changed, 6 deletions(-) diff --git a/cortexlab/+tl/bindMpepServer.m b/cortexlab/+tl/bindMpepServer.m index ccbc953f..533e375b 100644 --- a/cortexlab/+tl/bindMpepServer.m +++ b/cortexlab/+tl/bindMpepServer.m @@ -145,18 +145,12 @@ function listen() [newExpRef, ~, subsessionURL] = dat.newExp(mouseName, now, expParams, ai); ai.subsessionURL = subsessionURL; tls.AlyxInstance = ai; - - %[subjectRef, expDate, expSequence] = dat.parseExpRef(newExpRef); - %newExpRef = dat.constructExpRef(mouseName, now, expNum); - communicator.send('AlyxSend', {tls.AlyxInstance}); - communicator.send('status', { 'starting', newExpRef}); tlObj.start(newExpRef, ai); end KbQueueFlush; elseif firstPress(manualStartKey) && tlObj.IsRunning fprintf(1, 'stopping timeline\n'); tlObj.stop(); - communicator.send('status', { 'completed', newExpRef}); end if toc(tid) > 0.2 pause(1e-3); % allow timeline aquisition every so often From 64dad61724319ae06fbed35e0920e2641a028894 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 31 Jan 2018 18:02:26 +0000 Subject: [PATCH 053/393] TL classes now have the correct case Also More documentation and TLOutputAcqLive merged with StartStopSync --- +hw/Timeline.m | 42 +++++-------- +hw/tlOutput.m | 35 ++++++----- +hw/tlOutputAcqLive.m | 97 ++++++++++++++++++------------ +hw/tlOutputChrono.m | 137 +++++++++++++++++++++++------------------- +hw/tlOutputClock.m | 84 ++++++++++++++------------ 5 files changed, 214 insertions(+), 181 deletions(-) diff --git a/+hw/Timeline.m b/+hw/Timeline.m index 2168306e..e465c212 100644 --- a/+hw/Timeline.m +++ b/+hw/Timeline.m @@ -69,18 +69,13 @@ DaqIds = 'Dev1' % Device ID can be found with daq.getDevices() DaqSampleRate = 1000 % rate at which daq aquires data in Hz, see Rate DaqSamplesPerNotify % determines the number of data samples to be processed each time, see Timeline.process(), constructor and NotifyWhenDataAvailableExceeds - - Outputs = hw.tlOutputChrono('chrono', 'Dev1', 'port0/line1') - % array of output classes, defining any signals you desire to be sent from the daq. - % see hw.tlOutput, and, e.g. hw.tlOutputClock. - + Outputs = hw.tlOutputChrono('chrono', 'Dev1', 'port1/line0') % array of output classes, defining any signals you desire to be sent from the daq. See Also HW.TLOUTPUT, HW.TLOUTPUTCLOCK Inputs = struct('name', 'chrono',... 'arrayColumn', -1,... % -1 is default indicating unused, this is update when the channels are added during tl.start() 'daqChannelID', 'ai0',... 'measurement', 'Voltage',... 'terminalConfig', 'SingleEnded') UseInputs = {'chrono'} % array of inputs to record while tl is running - StopDelay = 2 % currently pauses for at least 2 secs as 'hack' before stopping main DAQ session MaxExpectedDuration = 2*60*60 % expected experiment time so data structure is initialised to sensible size (in secs) AquiredDataType = 'double' % default data type for the acquired data array (i.e. Data.rawDAQData) @@ -88,7 +83,6 @@ LivePlot = false % if true the data are plotted as the data are aquired LivePlotParams = []; WriteBufferToDisk = false % if true the data buffer is written to disk as they're aquired NB: in the future this will happen by default - end properties (Transient) @@ -115,9 +109,12 @@ methods function obj = Timeline(hw) - % Constructor method + % TIMELINE Constructor method + % HW.TIMELINE(hw) constructor can take a timeline hardware + % structure as an input, replicating a previous instance. % Adds chrono, aquireLive and clock to the outputs list, % along with default ports and delays + obj.DaqSamplesPerNotify = 1/obj.SamplingInterval; % calculate DaqSamplesPerNotify if nargin % if old tl hardware struct provided, use these to populate properties obj.Inputs = hw.inputs; @@ -126,12 +123,11 @@ obj.DaqSampleRate = hw.daqSampleRate; obj.DaqSamplesPerNotify = hw.daqSamplesPerNotify; end - end function start(obj, expRef, Alyx) - % Starts tl data acquisition - % TL.START(obj, ref, Alyx) starts all DAQ sessions and adds + % START Starts timeline data acquisition + % START(obj, ref, Alyx) starts all DAQ sessions and adds % the relevent output and input channels. if obj.IsRunning % check if it's already running, and if so, stop it @@ -178,14 +174,10 @@ function start(obj, expRef, Alyx) startBackground(obj.Sessions('main')); % start aquisition % wait for first acquisition processing to begin - while ~obj.IsRunning - pause(5e-3); - end + while ~obj.IsRunning; pause(5e-3); end % Start each output - for outidx = 1:numel(obj.Outputs) - obj.Outputs(outidx).start(obj); - end + arrayfun(@start, obj.Outputs) % Report success fprintf('Timeline started successfully for ''%s''.\n', expRef); @@ -233,7 +225,7 @@ function record(obj, name, event, t) end function secs = time(obj, strict) - % Time relative to Timeline acquisition + % TIME Time relative to Timeline acquisition % secs = TL.TIME([strict]) Returns the time in seconds relative to % Timeline data acquistion. 'strict' is optional (defaults to true), and % if true, this function will fail if Timeline is not running. If false, @@ -252,7 +244,7 @@ function record(obj, name, event, t) end function secs = ptbSecsToTimeline(obj, secs) - % Convert from Pyschtoolbox to Timeline time + % PTBSECSTOTIMELINE Convert from Pyschtoolbox to Timeline time % secs = TL.PTBSECSTOTIMELINE(secs) takes a timestamp 'secs' obtained % from Pyschtoolbox's functions and converts to Timeline-relative time. % See also TL.TIME(). @@ -262,7 +254,7 @@ function record(obj, name, event, t) function addInput(obj, name, channelID, measurement, terminalConfig, use) % Add a new input to the object's Input property - % TL.ADDINPUT(name, channelID, measurement, terminalConfig, use) + % ADDINPUT(name, channelID, measurement, terminalConfig, use) % adds a new input 'name' to the Inputs list. If use is % true, the input is also added to the UseInputs array. @@ -307,7 +299,7 @@ function addInput(obj, name, channelID, measurement, terminalConfig, use) end function wiringInfo(obj, name) - % TL.WIRINGINFO Return information about how the input/output + % WIRINGINFO Return information about how the input/output % 'name' is wired. If no name is provided, the different port % naming conventions of the NI DAQ are returned. if nargin < 2 @@ -364,9 +356,7 @@ function stop(obj) end % kill acquisition output signals - for outidx = 1:numel(obj.Outputs) - obj.Outputs(outidx).stop(obj); - end + arrayfun(@stop, obj.Outputs) pause(obj.StopDelay) % stop actual DAQ aquisition @@ -530,9 +520,7 @@ function init(obj) end % Initialize outputs - for outidx = 1:numel(obj.Outputs) - obj.Outputs(outidx).init(obj); - end + arrayfun(@(o)o.init(obj), obj.Outputs) end function process(obj, ~, event) diff --git a/+hw/tlOutput.m b/+hw/tlOutput.m index 697b2041..322a1e7f 100644 --- a/+hw/tlOutput.m +++ b/+hw/tlOutput.m @@ -1,14 +1,13 @@ -classdef tlOutput < matlab.mixin.Heterogeneous & handle - %hw.tlOutput Code to specify an output channel for timeline +classdef TLOutput < matlab.mixin.Heterogeneous & handle + % HW.TLOUTPUT Code to specify an output channel for timeline % This is an abstract class. % % Below is a list of some subclasses and their functions: - % hw.tlOutputClock - clocked output on a counter channel - % hw.tlOutputChrono - the default, flip/flop status check output - % hw.tlOutputAcqLive - a digital channel that is on for the duration - % of the recording - % hw.tlOutputStartStopSync - a digital channel that turns on only at - % the beginning and end of the recording + % hw.TLOutputClock - clocked output on a counter channel + % hw.TLOutputChrono - the default, flip/flop status check output + % hw.TLOutputAcqLive - a digital channel that signals that + % aquisition has begun or ended with either a constant on signal or a + % brief pulse. % % The timeline object will call the init, start, process, and stop % methods. @@ -18,21 +17,21 @@ % 2018-01 NS created properties - name % user choice, text - enable = true % will not do anything with it unless this is true - verbose = false % output status updates. Initialization message outputs regardless of verbose. + Name % The name of the timeline output, for easy identification + Enable = true % Will not do anything with it unless this is true + Verbose = false % Flag to output status updates. Initialization message outputs regardless of verbose. end - properties (Transient) - session + properties (Transient, Hidden) + Session % Holds an NI DAQ session object end methods (Abstract) - init(obj, timeline) % called when timeline is initialized (see hw.Timeline/init), e.g. to open daq session and set parameters - start(obj, timeline) % called when timeline is started (see hw.Timeline/start), e.g. to start outputs - process(obj, timeline, event) % called every time Timeline processes a chunk of data, in case output needs to react to it - stop(obj, timeline) % called when timeline is stopped (see hw.Timeline/stop), to close and clean up - s = toStr(obj) % a string that describes the object succintly + init(obj, timeline) % Called when timeline is initialized (see HW.TIMELINE/INIT), e.g. to open daq session and set parameters + start(obj, timeline) % Called when timeline is started (see HW.TIMELINE/START), e.g. to start outputs + process(obj, timeline, event) % Called every time Timeline processes a chunk of data, in case output needs to react to it + stop(obj, timeline) % Called when timeline is stopped (see HW.TIMELINE/STOP), to close and clean up + s = toStr(obj) % A string that describes the object succintly end end diff --git a/+hw/tlOutputAcqLive.m b/+hw/tlOutputAcqLive.m index 089e1710..a7610a3e 100644 --- a/+hw/tlOutputAcqLive.m +++ b/+hw/tlOutputAcqLive.m @@ -1,45 +1,59 @@ -classdef tlOutputAcqLive < hw.tlOutput - %hw.tlOutputAcqLive A digital signal that goes up when the recording starts, - % down when it ends. - % See also hw.tlOutput and hw.Timeline +classdef TLOutputAcqLive < hw.TlOutput + % HW.TLOUTPUTACQLIVE A digital signal that goes up when the recording starts, + % down when it ends. + % Used for triggaring external instruments during data aquisition. + % + % See also HW.TLOUTPUT and HW.TIMELINE % % Part of Rigbox % 2018-01 NS properties - daqDeviceID - daqChannelID - daqVendor = 'ni' - initialDelay = 0 % sec, time to wait before starting + DaqDeviceID + DaqChannelID + DaqVendor = 'ni' + InitialDelay = 0 % sec, time to wait before starting + PulseDuration = Inf; % sec, time that the pulse is on at beginning and end end methods - function obj = tlOutputAcqLive(name, daqDeviceID, daqChannelID) - obj.name = name; - obj.daqDeviceID = daqDeviceID; - obj.daqChannelID = daqChannelID; + function obj = TLOutputAcqLive(name, daqDeviceID, daqChannelID) + % TLOUTPUTCHRONO Constructor method + obj.Name = name; + obj.DaqDeviceID = daqDeviceID; + obj.DaqChannelID = daqChannelID; end function init(obj, ~) - % called when timeline is initialized (see hw.Timeline/init) - if obj.enable - fprintf(1, 'initializing %s\n', obj.toStr); - obj.session = daq.createSession(obj.daqVendor); - obj.session.addDigitalChannel(obj.daqDeviceID, obj.daqChannelID, 'OutputOnly'); - outputSingleScan(obj.session, false); % start in the off/false state - end + % INIT Initialize the output session + % INIT(obj, timeline) is called when timeline is initialized. + % Creates the DAQ session and ensures it is outputting a low + % (digital off) signal. + % + % See Also HW.TIMELINE/INIT + if obj.Enable + fprintf(1, 'initializing %s\n', obj.toStr); + obj.Session = daq.createSession(obj.DaqVendor); + obj.Session.addDigitalChannel(obj.DaqDeviceID, obj.DaqChannelID, 'OutputOnly'); + outputSingleScan(obj.Session, false); % start in the off/false state + end end function start(obj, ~) - % called when timeline is started (see hw.Timeline/start) - if obj.enable - if obj.verbose - fprintf(1, 'start %s\n', obj.name); - end - - pause(obj.initialDelay); % wait for some duration before starting - outputSingleScan(obj.session, true); % set digital output true: acquisition is "live" + % START Output a high voltage signal + % Called when timeline is started, this outputs the first high + % voltage signal to triggar external instrument aquisition + % + % See Also HW.TIMELINE/START + if obj.Enable + if obj.Verbose; fprintf(1, 'start %s\n', obj.Name); end + pause(obj.InitialDelay); % wait for some duration before starting + outputSingleScan(obj.Session, true); % set digital output true: acquisition is "live" + if obj.PulseDuration ~= Inf + pause(obj.PulseDuration); + outputSingleScan(obj.Session, false); end + end end function process(~, ~, ~) @@ -49,20 +63,29 @@ function process(~, ~, ~) end function stop(obj,~) - % called when timeline is stopped (see hw.Timeline/stop) - if obj.enable - if obj.verbose - fprintf(1, 'stop %s\n', obj.name); - end - stop(obj.session); - release(obj.session); - obj.session = []; + % STOP Stops the DAQ session object. + % Called when timeline is stopped. Outputs a low voltage signal, + % the stops and releases the session object. + % + % See Also HW.TIMELINE/STOP + if obj.Enable + % set digital output false: acquisition is no longer "live" + if obj.PulseDuration ~= Inf + outputSingleScan(obj.Session, true); + pause(obj.PulseDuration); + end + outputSingleScan(obj.Session, false); + + if obj.Verbose; fprintf(1, 'stop %s\n', obj.Name); end + stop(obj.Session); + release(obj.Session); + obj.Session = []; end end function s = toStr(obj) - s = sprintf('"%s" on %s/%s (acqLive, initial delay %.2f)', obj.name, ... - obj.daqDeviceID, obj.daqChannelID, obj.initialDelay); + s = sprintf('"%s" on %s/%s (acqLive, initial delay %.2f)', obj.Name, ... + obj.DaqDeviceID, obj.DaqChannelID, obj.InitialDelay); end end diff --git a/+hw/tlOutputChrono.m b/+hw/tlOutputChrono.m index f0c9f230..bad6aed1 100644 --- a/+hw/tlOutputChrono.m +++ b/+hw/tlOutputChrono.m @@ -1,89 +1,101 @@ -classdef tlOutputChrono < hw.tlOutput - %hw.tlOutputChrono Timeline uses this to monitor that - % acquisition is proceeding normally during a recording and to update - % the synchronization between the system time and the timeline time (to - % prevent drift between daq and computer clock). - % See also hw.tlOutput and hw.Timeline +classdef TLOutputChrono < hw.TlOutput + % HW.TLOUTPUTCHRONO Principle output channel class which sets timeline clock offset + % Timeline uses this to monitor that acquisition is proceeding normally + % during a recording and to update the synchronization between the + % system time and the timeline time (to prevent drift between daq and + % computer clock). + % + % See also HW.TLOUTPUT and HW.TIMELINE % % Part of Rigbox % 2018-01 NS properties - daqDeviceID - daqChannelID - daqVendor = 'ni' - NextChronoSign = 1 % the value to output on the chrono channel, the sign is changed each 'Process' event + DaqDeviceID + DaqChannelID + DaqVendor = 'ni' % Name of the DAQ vendor + NextChronoSign = 1 % The value to output on the chrono channel, the sign is changed each 'Process' event end methods - function obj = tlOutputChrono(name, daqDeviceID, daqChannelID) - obj.name = name; - obj.daqDeviceID = daqDeviceID; - obj.daqChannelID = daqChannelID; + function obj = TLOutputChrono(name, daqDeviceID, daqChannelID) + % TLOUTPUTCHRONO Constructor method + obj.Name = name; + obj.DaqDeviceID = daqDeviceID; + obj.DaqChannelID = daqChannelID; end function init(obj, timeline) - % called when timeline is initialized (see hw.Timeline/init) - if obj.enable + % INIT Initialize the output session + % INIT(obj, timeline) is called when timeline is initialized. + % Creates the DAQ session and ensures that the clocking pulse test + % can not be read back + % + % See Also HW.TIMELINE/INIT + if obj.Enable fprintf(1, 'initializing %s\n', obj.toStr); - obj.session = daq.createSession(obj.daqVendor); - obj.session.addDigitalChannel(obj.daqDeviceID, obj.daqChannelID, 'OutputOnly'); + obj.Session = daq.createSession(obj.DaqVendor); + obj.Session.addDigitalChannel(obj.DaqDeviceID, obj.DaqChannelID, 'OutputOnly'); tls = timeline.getSessions('main'); %%Send a test pulse low, then high to clocking channel & check we read it back idx = cellfun(@(s2)strcmp('chrono',s2), {timeline.Inputs.name}); - outputSingleScan(obj.session, false) + outputSingleScan(obj.Session, false) x1 = tls.inputSingleScan; - outputSingleScan(obj.session, true) + outputSingleScan(obj.Session, true) x2 = tls.inputSingleScan; assert(x1(timeline.Inputs(idx).arrayColumn) < 2.5 && x2(timeline.Inputs(idx).arrayColumn) > 2.5,... 'The clocking pulse test could not be read back'); - timeline.CurrSysTimeTimelineOffset = GetSecs; % to initialize this, will be a bit off but fixed after the first pulse end end function start(obj, timeline) - % called when timeline is started (see hw.Timeline/start) - if obj.enable - if obj.verbose - fprintf(1, 'start %s\n', obj.name); - end - t = GetSecs; % system time before output - outputSingleScan(obj.session, false) % this will be the clocking pulse detected the first time process is called - timeline.LastClockSentSysTime = (t + GetSecs)/2; - end + % START Starts the first chrono flip + % Called when timeline is started, this outputs the first low + % voltage output on the chrono output channel + % + % See Also HW.TIMELINE/START + if obj.Enable % If the object is to be used + if obj.Verbose; fprintf(1, 'start %s\n', obj.name); end + t = GetSecs; % system time before output + outputSingleScan(obj.session, false) % this will be the clocking pulse detected the first time process is called + timeline.LastClockSentSysTime = (t + GetSecs)/2; + end end function process(obj, timeline, event) - % called every time Timeline processes a chunk of data - if obj.enable && timeline.IsRunning && ~isempty(obj.session) - if obj.verbose - fprintf(1, 'process %s\n', obj.name); + % PROCESS Record the timestamp of last chrono flip, and output again + % OBJ.PROCESS(TIMELINE, EVENT) is called every time Timeline + % processes a chunk of data. The sign of the chrono signal is + % flipped on each call (at LastClockSentSysTime), and the time of + % the previous flip is found in the data and its timestamp noted. + % This is used by TL.TIME() to convert between system time and + % acquisition time. + % + % LastTimestamp is the time of the last scan in the previous data + % chunk, and is used to ensure no data samples have been lost. + % + % See Also TL.TIME() + + if obj.Enable && timeline.IsRunning && ~isempty(obj.Session) + if obj.Verbose + fprintf(1, 'process %s\n', obj.Name); end - % sign of the chrono signal is - % flipped on each call (at LastClockSentSysTime), and the - % time of the previous flip is found in the data and its - % timestamp noted. This is used by tl.time() to convert - % between system time and acquisition time. - % - % LastTimestamp is the time of the last scan in the previous - % data chunk, and is used to ensure no data samples have been - % lost. - %%% The chrono "out" value is flipped at a recorded time, and - %%% the sample index that this flip is measured is noted - % First, find the index of the flip in the latest chunk of data + % The chrono "out" value is flipped at a recorded time, and the + % sample index that this flip is measured is noted First, find + % the index of the flip in the latest chunk of data idx = elementByName(timeline.Inputs, 'chrono'); clockChangeIdx = find(sign(event.Data(:,timeline.Inputs(idx).arrayColumn) - 2.5) == obj.NextChronoSign, 1); - if obj.verbose + if obj.Verbose fprintf(1, ' CurrOffset=%.2f, LastClock=%.2f\n', ... timeline.CurrSysTimeTimelineOffset, timeline.LastClockSentSysTime); end - %Ensure the clocking pulse was detected + % Ensure the clocking pulse was detected if ~isempty(clockChangeIdx) clockChangeTimestamp = event.TimeStamps(clockChangeIdx); timeline.CurrSysTimeTimelineOffset = timeline.LastClockSentSysTime - clockChangeTimestamp; @@ -91,34 +103,35 @@ function process(obj, timeline, event) warning('Rigging:Timeline:timing', 'clocking pulse not detected - probably lagging more than one data chunk'); end - %Now send the next clock pulse + % Now send the next clock pulse obj.NextChronoSign = -obj.NextChronoSign; % flip next chrono t = GetSecs; % system time before output - outputSingleScan(obj.session, obj.NextChronoSign > 0); % send next chrono flip + outputSingleScan(obj.Session, obj.NextChronoSign > 0); % send next chrono flip timeline.LastClockSentSysTime = (t + GetSecs)/2; % record mean before/after system time - if obj.verbose + if obj.Verbose fprintf(1, ' CurrOffset=%.2f, LastClock=%.2f\n', ... timeline.CurrSysTimeTimelineOffset, timeline.LastClockSentSysTime); end - end end function stop(obj,~) - % called when timeline is stopped (see hw.Timeline/stop) - if obj.enable - if obj.verbose - fprintf(1, 'stop %s\n', obj.name); - end - stop(obj.session); - release(obj.session); - obj.session = []; + % STOP Stops the DAQ session object. + % Called when timeline is stopped. Stops and releases the + % session object. + % + % See Also HW.TIMELINE/STOP + if obj.Enable + if obj.Verbose; fprintf(1, 'stop %s\n', obj.Name); end + stop(obj.Session); + release(obj.Session); + obj.Session = []; end end function s = toStr(obj) - s = sprintf('"%s" on %s/%s (chrono)', obj.name, ... - obj.daqDeviceID, obj.daqChannelID); + s = sprintf('"%s" on %s/%s (chrono)', obj.Name, ... + obj.DaqDeviceID, obj.DaqChannelID); end end diff --git a/+hw/tlOutputClock.m b/+hw/tlOutputClock.m index 6c26685b..f7a3bfaa 100644 --- a/+hw/tlOutputClock.m +++ b/+hw/tlOutputClock.m @@ -1,52 +1,61 @@ -classdef tlOutputClock < hw.tlOutput - %hw.tlOutputClock A a regular pulse at a specified frequency and duty - % cycle. Can be used to trigger camera frames, e.g. - % See also hw.tlOutput and hw.Timeline +classdef TLOutputClock < hw.TlOutput + % HW.TLOUTPUTCLOCK A regular pulse at a specified frequency and duty + % cycle. Can be used to trigger camera frames. + % + % See also HW.TLOUTPUT and HW.TIMELINE % % Part of Rigbox % 2018-01 NS properties - daqDeviceID - daqChannelID - daqVendor = 'ni' - initialDelay = 0 % delay from session start to clock output - frequency = 60; % Hz, of the clocking pulse - dutyCycle = 0.2; % proportion of each cycle that the pulse is "true" + DaqDeviceID + DaqChannelID + DaqVendor = 'ni' + InitialDelay = 0 % delay from session start to clock output + Frequency = 60; % Hz, of the clocking pulse + DutyCycle = 0.2; % proportion of each cycle that the pulse is "true" end - properties (Transient) - clockChan + properties (Transient, Hidden) + ClockChan % Holds an instance of the PulseGeneration channel end methods - function obj = tlOutputClock(name, daqDeviceID, daqChannelID) - obj.name = name; - obj.daqDeviceID = daqDeviceID; - obj.daqChannelID = daqChannelID; + function obj = TLOutputClock(name, daqDeviceID, daqChannelID) + % TLOUTPUTCHRONO Constructor method + obj.Name = name; + obj.DaqDeviceID = daqDeviceID; + obj.DaqChannelID = daqChannelID; end function init(obj, ~) - % called when timeline is initialized (see hw.Timeline/init) - if obj.enable + % INIT Initialize the output session + % INIT(obj, timeline) is called when timeline is initialized. + % Creates the DAQ session and adds a PulseGeneration channel with + % the specified frequency, duty cycle and delay. + % + % See Also HW.TIMELINE/INIT + if obj.Enable fprintf(1, 'initializing %s\n', obj.toStr); - obj.session = daq.createSession(obj.daqVendor); + obj.session = daq.createSession(obj.DaqVendor); obj.session.IsContinuous = true; - clocked = obj.session.addCounterOutputChannel(obj.daqDeviceID, obj.daqChannelID, 'PulseGeneration'); - clocked.Frequency = obj.frequency; - clocked.DutyCycle = obj.dutyCycle; - clocked.InitialDelay = obj.initialDelay; - obj.clockChan = clocked; + clocked = obj.Session.addCounterOutputChannel(obj.DaqDeviceID, obj.DaqChannelID, 'PulseGeneration'); + clocked.Frequency = obj.Frequency; + clocked.DutyCycle = obj.DutyCycle; + clocked.InitialDelay = obj.InitialDelay; + obj.ClockChan = clocked; end end function start(obj, ~) - % called when timeline is started (see hw.Timeline/start) - if obj.enable - if obj.verbose - fprintf(1, 'start %s\n', obj.name); - end + % START Starts the clocking pulse + % Called when timeline is started, this uses STARTBACKGROUND to + % start the clocking pulse + % + % See Also HW.TIMELINE/START + if obj.Enable + if obj.Verbose; fprintf(1, 'start %s\n', obj.Name); end startBackground(obj.session); end end @@ -58,12 +67,13 @@ function process(~, ~, ~) end function stop(obj,~) - % called when timeline is stopped (see hw.Timeline/stop) - if obj.enable - if obj.verbose - fprintf(1, 'stop %s\n', obj.name); - end - + % STOP Stops the DAQ session object. + % Called when timeline is stopped. Stops and releases the + % session object. + % + % See Also HW.TIMELINE/STOP + if obj.Enable + if obj.Verbose; fprintf(1, 'stop %s\n', obj.Name); end stop(obj.session); release(obj.session); obj.session = []; @@ -71,8 +81,8 @@ function stop(obj,~) end function s = toStr(obj) - s = sprintf('"%s" on %s/%s (clock, %dHz, %.2f duty cycle)', obj.name, ... - obj.daqDeviceID, obj.daqChannelID, obj.frequency, obj.dutyCycle); + s = sprintf('"%s" on %s/%s (clock, %dHz, %.2f duty cycle)', obj.Name, ... + obj.DaqDeviceID, obj.DaqChannelID, obj.Frequency, obj.DutyCycle); end end From 17c2570b394155baa4d1946a9ed191ef245006aa Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 31 Jan 2018 18:02:58 +0000 Subject: [PATCH 054/393] Removed classes --- +hw/tlOutput.m | 38 ---------- +hw/tlOutputAcqLive.m | 94 ------------------------ +hw/tlOutputChrono.m | 140 ------------------------------------ +hw/tlOutputClock.m | 90 ----------------------- +hw/tlOutputStartStopSync.m | 82 --------------------- 5 files changed, 444 deletions(-) delete mode 100644 +hw/tlOutput.m delete mode 100644 +hw/tlOutputAcqLive.m delete mode 100644 +hw/tlOutputChrono.m delete mode 100644 +hw/tlOutputClock.m delete mode 100644 +hw/tlOutputStartStopSync.m diff --git a/+hw/tlOutput.m b/+hw/tlOutput.m deleted file mode 100644 index 322a1e7f..00000000 --- a/+hw/tlOutput.m +++ /dev/null @@ -1,38 +0,0 @@ -classdef TLOutput < matlab.mixin.Heterogeneous & handle - % HW.TLOUTPUT Code to specify an output channel for timeline - % This is an abstract class. - % - % Below is a list of some subclasses and their functions: - % hw.TLOutputClock - clocked output on a counter channel - % hw.TLOutputChrono - the default, flip/flop status check output - % hw.TLOutputAcqLive - a digital channel that signals that - % aquisition has begun or ended with either a constant on signal or a - % brief pulse. - % - % The timeline object will call the init, start, process, and stop - % methods. - % - % Part of Rigbox - - % 2018-01 NS created - - properties - Name % The name of the timeline output, for easy identification - Enable = true % Will not do anything with it unless this is true - Verbose = false % Flag to output status updates. Initialization message outputs regardless of verbose. - end - - properties (Transient, Hidden) - Session % Holds an NI DAQ session object - end - - methods (Abstract) - init(obj, timeline) % Called when timeline is initialized (see HW.TIMELINE/INIT), e.g. to open daq session and set parameters - start(obj, timeline) % Called when timeline is started (see HW.TIMELINE/START), e.g. to start outputs - process(obj, timeline, event) % Called every time Timeline processes a chunk of data, in case output needs to react to it - stop(obj, timeline) % Called when timeline is stopped (see HW.TIMELINE/STOP), to close and clean up - s = toStr(obj) % A string that describes the object succintly - end - -end - diff --git a/+hw/tlOutputAcqLive.m b/+hw/tlOutputAcqLive.m deleted file mode 100644 index a7610a3e..00000000 --- a/+hw/tlOutputAcqLive.m +++ /dev/null @@ -1,94 +0,0 @@ -classdef TLOutputAcqLive < hw.TlOutput - % HW.TLOUTPUTACQLIVE A digital signal that goes up when the recording starts, - % down when it ends. - % Used for triggaring external instruments during data aquisition. - % - % See also HW.TLOUTPUT and HW.TIMELINE - % - % Part of Rigbox - % 2018-01 NS - - properties - DaqDeviceID - DaqChannelID - DaqVendor = 'ni' - InitialDelay = 0 % sec, time to wait before starting - PulseDuration = Inf; % sec, time that the pulse is on at beginning and end - end - - methods - function obj = TLOutputAcqLive(name, daqDeviceID, daqChannelID) - % TLOUTPUTCHRONO Constructor method - obj.Name = name; - obj.DaqDeviceID = daqDeviceID; - obj.DaqChannelID = daqChannelID; - end - - function init(obj, ~) - % INIT Initialize the output session - % INIT(obj, timeline) is called when timeline is initialized. - % Creates the DAQ session and ensures it is outputting a low - % (digital off) signal. - % - % See Also HW.TIMELINE/INIT - if obj.Enable - fprintf(1, 'initializing %s\n', obj.toStr); - obj.Session = daq.createSession(obj.DaqVendor); - obj.Session.addDigitalChannel(obj.DaqDeviceID, obj.DaqChannelID, 'OutputOnly'); - outputSingleScan(obj.Session, false); % start in the off/false state - end - end - - function start(obj, ~) - % START Output a high voltage signal - % Called when timeline is started, this outputs the first high - % voltage signal to triggar external instrument aquisition - % - % See Also HW.TIMELINE/START - if obj.Enable - if obj.Verbose; fprintf(1, 'start %s\n', obj.Name); end - pause(obj.InitialDelay); % wait for some duration before starting - outputSingleScan(obj.Session, true); % set digital output true: acquisition is "live" - if obj.PulseDuration ~= Inf - pause(obj.PulseDuration); - outputSingleScan(obj.Session, false); - end - end - end - - function process(~, ~, ~) - % called every time Timeline processes a chunk of data - %fprintf(1, 'process acqLive\n'); - % -- pass - end - - function stop(obj,~) - % STOP Stops the DAQ session object. - % Called when timeline is stopped. Outputs a low voltage signal, - % the stops and releases the session object. - % - % See Also HW.TIMELINE/STOP - if obj.Enable - % set digital output false: acquisition is no longer "live" - if obj.PulseDuration ~= Inf - outputSingleScan(obj.Session, true); - pause(obj.PulseDuration); - end - outputSingleScan(obj.Session, false); - - if obj.Verbose; fprintf(1, 'stop %s\n', obj.Name); end - stop(obj.Session); - release(obj.Session); - obj.Session = []; - end - end - - function s = toStr(obj) - s = sprintf('"%s" on %s/%s (acqLive, initial delay %.2f)', obj.Name, ... - obj.DaqDeviceID, obj.DaqChannelID, obj.InitialDelay); - end - - end - -end - diff --git a/+hw/tlOutputChrono.m b/+hw/tlOutputChrono.m deleted file mode 100644 index bad6aed1..00000000 --- a/+hw/tlOutputChrono.m +++ /dev/null @@ -1,140 +0,0 @@ -classdef TLOutputChrono < hw.TlOutput - % HW.TLOUTPUTCHRONO Principle output channel class which sets timeline clock offset - % Timeline uses this to monitor that acquisition is proceeding normally - % during a recording and to update the synchronization between the - % system time and the timeline time (to prevent drift between daq and - % computer clock). - % - % See also HW.TLOUTPUT and HW.TIMELINE - % - % Part of Rigbox - % 2018-01 NS - - properties - DaqDeviceID - DaqChannelID - DaqVendor = 'ni' % Name of the DAQ vendor - NextChronoSign = 1 % The value to output on the chrono channel, the sign is changed each 'Process' event - end - - methods - function obj = TLOutputChrono(name, daqDeviceID, daqChannelID) - % TLOUTPUTCHRONO Constructor method - obj.Name = name; - obj.DaqDeviceID = daqDeviceID; - obj.DaqChannelID = daqChannelID; - end - - function init(obj, timeline) - % INIT Initialize the output session - % INIT(obj, timeline) is called when timeline is initialized. - % Creates the DAQ session and ensures that the clocking pulse test - % can not be read back - % - % See Also HW.TIMELINE/INIT - if obj.Enable - fprintf(1, 'initializing %s\n', obj.toStr); - obj.Session = daq.createSession(obj.DaqVendor); - obj.Session.addDigitalChannel(obj.DaqDeviceID, obj.DaqChannelID, 'OutputOnly'); - - tls = timeline.getSessions('main'); - - %%Send a test pulse low, then high to clocking channel & check we read it back - idx = cellfun(@(s2)strcmp('chrono',s2), {timeline.Inputs.name}); - outputSingleScan(obj.Session, false) - x1 = tls.inputSingleScan; - outputSingleScan(obj.Session, true) - x2 = tls.inputSingleScan; - assert(x1(timeline.Inputs(idx).arrayColumn) < 2.5 && x2(timeline.Inputs(idx).arrayColumn) > 2.5,... - 'The clocking pulse test could not be read back'); - timeline.CurrSysTimeTimelineOffset = GetSecs; % to initialize this, will be a bit off but fixed after the first pulse - end - end - - function start(obj, timeline) - % START Starts the first chrono flip - % Called when timeline is started, this outputs the first low - % voltage output on the chrono output channel - % - % See Also HW.TIMELINE/START - if obj.Enable % If the object is to be used - if obj.Verbose; fprintf(1, 'start %s\n', obj.name); end - t = GetSecs; % system time before output - outputSingleScan(obj.session, false) % this will be the clocking pulse detected the first time process is called - timeline.LastClockSentSysTime = (t + GetSecs)/2; - end - end - - function process(obj, timeline, event) - % PROCESS Record the timestamp of last chrono flip, and output again - % OBJ.PROCESS(TIMELINE, EVENT) is called every time Timeline - % processes a chunk of data. The sign of the chrono signal is - % flipped on each call (at LastClockSentSysTime), and the time of - % the previous flip is found in the data and its timestamp noted. - % This is used by TL.TIME() to convert between system time and - % acquisition time. - % - % LastTimestamp is the time of the last scan in the previous data - % chunk, and is used to ensure no data samples have been lost. - % - % See Also TL.TIME() - - if obj.Enable && timeline.IsRunning && ~isempty(obj.Session) - if obj.Verbose - fprintf(1, 'process %s\n', obj.Name); - end - - % The chrono "out" value is flipped at a recorded time, and the - % sample index that this flip is measured is noted First, find - % the index of the flip in the latest chunk of data - idx = elementByName(timeline.Inputs, 'chrono'); - clockChangeIdx = find(sign(event.Data(:,timeline.Inputs(idx).arrayColumn) - 2.5) == obj.NextChronoSign, 1); - - if obj.Verbose - fprintf(1, ' CurrOffset=%.2f, LastClock=%.2f\n', ... - timeline.CurrSysTimeTimelineOffset, timeline.LastClockSentSysTime); - end - - % Ensure the clocking pulse was detected - if ~isempty(clockChangeIdx) - clockChangeTimestamp = event.TimeStamps(clockChangeIdx); - timeline.CurrSysTimeTimelineOffset = timeline.LastClockSentSysTime - clockChangeTimestamp; - else - warning('Rigging:Timeline:timing', 'clocking pulse not detected - probably lagging more than one data chunk'); - end - - % Now send the next clock pulse - obj.NextChronoSign = -obj.NextChronoSign; % flip next chrono - t = GetSecs; % system time before output - outputSingleScan(obj.Session, obj.NextChronoSign > 0); % send next chrono flip - timeline.LastClockSentSysTime = (t + GetSecs)/2; % record mean before/after system time - if obj.Verbose - fprintf(1, ' CurrOffset=%.2f, LastClock=%.2f\n', ... - timeline.CurrSysTimeTimelineOffset, timeline.LastClockSentSysTime); - end - end - end - - function stop(obj,~) - % STOP Stops the DAQ session object. - % Called when timeline is stopped. Stops and releases the - % session object. - % - % See Also HW.TIMELINE/STOP - if obj.Enable - if obj.Verbose; fprintf(1, 'stop %s\n', obj.Name); end - stop(obj.Session); - release(obj.Session); - obj.Session = []; - end - end - - function s = toStr(obj) - s = sprintf('"%s" on %s/%s (chrono)', obj.Name, ... - obj.DaqDeviceID, obj.DaqChannelID); - end - - end - -end - diff --git a/+hw/tlOutputClock.m b/+hw/tlOutputClock.m deleted file mode 100644 index f7a3bfaa..00000000 --- a/+hw/tlOutputClock.m +++ /dev/null @@ -1,90 +0,0 @@ -classdef TLOutputClock < hw.TlOutput - % HW.TLOUTPUTCLOCK A regular pulse at a specified frequency and duty - % cycle. Can be used to trigger camera frames. - % - % See also HW.TLOUTPUT and HW.TIMELINE - % - % Part of Rigbox - % 2018-01 NS - - properties - DaqDeviceID - DaqChannelID - DaqVendor = 'ni' - InitialDelay = 0 % delay from session start to clock output - Frequency = 60; % Hz, of the clocking pulse - DutyCycle = 0.2; % proportion of each cycle that the pulse is "true" - end - - properties (Transient, Hidden) - ClockChan % Holds an instance of the PulseGeneration channel - end - - methods - function obj = TLOutputClock(name, daqDeviceID, daqChannelID) - % TLOUTPUTCHRONO Constructor method - obj.Name = name; - obj.DaqDeviceID = daqDeviceID; - obj.DaqChannelID = daqChannelID; - end - - function init(obj, ~) - % INIT Initialize the output session - % INIT(obj, timeline) is called when timeline is initialized. - % Creates the DAQ session and adds a PulseGeneration channel with - % the specified frequency, duty cycle and delay. - % - % See Also HW.TIMELINE/INIT - if obj.Enable - fprintf(1, 'initializing %s\n', obj.toStr); - - obj.session = daq.createSession(obj.DaqVendor); - obj.session.IsContinuous = true; - clocked = obj.Session.addCounterOutputChannel(obj.DaqDeviceID, obj.DaqChannelID, 'PulseGeneration'); - clocked.Frequency = obj.Frequency; - clocked.DutyCycle = obj.DutyCycle; - clocked.InitialDelay = obj.InitialDelay; - obj.ClockChan = clocked; - end - end - - function start(obj, ~) - % START Starts the clocking pulse - % Called when timeline is started, this uses STARTBACKGROUND to - % start the clocking pulse - % - % See Also HW.TIMELINE/START - if obj.Enable - if obj.Verbose; fprintf(1, 'start %s\n', obj.Name); end - startBackground(obj.session); - end - end - - function process(~, ~, ~) - % called every time Timeline processes a chunk of data - %fprintf(1, 'process Clock\n'); - % -- pass - end - - function stop(obj,~) - % STOP Stops the DAQ session object. - % Called when timeline is stopped. Stops and releases the - % session object. - % - % See Also HW.TIMELINE/STOP - if obj.Enable - if obj.Verbose; fprintf(1, 'stop %s\n', obj.Name); end - stop(obj.session); - release(obj.session); - obj.session = []; - end - end - - function s = toStr(obj) - s = sprintf('"%s" on %s/%s (clock, %dHz, %.2f duty cycle)', obj.Name, ... - obj.DaqDeviceID, obj.DaqChannelID, obj.Frequency, obj.DutyCycle); - end - end - -end - diff --git a/+hw/tlOutputStartStopSync.m b/+hw/tlOutputStartStopSync.m deleted file mode 100644 index cd47c20a..00000000 --- a/+hw/tlOutputStartStopSync.m +++ /dev/null @@ -1,82 +0,0 @@ -classdef tlOutputStartStopSync < hw.tlOutput - %hw.tlOutputStartStopSync A digital signal that goes up when the recording starts, - % but just briefly, then down again at the end. - % See also hw.tlOutput and hw.Timeline - % - % Part of Rigbox - % 2018-01 NS - - properties - daqDeviceID - daqChannelID - daqVendor = 'ni' - initialDelay = 0 % sec, time between start of acquisition and onset of this pulse - pulseDuration = 0.2; % sec, time that the pulse is on at beginning and end - end - - methods - function obj = tlOutputStartStopSync(name, daqDeviceID, daqChannelID) - obj.name = name; - obj.daqDeviceID = daqDeviceID; - obj.daqChannelID = daqChannelID; - end - - function init(obj, ~) - % called when timeline is initialized (see hw.Timeline/init) - if obj.enable - fprintf(1, 'initializing %s\n', obj.toStr); - obj.session = daq.createSession(obj.daqVendor); - obj.session.addDigitalChannel(obj.daqDeviceID, obj.daqChannelID, 'OutputOnly'); - outputSingleScan(obj.session, false); % ensure that it starts down - % by the way, if you use this to control a light for - % synchronization, note that you can configure in nidaqMX a - % "default" value for the channel, so for example it will stay - % "false" at all times even if the computer reboots. - end - end - - function start(obj, ~) - % called when timeline is started (see hw.Timeline/start) - if obj.enable - if obj.verbose - fprintf(1, 'start %s\n', obj.name); - end - pause(obj.initialDelay); - outputSingleScan(obj.session, true); - pause(obj.pulseDuration); - outputSingleScan(obj.session, false); - end - end - - function process(~, ~, ~) - % called every time Timeline processes a chunk of data - %fprintf(1, 'process StartStopSync\n'); - % -- pass - end - - function stop(obj,~) - % called when timeline is stopped (see hw.Timeline/stop) - if obj.enable - if obj.verbose - fprintf(1, 'stop %s\n', obj.name); - end - - outputSingleScan(obj.session, true); - pause(obj.pulseDuration); - outputSingleScan(obj.session, false); - - stop(obj.session); - release(obj.session); - obj.session = []; - end - end - - function s = toStr(obj) - s = sprintf('"%s" on %s/%s (StartStopSync, pulse duration %.2f)', obj.name, ... - obj.daqDeviceID, obj.daqChannelID, obj.pulseDuration); - end - - end - -end - From 5262d19425b5ce71a125346fdb0f171eb810dfda Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 31 Jan 2018 18:03:25 +0000 Subject: [PATCH 055/393] Re-added classes with correct case --- +hw/TLOutput.m | 38 ++++++++++++ +hw/TLOutputAcqLive.m | 94 ++++++++++++++++++++++++++++ +hw/TLOutputChrono.m | 140 ++++++++++++++++++++++++++++++++++++++++++ +hw/TLOutputClock.m | 90 +++++++++++++++++++++++++++ 4 files changed, 362 insertions(+) create mode 100644 +hw/TLOutput.m create mode 100644 +hw/TLOutputAcqLive.m create mode 100644 +hw/TLOutputChrono.m create mode 100644 +hw/TLOutputClock.m diff --git a/+hw/TLOutput.m b/+hw/TLOutput.m new file mode 100644 index 00000000..322a1e7f --- /dev/null +++ b/+hw/TLOutput.m @@ -0,0 +1,38 @@ +classdef TLOutput < matlab.mixin.Heterogeneous & handle + % HW.TLOUTPUT Code to specify an output channel for timeline + % This is an abstract class. + % + % Below is a list of some subclasses and their functions: + % hw.TLOutputClock - clocked output on a counter channel + % hw.TLOutputChrono - the default, flip/flop status check output + % hw.TLOutputAcqLive - a digital channel that signals that + % aquisition has begun or ended with either a constant on signal or a + % brief pulse. + % + % The timeline object will call the init, start, process, and stop + % methods. + % + % Part of Rigbox + + % 2018-01 NS created + + properties + Name % The name of the timeline output, for easy identification + Enable = true % Will not do anything with it unless this is true + Verbose = false % Flag to output status updates. Initialization message outputs regardless of verbose. + end + + properties (Transient, Hidden) + Session % Holds an NI DAQ session object + end + + methods (Abstract) + init(obj, timeline) % Called when timeline is initialized (see HW.TIMELINE/INIT), e.g. to open daq session and set parameters + start(obj, timeline) % Called when timeline is started (see HW.TIMELINE/START), e.g. to start outputs + process(obj, timeline, event) % Called every time Timeline processes a chunk of data, in case output needs to react to it + stop(obj, timeline) % Called when timeline is stopped (see HW.TIMELINE/STOP), to close and clean up + s = toStr(obj) % A string that describes the object succintly + end + +end + diff --git a/+hw/TLOutputAcqLive.m b/+hw/TLOutputAcqLive.m new file mode 100644 index 00000000..a7610a3e --- /dev/null +++ b/+hw/TLOutputAcqLive.m @@ -0,0 +1,94 @@ +classdef TLOutputAcqLive < hw.TlOutput + % HW.TLOUTPUTACQLIVE A digital signal that goes up when the recording starts, + % down when it ends. + % Used for triggaring external instruments during data aquisition. + % + % See also HW.TLOUTPUT and HW.TIMELINE + % + % Part of Rigbox + % 2018-01 NS + + properties + DaqDeviceID + DaqChannelID + DaqVendor = 'ni' + InitialDelay = 0 % sec, time to wait before starting + PulseDuration = Inf; % sec, time that the pulse is on at beginning and end + end + + methods + function obj = TLOutputAcqLive(name, daqDeviceID, daqChannelID) + % TLOUTPUTCHRONO Constructor method + obj.Name = name; + obj.DaqDeviceID = daqDeviceID; + obj.DaqChannelID = daqChannelID; + end + + function init(obj, ~) + % INIT Initialize the output session + % INIT(obj, timeline) is called when timeline is initialized. + % Creates the DAQ session and ensures it is outputting a low + % (digital off) signal. + % + % See Also HW.TIMELINE/INIT + if obj.Enable + fprintf(1, 'initializing %s\n', obj.toStr); + obj.Session = daq.createSession(obj.DaqVendor); + obj.Session.addDigitalChannel(obj.DaqDeviceID, obj.DaqChannelID, 'OutputOnly'); + outputSingleScan(obj.Session, false); % start in the off/false state + end + end + + function start(obj, ~) + % START Output a high voltage signal + % Called when timeline is started, this outputs the first high + % voltage signal to triggar external instrument aquisition + % + % See Also HW.TIMELINE/START + if obj.Enable + if obj.Verbose; fprintf(1, 'start %s\n', obj.Name); end + pause(obj.InitialDelay); % wait for some duration before starting + outputSingleScan(obj.Session, true); % set digital output true: acquisition is "live" + if obj.PulseDuration ~= Inf + pause(obj.PulseDuration); + outputSingleScan(obj.Session, false); + end + end + end + + function process(~, ~, ~) + % called every time Timeline processes a chunk of data + %fprintf(1, 'process acqLive\n'); + % -- pass + end + + function stop(obj,~) + % STOP Stops the DAQ session object. + % Called when timeline is stopped. Outputs a low voltage signal, + % the stops and releases the session object. + % + % See Also HW.TIMELINE/STOP + if obj.Enable + % set digital output false: acquisition is no longer "live" + if obj.PulseDuration ~= Inf + outputSingleScan(obj.Session, true); + pause(obj.PulseDuration); + end + outputSingleScan(obj.Session, false); + + if obj.Verbose; fprintf(1, 'stop %s\n', obj.Name); end + stop(obj.Session); + release(obj.Session); + obj.Session = []; + end + end + + function s = toStr(obj) + s = sprintf('"%s" on %s/%s (acqLive, initial delay %.2f)', obj.Name, ... + obj.DaqDeviceID, obj.DaqChannelID, obj.InitialDelay); + end + + end + +end + diff --git a/+hw/TLOutputChrono.m b/+hw/TLOutputChrono.m new file mode 100644 index 00000000..bad6aed1 --- /dev/null +++ b/+hw/TLOutputChrono.m @@ -0,0 +1,140 @@ +classdef TLOutputChrono < hw.TlOutput + % HW.TLOUTPUTCHRONO Principle output channel class which sets timeline clock offset + % Timeline uses this to monitor that acquisition is proceeding normally + % during a recording and to update the synchronization between the + % system time and the timeline time (to prevent drift between daq and + % computer clock). + % + % See also HW.TLOUTPUT and HW.TIMELINE + % + % Part of Rigbox + % 2018-01 NS + + properties + DaqDeviceID + DaqChannelID + DaqVendor = 'ni' % Name of the DAQ vendor + NextChronoSign = 1 % The value to output on the chrono channel, the sign is changed each 'Process' event + end + + methods + function obj = TLOutputChrono(name, daqDeviceID, daqChannelID) + % TLOUTPUTCHRONO Constructor method + obj.Name = name; + obj.DaqDeviceID = daqDeviceID; + obj.DaqChannelID = daqChannelID; + end + + function init(obj, timeline) + % INIT Initialize the output session + % INIT(obj, timeline) is called when timeline is initialized. + % Creates the DAQ session and ensures that the clocking pulse test + % can not be read back + % + % See Also HW.TIMELINE/INIT + if obj.Enable + fprintf(1, 'initializing %s\n', obj.toStr); + obj.Session = daq.createSession(obj.DaqVendor); + obj.Session.addDigitalChannel(obj.DaqDeviceID, obj.DaqChannelID, 'OutputOnly'); + + tls = timeline.getSessions('main'); + + %%Send a test pulse low, then high to clocking channel & check we read it back + idx = cellfun(@(s2)strcmp('chrono',s2), {timeline.Inputs.name}); + outputSingleScan(obj.Session, false) + x1 = tls.inputSingleScan; + outputSingleScan(obj.Session, true) + x2 = tls.inputSingleScan; + assert(x1(timeline.Inputs(idx).arrayColumn) < 2.5 && x2(timeline.Inputs(idx).arrayColumn) > 2.5,... + 'The clocking pulse test could not be read back'); + timeline.CurrSysTimeTimelineOffset = GetSecs; % to initialize this, will be a bit off but fixed after the first pulse + end + end + + function start(obj, timeline) + % START Starts the first chrono flip + % Called when timeline is started, this outputs the first low + % voltage output on the chrono output channel + % + % See Also HW.TIMELINE/START + if obj.Enable % If the object is to be used + if obj.Verbose; fprintf(1, 'start %s\n', obj.name); end + t = GetSecs; % system time before output + outputSingleScan(obj.session, false) % this will be the clocking pulse detected the first time process is called + timeline.LastClockSentSysTime = (t + GetSecs)/2; + end + end + + function process(obj, timeline, event) + % PROCESS Record the timestamp of last chrono flip, and output again + % OBJ.PROCESS(TIMELINE, EVENT) is called every time Timeline + % processes a chunk of data. The sign of the chrono signal is + % flipped on each call (at LastClockSentSysTime), and the time of + % the previous flip is found in the data and its timestamp noted. + % This is used by TL.TIME() to convert between system time and + % acquisition time. + % + % LastTimestamp is the time of the last scan in the previous data + % chunk, and is used to ensure no data samples have been lost. + % + % See Also TL.TIME() + + if obj.Enable && timeline.IsRunning && ~isempty(obj.Session) + if obj.Verbose + fprintf(1, 'process %s\n', obj.Name); + end + + % The chrono "out" value is flipped at a recorded time, and the + % sample index that this flip is measured is noted First, find + % the index of the flip in the latest chunk of data + idx = elementByName(timeline.Inputs, 'chrono'); + clockChangeIdx = find(sign(event.Data(:,timeline.Inputs(idx).arrayColumn) - 2.5) == obj.NextChronoSign, 1); + + if obj.Verbose + fprintf(1, ' CurrOffset=%.2f, LastClock=%.2f\n', ... + timeline.CurrSysTimeTimelineOffset, timeline.LastClockSentSysTime); + end + + % Ensure the clocking pulse was detected + if ~isempty(clockChangeIdx) + clockChangeTimestamp = event.TimeStamps(clockChangeIdx); + timeline.CurrSysTimeTimelineOffset = timeline.LastClockSentSysTime - clockChangeTimestamp; + else + warning('Rigging:Timeline:timing', 'clocking pulse not detected - probably lagging more than one data chunk'); + end + + % Now send the next clock pulse + obj.NextChronoSign = -obj.NextChronoSign; % flip next chrono + t = GetSecs; % system time before output + outputSingleScan(obj.Session, obj.NextChronoSign > 0); % send next chrono flip + timeline.LastClockSentSysTime = (t + GetSecs)/2; % record mean before/after system time + if obj.Verbose + fprintf(1, ' CurrOffset=%.2f, LastClock=%.2f\n', ... + timeline.CurrSysTimeTimelineOffset, timeline.LastClockSentSysTime); + end + end + end + + function stop(obj,~) + % STOP Stops the DAQ session object. + % Called when timeline is stopped. Stops and releases the + % session object. + % + % See Also HW.TIMELINE/STOP + if obj.Enable + if obj.Verbose; fprintf(1, 'stop %s\n', obj.Name); end + stop(obj.Session); + release(obj.Session); + obj.Session = []; + end + end + + function s = toStr(obj) + s = sprintf('"%s" on %s/%s (chrono)', obj.Name, ... + obj.DaqDeviceID, obj.DaqChannelID); + end + + end + +end + diff --git a/+hw/TLOutputClock.m b/+hw/TLOutputClock.m new file mode 100644 index 00000000..f7a3bfaa --- /dev/null +++ b/+hw/TLOutputClock.m @@ -0,0 +1,90 @@ +classdef TLOutputClock < hw.TlOutput + % HW.TLOUTPUTCLOCK A regular pulse at a specified frequency and duty + % cycle. Can be used to trigger camera frames. + % + % See also HW.TLOUTPUT and HW.TIMELINE + % + % Part of Rigbox + % 2018-01 NS + + properties + DaqDeviceID + DaqChannelID + DaqVendor = 'ni' + InitialDelay = 0 % delay from session start to clock output + Frequency = 60; % Hz, of the clocking pulse + DutyCycle = 0.2; % proportion of each cycle that the pulse is "true" + end + + properties (Transient, Hidden) + ClockChan % Holds an instance of the PulseGeneration channel + end + + methods + function obj = TLOutputClock(name, daqDeviceID, daqChannelID) + % TLOUTPUTCHRONO Constructor method + obj.Name = name; + obj.DaqDeviceID = daqDeviceID; + obj.DaqChannelID = daqChannelID; + end + + function init(obj, ~) + % INIT Initialize the output session + % INIT(obj, timeline) is called when timeline is initialized. + % Creates the DAQ session and adds a PulseGeneration channel with + % the specified frequency, duty cycle and delay. + % + % See Also HW.TIMELINE/INIT + if obj.Enable + fprintf(1, 'initializing %s\n', obj.toStr); + + obj.session = daq.createSession(obj.DaqVendor); + obj.session.IsContinuous = true; + clocked = obj.Session.addCounterOutputChannel(obj.DaqDeviceID, obj.DaqChannelID, 'PulseGeneration'); + clocked.Frequency = obj.Frequency; + clocked.DutyCycle = obj.DutyCycle; + clocked.InitialDelay = obj.InitialDelay; + obj.ClockChan = clocked; + end + end + + function start(obj, ~) + % START Starts the clocking pulse + % Called when timeline is started, this uses STARTBACKGROUND to + % start the clocking pulse + % + % See Also HW.TIMELINE/START + if obj.Enable + if obj.Verbose; fprintf(1, 'start %s\n', obj.Name); end + startBackground(obj.session); + end + end + + function process(~, ~, ~) + % called every time Timeline processes a chunk of data + %fprintf(1, 'process Clock\n'); + % -- pass + end + + function stop(obj,~) + % STOP Stops the DAQ session object. + % Called when timeline is stopped. Stops and releases the + % session object. + % + % See Also HW.TIMELINE/STOP + if obj.Enable + if obj.Verbose; fprintf(1, 'stop %s\n', obj.Name); end + stop(obj.session); + release(obj.session); + obj.session = []; + end + end + + function s = toStr(obj) + s = sprintf('"%s" on %s/%s (clock, %dHz, %.2f duty cycle)', obj.Name, ... + obj.DaqDeviceID, obj.DaqChannelID, obj.Frequency, obj.DutyCycle); + end + end + +end + From f93e62318847c84b068cc33396d0d77d5d9c3e45 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 31 Jan 2018 19:02:07 +0000 Subject: [PATCH 056/393] Comment formatting --- +hw/DummyFeedback.m | 23 ---------------------- +hw/TLOutput.m | 32 ++++++++++++++++++++++-------- +hw/TLOutputAcqLive.m | 40 ++++++++++++++++++++++++++++---------- +hw/TLOutputChrono.m | 22 ++++++++++++++++----- +hw/TLOutputClock.m | 37 ++++++++++++++++++++++++++--------- +hw/Timeline.m | 45 ++++++++++++++++++++++++++----------------- 6 files changed, 126 insertions(+), 73 deletions(-) delete mode 100644 +hw/DummyFeedback.m diff --git a/+hw/DummyFeedback.m b/+hw/DummyFeedback.m deleted file mode 100644 index ca3da58a..00000000 --- a/+hw/DummyFeedback.m +++ /dev/null @@ -1,23 +0,0 @@ -classdef DummyFeedback < hw.RewardController - %HW.DUMMYFEEDBACK hw.RewardController implementation that does nothing - % Detailed explanation goes here - % - % Part of Rigbox - - % 2012-10 CB created - - properties - end - - methods - function deliverBackground(obj, size) - % do nothing for now - fprintf('reward %f\n', size); - logSample(obj, size, obj.Clock.now); - end - function deliverMultiple(obj, size, interval, n) - % do nothing for now - end - end -end - diff --git a/+hw/TLOutput.m b/+hw/TLOutput.m index 322a1e7f..0d7dff65 100644 --- a/+hw/TLOutput.m +++ b/+hw/TLOutput.m @@ -1,5 +1,5 @@ classdef TLOutput < matlab.mixin.Heterogeneous & handle - % HW.TLOUTPUT Code to specify an output channel for timeline + %HW.TLOUTPUT Code to specify an output channel for timeline % This is an abstract class. % % Below is a list of some subclasses and their functions: @@ -10,7 +10,18 @@ % brief pulse. % % The timeline object will call the init, start, process, and stop - % methods. + % methods. Example: + % + % tl = hw.Timeline; + % tl.Outputs(1) = hw.TLOutputAcqLive('Instra-Triggar', 'Dev1', + % 'PFI4'); + % tl.start('2018-01-01_1_mouse2', alyxInstance); + % >> initializing Instra-Triggar + % >> start Instra-Triggar + % >> Timeline started successfully + % tl.stop; + % + % See Also HW.TLOutputChrono, HW.TLOutputAcqLive, HW.TLOutputClock % % Part of Rigbox @@ -22,16 +33,21 @@ Verbose = false % Flag to output status updates. Initialization message outputs regardless of verbose. end - properties (Transient, Hidden) + properties (Transient, Hidden, Access = protected) Session % Holds an NI DAQ session object end methods (Abstract) - init(obj, timeline) % Called when timeline is initialized (see HW.TIMELINE/INIT), e.g. to open daq session and set parameters - start(obj, timeline) % Called when timeline is started (see HW.TIMELINE/START), e.g. to start outputs - process(obj, timeline, event) % Called every time Timeline processes a chunk of data, in case output needs to react to it - stop(obj, timeline) % Called when timeline is stopped (see HW.TIMELINE/STOP), to close and clean up - s = toStr(obj) % A string that describes the object succintly + % Called when timeline is initialized (see HW.TIMELINE/INIT), e.g. to open daq session and set parameters + init(obj, timeline) + % Called when timeline is started (see HW.TIMELINE/START), e.g. to start outputs + start(obj, timeline) + % Called every time Timeline processes a chunk of data, in case output needs to react to it + process(obj, timeline, event) + % Called when timeline is stopped (see HW.TIMELINE/STOP), to close and clean up + stop(obj, timeline) + % Returns a string that describes the object succintly + s = toStr(obj) end end diff --git a/+hw/TLOutputAcqLive.m b/+hw/TLOutputAcqLive.m index a7610a3e..314f73b0 100644 --- a/+hw/TLOutputAcqLive.m +++ b/+hw/TLOutputAcqLive.m @@ -1,17 +1,30 @@ classdef TLOutputAcqLive < hw.TlOutput - % HW.TLOUTPUTACQLIVE A digital signal that goes up when the recording starts, + %HW.TLOUTPUTACQLIVE A digital signal that goes up when the recording starts, % down when it ends. - % Used for triggaring external instruments during data aquisition. + % Used for triggaring external instruments during data aquisition. Will + % either output a constant high voltage signal while Timeline is + % running, or if obj.PulseDuration is set to a value > 0 and < Inf, the + % DAQ will output a pulse of that duration at the start and end of the + % aquisition. % - % See also HW.TLOUTPUT and HW.TIMELINE + % Example: + % tl = hw.Timeline; + % tl.Outputs(1) = hw.TLOutputAcqLive('Instra-Triggar', 'Dev1', 'PFI4'); + % tl.start('2018-01-01_1_mouse2', alyxInstance); + % >> initializing Instra-Triggar + % >> start Instra-Triggar + % >> Timeline started successfully + % tl.stop; + % + % See also HW.TLOUTPUT, HW.TIMELINE % % Part of Rigbox % 2018-01 NS properties - DaqDeviceID - DaqChannelID - DaqVendor = 'ni' + DaqDeviceID % The name of the DAQ device ID, e.g. 'Dev1', see DAQ.GETDEVICES + DaqChannelID % The name of the DAQ channel ID, e.g. 'port1/line0', see DAQ.GETDEVICES + DaqVendor = 'ni' % Name of the DAQ vendor InitialDelay = 0 % sec, time to wait before starting PulseDuration = Inf; % sec, time that the pulse is on at beginning and end end @@ -57,9 +70,14 @@ function start(obj, ~) end function process(~, ~, ~) - % called every time Timeline processes a chunk of data - %fprintf(1, 'process acqLive\n'); - % -- pass + % PROCESS() Listener for processing acquired Timeline data + % PROCESS(obj, source, event) is a listener callback + % function for handling tl data acquisition. Called by the + % 'main' DAQ session with latest chunk of data. + % + % See Also HW.TIMELINE/PROCESS + %fprintf(1, 'process acqLive\n'); + % -- pass end function stop(obj,~) @@ -84,10 +102,12 @@ function stop(obj,~) end function s = toStr(obj) + % TOSTR Returns a string that describes the object succintly + % + % See Also INIT s = sprintf('"%s" on %s/%s (acqLive, initial delay %.2f)', obj.Name, ... obj.DaqDeviceID, obj.DaqChannelID, obj.InitialDelay); end - end end diff --git a/+hw/TLOutputChrono.m b/+hw/TLOutputChrono.m index bad6aed1..609865ba 100644 --- a/+hw/TLOutputChrono.m +++ b/+hw/TLOutputChrono.m @@ -1,18 +1,27 @@ classdef TLOutputChrono < hw.TlOutput - % HW.TLOUTPUTCHRONO Principle output channel class which sets timeline clock offset + %HW.TLOUTPUTCHRONO Principle output channel class which sets timeline clock offset % Timeline uses this to monitor that acquisition is proceeding normally % during a recording and to update the synchronization between the % system time and the timeline time (to prevent drift between daq and % computer clock). % - % See also HW.TLOUTPUT and HW.TIMELINE + % Example: + % tl = hw.Timeline; + % tl.Outputs(1) = hw.TLOutputChrono('Chrono', 'Dev1', 'PFI4'); + % tl.start('2018-01-01_1_mouse2', alyxInstance); + % >> initializing Chrono + % >> start Chrono + % >> Timeline started successfully + % tl.stop; + % + % See also HW.TLOUTPUT, HW.TIMELINE % % Part of Rigbox % 2018-01 NS properties - DaqDeviceID - DaqChannelID + DaqDeviceID % The name of the DAQ device ID, e.g. 'Dev1', see DAQ.GETDEVICES + DaqChannelID % The name of the DAQ channel ID, e.g. 'port1/line0', see DAQ.GETDEVICES DaqVendor = 'ni' % Name of the DAQ vendor NextChronoSign = 1 % The value to output on the chrono channel, the sign is changed each 'Process' event end @@ -77,7 +86,7 @@ function process(obj, timeline, event) % LastTimestamp is the time of the last scan in the previous data % chunk, and is used to ensure no data samples have been lost. % - % See Also TL.TIME() + % See Also HW.TIMELINE/TIME() and HW.TIMELINE/PROCESS if obj.Enable && timeline.IsRunning && ~isempty(obj.Session) if obj.Verbose @@ -130,6 +139,9 @@ function stop(obj,~) end function s = toStr(obj) + % TOSTR Returns a string that describes the object succintly + % + % See Also INIT s = sprintf('"%s" on %s/%s (chrono)', obj.Name, ... obj.DaqDeviceID, obj.DaqChannelID); end diff --git a/+hw/TLOutputClock.m b/+hw/TLOutputClock.m index f7a3bfaa..1e0210ec 100644 --- a/+hw/TLOutputClock.m +++ b/+hw/TLOutputClock.m @@ -1,22 +1,32 @@ classdef TLOutputClock < hw.TlOutput - % HW.TLOUTPUTCLOCK A regular pulse at a specified frequency and duty + %HW.TLOUTPUTCLOCK A regular pulse at a specified frequency and duty % cycle. Can be used to trigger camera frames. % - % See also HW.TLOUTPUT and HW.TIMELINE + % Example: + % tl = hw.Timeline; + % tl.Outputs(end+1) = hw.TLOutputClock('Cam-Triggar', 'Dev1', 'PFI4'); + % tl.Outputs(end).InitialDelay = 5 % Add initial delay before start + % tl.start('2018-01-01_1_mouse2', alyxInstance); + % >> initializing Cam-Triggar + % >> start Cam-Triggar + % >> Timeline started successfully + % tl.stop; + % + % See also HW.TLOUTPUT, HW.TIMELINE % % Part of Rigbox % 2018-01 NS properties - DaqDeviceID - DaqChannelID - DaqVendor = 'ni' + DaqDeviceID % The name of the DAQ device ID, e.g. 'Dev1', see DAQ.GETDEVICES + DaqChannelID % The name of the DAQ channel ID, e.g. 'port1/line0', see DAQ.GETDEVICES + DaqVendor = 'ni' % Name of the DAQ vendor InitialDelay = 0 % delay from session start to clock output Frequency = 60; % Hz, of the clocking pulse DutyCycle = 0.2; % proportion of each cycle that the pulse is "true" end - properties (Transient, Hidden) + properties (Transient, Hidden, Access = protected) ClockChan % Holds an instance of the PulseGeneration channel end @@ -61,9 +71,15 @@ function start(obj, ~) end function process(~, ~, ~) - % called every time Timeline processes a chunk of data - %fprintf(1, 'process Clock\n'); - % -- pass + % PROCESS() Listener for processing acquired Timeline data + % PROCESS(obj, source, event) is a listener callback + % function for handling tl data acquisition. Called by the + % 'main' DAQ session with latest chunk of data. + % + % See Also HW.TIMELINE/PROCESS + + %fprintf(1, 'process Clock\n'); + % -- pass end function stop(obj,~) @@ -81,6 +97,9 @@ function stop(obj,~) end function s = toStr(obj) + % TOSTR Returns a string that describes the object succintly + % + % See Also INIT s = sprintf('"%s" on %s/%s (clock, %dHz, %.2f duty cycle)', obj.Name, ... obj.DaqDeviceID, obj.DaqChannelID, obj.Frequency, obj.DutyCycle); end diff --git a/+hw/Timeline.m b/+hw/Timeline.m index e465c212..d5862738 100644 --- a/+hw/Timeline.m +++ b/+hw/Timeline.m @@ -129,6 +129,8 @@ function start(obj, expRef, Alyx) % START Starts timeline data acquisition % START(obj, ref, Alyx) starts all DAQ sessions and adds % the relevent output and input channels. + % + % See Also HW.TLOUTPUT/START if obj.IsRunning % check if it's already running, and if so, stop it disp('Timeline already running, stopping first'); @@ -230,7 +232,8 @@ function record(obj, name, event, t) % Timeline data acquistion. 'strict' is optional (defaults to true), and % if true, this function will fail if Timeline is not running. If false, % it will just return the time using Psychtoolbox GetSecs if it's not - % running. See also TL.PTBSECSTOTIMELINE(). + % running. + % See also TL.PTBSECSTOTIMELINE(). if nargin < 2; strict = true; end if obj.IsRunning secs = GetSecs - obj.CurrSysTimeTimelineOffset; @@ -329,6 +332,7 @@ function wiringInfo(obj, name) end function v = get.SamplingInterval(obj) + %GET.SAMPLINGINTERVAL Defined as the reciprocal of obj.DaqSampleRate v = 1/obj.DaqSampleRate; end @@ -350,6 +354,7 @@ function stop(obj) % TL.STOP() Deletes the listener, saves the aquired data, % stops all running DAQ sessions % + % See Also HW.TLOUTPUT/STOP if ~obj.IsRunning warning('Nothing to do, Timeline is not running!') return @@ -476,8 +481,11 @@ function stop(obj) end function s = getSessions(obj, name) + % GETSESSIONS() Returns the Sessions property % returns the Sessions property. Some things (e.g. output - % classes) need this. + % classes) need this. + % + % See Also HW.TLOUTPUT s = obj.Sessions(name); end @@ -489,13 +497,15 @@ function init(obj) % TL.INIT() creates all the DAQ sessions % and stores them in the Sessions map by their Outputs name. % Also add a 'main' session to which all input channels are - % added. See daq.createSession + % added. + % + % See Also DAQ.CREATESESSION - %%Create channels for each input + %%reate channels for each input [use, idx] = intersect({obj.Inputs.name}, obj.UseInputs);% find which inputs to use assert(numel(idx) == numel(obj.UseInputs), 'Not all inputs were recognised'); - inputSession = daq.createSession(obj.DaqVendor); - inputSession.Rate = obj.DaqSampleRate; + inputSession = daq.createSession(obj.DaqVendor); %create DAQ session for input aquisition + inputSession.Rate = obj.DaqSampleRate; % set the aquisition sample rate inputSession.IsContinuous = true; % once started, continue acquiring until manually stopped inputSession.NotifyWhenDataAvailableExceeds = obj.DaqSamplesPerNotify; % when to process data obj.Sessions('main') = inputSession; @@ -519,12 +529,12 @@ function init(obj) obj.Inputs(strcmp({obj.Inputs.name}, obj.UseInputs(i))).arrayColumn = i; end - % Initialize outputs - arrayfun(@(o)o.init(obj), obj.Outputs) + %Initialize outputs + arrayfun(@(out)out.init(obj), obj.Outputs) end - function process(obj, ~, event) - % Listener for processing acquired Timeline data + function process(obj, source, event) + % PROCESS() Listener for processing acquired Timeline data % TL.PROCESS(source, event) is a listener callback % function for handling tl data acquisition. Called by the % 'main' DAQ session with latest chunk of data. This is @@ -538,18 +548,18 @@ function process(obj, ~, event) % LastTimestamp is the time of the last scan in the previous % data chunk, and is used to ensure no data samples have been % lost. + % + % See Also HW.TLOUTPUT/PROCESS - % assert continuity of this data from previous + %Assert continuity of this data from previous assert(abs(event.TimeStamps(1) - obj.LastTimestamp - obj.SamplingInterval) < 1e-8,... 'Discontinuity of DAQ acquistion detected: last timestamp was %f and this one is %f',... obj.LastTimestamp, event.TimeStamps(1)); - % process methods for outputs - for outidx = 1:numel(obj.Outputs) - obj.Outputs(outidx).process(obj, event); - end + %Process methods for outputs + arrayfun(@(out)out.process(source, event), obj.Outputs); - %%% Store new samples into the timeline array + %Store new samples into the timeline array prevSampleCount = obj.Data.rawDAQSampleCount; newSampleCount = prevSampleCount + size(event.Data, 1); @@ -570,10 +580,9 @@ function process(obj, ~, event) datToWrite = cast(event.Data, obj.AquiredDataType); % Ensure data are the correct type fwrite(obj.DataFID, datToWrite', obj.AquiredDataType); % Write to file end - % if plotting the channels live, plot the new data + %If plotting the channels live, plot the new data if obj.LivePlot; obj.livePlot(event.Data); end - end function livePlot(obj, data) From 963fd5522e9b7bb3fddd078c2a99f9d5e0d57dbd Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 1 Feb 2018 10:41:48 +0000 Subject: [PATCH 057/393] More documentation and minor fixes --- +hw/TLOutputAcqLive.m | 6 +++++- +hw/TLOutputChrono.m | 33 +++++++++++++++++++-------------- +hw/TLOutputClock.m | 15 +++++++-------- +hw/Timeline.m | 36 +++++++++++++++++------------------- 4 files changed, 48 insertions(+), 42 deletions(-) diff --git a/+hw/TLOutputAcqLive.m b/+hw/TLOutputAcqLive.m index 314f73b0..5dbfaf82 100644 --- a/+hw/TLOutputAcqLive.m +++ b/+hw/TLOutputAcqLive.m @@ -1,4 +1,4 @@ -classdef TLOutputAcqLive < hw.TlOutput +classdef TLOutputAcqLive < hw.TLOutput %HW.TLOUTPUTACQLIVE A digital signal that goes up when the recording starts, % down when it ends. % Used for triggaring external instruments during data aquisition. Will @@ -47,7 +47,11 @@ function init(obj, ~) if obj.Enable fprintf(1, 'initializing %s\n', obj.toStr); obj.Session = daq.createSession(obj.DaqVendor); + % Turn off warning about clocked sampling availability + warning('off', 'daq:Session:onDemandOnlyChannelsAdded'); + % Add on-demand digital channel obj.Session.addDigitalChannel(obj.DaqDeviceID, obj.DaqChannelID, 'OutputOnly'); + warning('on', 'daq:Session:onDemandOnlyChannelsAdded'); outputSingleScan(obj.Session, false); % start in the off/false state end end diff --git a/+hw/TLOutputChrono.m b/+hw/TLOutputChrono.m index 609865ba..059d3a85 100644 --- a/+hw/TLOutputChrono.m +++ b/+hw/TLOutputChrono.m @@ -1,4 +1,4 @@ -classdef TLOutputChrono < hw.TlOutput +classdef TLOutputChrono < hw.TLOutput %HW.TLOUTPUTCHRONO Principle output channel class which sets timeline clock offset % Timeline uses this to monitor that acquisition is proceeding normally % during a recording and to update the synchronization between the @@ -26,6 +26,11 @@ NextChronoSign = 1 % The value to output on the chrono channel, the sign is changed each 'Process' event end + properties (SetAccess = private) + CurrSysTimeTimelineOffset = 0 % difference between the system time when the last chrono flip occured and the timestamp recorded by the DAQ, see tl.process() + LastClockSentSysTime % the mean of the system time before and after the last chrono flip. Used to calculate CurrSysTimeTimelineOffset, see tl.process() + end + methods function obj = TLOutputChrono(name, daqDeviceID, daqChannelID) % TLOUTPUTCHRONO Constructor method @@ -44,8 +49,11 @@ function init(obj, timeline) if obj.Enable fprintf(1, 'initializing %s\n', obj.toStr); obj.Session = daq.createSession(obj.DaqVendor); + % Turn off warning about clocked sampling availability + warning('off', 'daq:Session:onDemandOnlyChannelsAdded'); + % Add on-demand digital channel obj.Session.addDigitalChannel(obj.DaqDeviceID, obj.DaqChannelID, 'OutputOnly'); - + warning('on', 'daq:Session:onDemandOnlyChannelsAdded'); tls = timeline.getSessions('main'); %%Send a test pulse low, then high to clocking channel & check we read it back @@ -56,11 +64,11 @@ function init(obj, timeline) x2 = tls.inputSingleScan; assert(x1(timeline.Inputs(idx).arrayColumn) < 2.5 && x2(timeline.Inputs(idx).arrayColumn) > 2.5,... 'The clocking pulse test could not be read back'); - timeline.CurrSysTimeTimelineOffset = GetSecs; % to initialize this, will be a bit off but fixed after the first pulse + obj.CurrSysTimeTimelineOffset = GetSecs; % to initialize this, will be a bit off but fixed after the first pulse end end - function start(obj, timeline) + function start(obj, ~) % START Starts the first chrono flip % Called when timeline is started, this outputs the first low % voltage output on the chrono output channel @@ -69,8 +77,8 @@ function start(obj, timeline) if obj.Enable % If the object is to be used if obj.Verbose; fprintf(1, 'start %s\n', obj.name); end t = GetSecs; % system time before output - outputSingleScan(obj.session, false) % this will be the clocking pulse detected the first time process is called - timeline.LastClockSentSysTime = (t + GetSecs)/2; + outputSingleScan(obj.Session, false) % this will be the clocking pulse detected the first time process is called + obj.LastClockSentSysTime = (t + GetSecs)/2; end end @@ -83,9 +91,6 @@ function process(obj, timeline, event) % This is used by TL.TIME() to convert between system time and % acquisition time. % - % LastTimestamp is the time of the last scan in the previous data - % chunk, and is used to ensure no data samples have been lost. - % % See Also HW.TIMELINE/TIME() and HW.TIMELINE/PROCESS if obj.Enable && timeline.IsRunning && ~isempty(obj.Session) @@ -101,13 +106,13 @@ function process(obj, timeline, event) if obj.Verbose fprintf(1, ' CurrOffset=%.2f, LastClock=%.2f\n', ... - timeline.CurrSysTimeTimelineOffset, timeline.LastClockSentSysTime); + obj.CurrSysTimeTimelineOffset, obj.LastClockSentSysTime); end % Ensure the clocking pulse was detected if ~isempty(clockChangeIdx) clockChangeTimestamp = event.TimeStamps(clockChangeIdx); - timeline.CurrSysTimeTimelineOffset = timeline.LastClockSentSysTime - clockChangeTimestamp; + obj.CurrSysTimeTimelineOffset = obj.LastClockSentSysTime - clockChangeTimestamp; else warning('Rigging:Timeline:timing', 'clocking pulse not detected - probably lagging more than one data chunk'); end @@ -116,10 +121,10 @@ function process(obj, timeline, event) obj.NextChronoSign = -obj.NextChronoSign; % flip next chrono t = GetSecs; % system time before output outputSingleScan(obj.Session, obj.NextChronoSign > 0); % send next chrono flip - timeline.LastClockSentSysTime = (t + GetSecs)/2; % record mean before/after system time + obj.LastClockSentSysTime = (t + GetSecs)/2; % record mean before/after system time if obj.Verbose fprintf(1, ' CurrOffset=%.2f, LastClock=%.2f\n', ... - timeline.CurrSysTimeTimelineOffset, timeline.LastClockSentSysTime); + obj.CurrSysTimeTimelineOffset, obj.LastClockSentSysTime); end end end @@ -141,7 +146,7 @@ function stop(obj,~) function s = toStr(obj) % TOSTR Returns a string that describes the object succintly % - % See Also INIT + % See Also HW.TIMELINE/INIT s = sprintf('"%s" on %s/%s (chrono)', obj.Name, ... obj.DaqDeviceID, obj.DaqChannelID); end diff --git a/+hw/TLOutputClock.m b/+hw/TLOutputClock.m index 1e0210ec..4784f531 100644 --- a/+hw/TLOutputClock.m +++ b/+hw/TLOutputClock.m @@ -1,4 +1,4 @@ -classdef TLOutputClock < hw.TlOutput +classdef TLOutputClock < hw.TLOutput %HW.TLOUTPUTCLOCK A regular pulse at a specified frequency and duty % cycle. Can be used to trigger camera frames. % @@ -47,9 +47,8 @@ function init(obj, ~) % See Also HW.TIMELINE/INIT if obj.Enable fprintf(1, 'initializing %s\n', obj.toStr); - - obj.session = daq.createSession(obj.DaqVendor); - obj.session.IsContinuous = true; + obj.Session = daq.createSession(obj.DaqVendor); + obj.Session.IsContinuous = true; clocked = obj.Session.addCounterOutputChannel(obj.DaqDeviceID, obj.DaqChannelID, 'PulseGeneration'); clocked.Frequency = obj.Frequency; clocked.DutyCycle = obj.DutyCycle; @@ -66,7 +65,7 @@ function start(obj, ~) % See Also HW.TIMELINE/START if obj.Enable if obj.Verbose; fprintf(1, 'start %s\n', obj.Name); end - startBackground(obj.session); + startBackground(obj.Session); end end @@ -90,9 +89,9 @@ function stop(obj,~) % See Also HW.TIMELINE/STOP if obj.Enable if obj.Verbose; fprintf(1, 'stop %s\n', obj.Name); end - stop(obj.session); - release(obj.session); - obj.session = []; + stop(obj.Session); + release(obj.Session); + obj.Session = []; end end diff --git a/+hw/Timeline.m b/+hw/Timeline.m index d5862738..7d650fa6 100644 --- a/+hw/Timeline.m +++ b/+hw/Timeline.m @@ -5,13 +5,14 @@ % is called 'chrono' and consists of a digital squarewave that flips each % time a new chunk of data is availible from the DAQ (see % NotifyWhenDataAvailableExceeds for more information). A callback -% function to this event (see tl.process()) collects the timestamp from the -% DAQ of the precise scan where the chrono signal flipped. The +% function to this event (see tl.process()) collects the timestamp from +% the DAQ of the precise scan where the chrono signal flipped. The % difference between this and the system time recorded when the flip % command was given is recorded as the CurrSysTimeTimelineOffset and can % be used to unify all timestamps across computers during an experiment -% (see tl.time() and tl.ptbSecsToTimeline()). In is assumed that the -% time between sending the chrono pulse and recieving it is negligible. +% (see tl.time(), tl.ptbSecsToTimeline() and hw.TLOutputChrono). In is +% assumed that the time between sending the chrono pulse and recieving it +% is negligible. % % There are other available clocking signals, for instance: 'acqLive' and 'clock'. % The former outputs a high (+5V) signal the entire time tl is aquiring @@ -57,7 +58,7 @@ % memory limitations when aquiring a lot of data % - Delete local binary files once timeline has successfully saved to zserver? % -% See also HW.TIMELINECLOCK +% See also HW.TIMELINECLOCK, HW.TLOUTPUT % % Part of Rigbox @@ -69,7 +70,7 @@ DaqIds = 'Dev1' % Device ID can be found with daq.getDevices() DaqSampleRate = 1000 % rate at which daq aquires data in Hz, see Rate DaqSamplesPerNotify % determines the number of data samples to be processed each time, see Timeline.process(), constructor and NotifyWhenDataAvailableExceeds - Outputs = hw.tlOutputChrono('chrono', 'Dev1', 'port1/line0') % array of output classes, defining any signals you desire to be sent from the daq. See Also HW.TLOUTPUT, HW.TLOUTPUTCLOCK + Outputs = hw.TLOutputChrono('chrono', 'Dev1', 'port1/line0') % array of output classes, defining any signals you desire to be sent from the daq. See Also HW.TLOUTPUT, HW.TLOUTPUTCLOCK Inputs = struct('name', 'chrono',... 'arrayColumn', -1,... % -1 is default indicating unused, this is update when the channels are added during tl.start() 'daqChannelID', 'ai0',... @@ -84,14 +85,7 @@ LivePlotParams = []; WriteBufferToDisk = false % if true the data buffer is written to disk as they're aquired NB: in the future this will happen by default end - - properties (Transient) - % moved these here (i.e. unprotected) so chrono class can access - NS - CurrSysTimeTimelineOffset = 0 % difference between the system time when the last chrono flip occured and the timestamp recorded by the DAQ, see tl.process() - LastTimestamp % the last timestamp returned from the daq during the DataAvailable event. Used to check sampling continuity, see tl.process() - LastClockSentSysTime % the mean of the system time before and after the last chrono flip. Used to calculate CurrSysTimeTimelineOffset, see tl.process() - end - + properties (Dependent) SamplingInterval % defined as 1/DaqSampleRate IsRunning = false % flag is set to true when the first chrono pulse is aquired and set to false when tl is stopped (and everything saved), see tl.process and tl.stop @@ -99,7 +93,8 @@ properties (Transient, Access = protected) Listener % holds the listener for 'DataAvailable', see DataAvailable and Timeline.process() - Sessions = containers.Map % map of daq sessions and their channels, created at tl.start() + Sessions = containers.Map % map of daq sessions and their channels, created at tl.start() + LastTimestamp % the last timestamp returned from the daq during the DataAvailable event. Used to check sampling continuity, see tl.process() Ref % the expRef string. See tl.start() AlyxInstance % a struct contraining the Alyx token, user and url for ile registration. See tl.start() Data % A structure containing timeline data @@ -389,10 +384,13 @@ function stop(obj) % metadata saving, see below outputClasses = arrayfun(@class, obj.Outputs, 'uni', false); chronoChan = []; nextChrono = []; acqLiveChan = []; useClock = false; clockF = []; clockD = []; + LastClockSentSysTime = []; CurrSysTimeTimelineOffset = []; chronoOutputIdx = find(strcmp(outputClasses, 'hw.tlOutputChrono'),1); if ~isempty(chronoOutputIdx) chronoChan = obj.Outputs(chronoOutputIdx).daqChannelID; nextChrono = obj.Outputs(chronoOutputIdx).NextChronoSign; + LastClockSentSysTime = obj.Outputs(chronoOutputIdx).LastClockSentSysTime; + CurrSysTimeTimelineOffset = obj.Outputs(chronoOutputIdx).CurrSysTimeTimelineOffset; end acqLiveOutputIdx = find(strcmp(outputClasses, 'hw.tlOutputAcqLive'),1); if ~isempty(acqLiveOutputIdx) @@ -417,8 +415,8 @@ function stop(obj) obj.Data.isRunning = obj.IsRunning; obj.Data.nextChronoSign = nextChrono; obj.Data.lastTimestamp = obj.LastTimestamp; - obj.Data.lastClockSentSysTime = obj.LastClockSentSysTime; - obj.Data.currSysTimeTimelineOffset = obj.CurrSysTimeTimelineOffset; + obj.Data.lastClockSentSysTime = LastClockSentSysTime; + obj.Data.currSysTimeTimelineOffset = CurrSysTimeTimelineOffset; % saving hardware metadata for each output warning('off', 'MATLAB:structOnObject'); % sorry, don't care @@ -533,7 +531,7 @@ function init(obj) arrayfun(@(out)out.init(obj), obj.Outputs) end - function process(obj, source, event) + function process(obj, ~, event) % PROCESS() Listener for processing acquired Timeline data % TL.PROCESS(source, event) is a listener callback % function for handling tl data acquisition. Called by the @@ -557,7 +555,7 @@ function process(obj, source, event) obj.LastTimestamp, event.TimeStamps(1)); %Process methods for outputs - arrayfun(@(out)out.process(source, event), obj.Outputs); + arrayfun(@(out)out.process(obj, event), obj.Outputs); %Store new samples into the timeline array prevSampleCount = obj.Data.rawDAQSampleCount; From 20903ef00a1e3906140a540d5ea23b26520afd4d Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 1 Feb 2018 13:46:08 +0000 Subject: [PATCH 058/393] Can now instantiate new timeline object with a previous hw struct --- +hw/TLOutputAcqLive.m | 28 ++++++++++++++++++++++------ +hw/TLOutputChrono.m | 22 ++++++++++++++++++---- +hw/TLOutputClock.m | 27 ++++++++++++++++++++++----- +hw/Timeline.m | 41 +++++++++++++++++++++++++---------------- 4 files changed, 87 insertions(+), 31 deletions(-) diff --git a/+hw/TLOutputAcqLive.m b/+hw/TLOutputAcqLive.m index 5dbfaf82..faf6a999 100644 --- a/+hw/TLOutputAcqLive.m +++ b/+hw/TLOutputAcqLive.m @@ -30,11 +30,27 @@ end methods - function obj = TLOutputAcqLive(name, daqDeviceID, daqChannelID) + function obj = TLOutputAcqLive(hw) % TLOUTPUTCHRONO Constructor method - obj.Name = name; - obj.DaqDeviceID = daqDeviceID; - obj.DaqChannelID = daqChannelID; + % Can take the struct form of a previous instance (as saved in the + % Timeline hw struct) to intantiate a new object with the same + % properties. + % + % See Also HW.TIMELINE + if nargin + obj.Name = hw.Name; + obj.DaqDeviceID = hw.DaqDeviceID; + obj.DaqVendor = hw.DaqVendor; + obj.DaqChannelID = hw.DaqChannelID; + obj.InitialDelay = hw.InitialDelay; + obj.PulseDuration = hw.PulseDuration; + obj.Enable = hw.Enable; + obj.Verbose = hw.Verbose; + else % Some safe defaults + obj.Name = 'Acquire Live'; + obj.DaqDeviceID = 'Dev1'; + obj.DaqChannelID = 'port1/line2'; + end end function init(obj, ~) @@ -109,8 +125,8 @@ function stop(obj,~) % TOSTR Returns a string that describes the object succintly % % See Also INIT - s = sprintf('"%s" on %s/%s (acqLive, initial delay %.2f)', obj.Name, ... - obj.DaqDeviceID, obj.DaqChannelID, obj.InitialDelay); + s = sprintf('"%s" on %s/%s (acqLive, initial delay %.2f, pulse duration %.2f)',... + obj.Name, obj.DaqDeviceID, obj.DaqChannelID, obj.InitialDelay, obj.PulseDuration); end end diff --git a/+hw/TLOutputChrono.m b/+hw/TLOutputChrono.m index 059d3a85..373cad10 100644 --- a/+hw/TLOutputChrono.m +++ b/+hw/TLOutputChrono.m @@ -32,11 +32,25 @@ end methods - function obj = TLOutputChrono(name, daqDeviceID, daqChannelID) + function obj = TLOutputChrono(hw) % TLOUTPUTCHRONO Constructor method - obj.Name = name; - obj.DaqDeviceID = daqDeviceID; - obj.DaqChannelID = daqChannelID; + % Can take the struct form of a previous instance (as saved in the + % Timeline hw struct) to intantiate a new object with the same + % properties. + % + % See Also HW.TIMELINE + if nargin + obj.Name = hw.Name; + obj.DaqDeviceID = hw.DaqDeviceID; + obj.DaqVendor = hw.DaqVendor; + obj.DaqChannelID = hw.DaqChannelID; + obj.Enable = hw.Enable; + obj.Verbose = hw.Verbose; + else % Some safe defaults + obj.Name = 'Chrono'; + obj.DaqDeviceID = 'Dev1'; + obj.DaqChannelID = 'port1/line0'; + end end function init(obj, timeline) diff --git a/+hw/TLOutputClock.m b/+hw/TLOutputClock.m index 4784f531..b258ae9e 100644 --- a/+hw/TLOutputClock.m +++ b/+hw/TLOutputClock.m @@ -19,7 +19,7 @@ properties DaqDeviceID % The name of the DAQ device ID, e.g. 'Dev1', see DAQ.GETDEVICES - DaqChannelID % The name of the DAQ channel ID, e.g. 'port1/line0', see DAQ.GETDEVICES + DaqChannelID % The name of the DAQ channel ID, e.g. 'ctr0', see DAQ.GETDEVICES DaqVendor = 'ni' % Name of the DAQ vendor InitialDelay = 0 % delay from session start to clock output Frequency = 60; % Hz, of the clocking pulse @@ -31,11 +31,28 @@ end methods - function obj = TLOutputClock(name, daqDeviceID, daqChannelID) + function obj = TLOutputClock(hw) % TLOUTPUTCHRONO Constructor method - obj.Name = name; - obj.DaqDeviceID = daqDeviceID; - obj.DaqChannelID = daqChannelID; + % Can take the struct form of a previous instance (as saved in the + % Timeline hw struct) to intantiate a new object with the same + % properties. + % + % See Also HW.TIMELINE + if nargin + obj.Name = hw.Name; + obj.DaqDeviceID = hw.DaqDeviceID; + obj.DaqVendor = hw.DaqVendor; + obj.DaqChannelID = hw.DaqChannelID; + obj.InitialDelay = hw.InitialDelay; + obj.Frequency = hw.Frequency; + obj.DutyCycle = hw.DutyCycle; + obj.Enable = hw.Enable; + obj.Verbose = hw.Verbose; + else % Some safe defaults + obj.Name = 'Clock'; + obj.DaqDeviceID = 'Dev1'; + obj.DaqChannelID = 'ctr0'; + end end function init(obj, ~) diff --git a/+hw/Timeline.m b/+hw/Timeline.m index 7d650fa6..6b208f78 100644 --- a/+hw/Timeline.m +++ b/+hw/Timeline.m @@ -70,19 +70,20 @@ DaqIds = 'Dev1' % Device ID can be found with daq.getDevices() DaqSampleRate = 1000 % rate at which daq aquires data in Hz, see Rate DaqSamplesPerNotify % determines the number of data samples to be processed each time, see Timeline.process(), constructor and NotifyWhenDataAvailableExceeds - Outputs = hw.TLOutputChrono('chrono', 'Dev1', 'port1/line0') % array of output classes, defining any signals you desire to be sent from the daq. See Also HW.TLOUTPUT, HW.TLOUTPUTCLOCK + Outputs % array of output classes, defining any signals you desire to be sent from the daq. See Also HW.TLOUTPUT, HW.TLOUTPUTCLOCK Inputs = struct('name', 'chrono',... 'arrayColumn', -1,... % -1 is default indicating unused, this is update when the channels are added during tl.start() 'daqChannelID', 'ai0',... 'measurement', 'Voltage',... - 'terminalConfig', 'SingleEnded') + 'terminalConfig', 'SingleEnded',... + 'figureScale', 1) UseInputs = {'chrono'} % array of inputs to record while tl is running StopDelay = 2 % currently pauses for at least 2 secs as 'hack' before stopping main DAQ session MaxExpectedDuration = 2*60*60 % expected experiment time so data structure is initialised to sensible size (in secs) AquiredDataType = 'double' % default data type for the acquired data array (i.e. Data.rawDAQData) UseTimeline = false % used by expServer. If true, timeline is started by default (otherwise can be toggled with the t key) LivePlot = false % if true the data are plotted as the data are aquired - LivePlotParams = []; + FigureScale = []; % figure position in normalized units, default is [0 0 1 1] (full screen) WriteBufferToDisk = false % if true the data buffer is written to disk as they're aquired NB: in the future this will happen by default end @@ -112,11 +113,17 @@ obj.DaqSamplesPerNotify = 1/obj.SamplingInterval; % calculate DaqSamplesPerNotify if nargin % if old tl hardware struct provided, use these to populate properties + % Configure the inputs obj.Inputs = hw.inputs; obj.DaqVendor = hw.daqVendor; obj.DaqIds = hw.daqDevice; obj.DaqSampleRate = hw.daqSampleRate; obj.DaqSamplesPerNotify = hw.daqSamplesPerNotify; + % Configure the outputs + outputs = catStructs(hw.Outputs); + obj.Outputs = objfun(@(o)eval([o.Class '(o)']), outputs, 'Uni', false); + obj.Outputs = [obj.Outputs{:}]; + else end end @@ -231,7 +238,8 @@ function record(obj, name, event, t) % See also TL.PTBSECSTOTIMELINE(). if nargin < 2; strict = true; end if obj.IsRunning - secs = GetSecs - obj.CurrSysTimeTimelineOffset; + idx = arrayfun(@(out)isa(out, 'hw.TLOutputChrono'), obj.Outputs); + secs = GetSecs - obj.Outputs(idx).CurrSysTimeTimelineOffset; elseif strict error('Tried to use Timeline clock when Timeline is not running'); else @@ -247,7 +255,8 @@ function record(obj, name, event, t) % from Pyschtoolbox's functions and converts to Timeline-relative time. % See also TL.TIME(). assert(obj.IsRunning, 'Timeline is not running.'); - secs = secs - obj.CurrSysTimeTimelineOffset; + idx = arrayfun(@(out)isa(out, 'hw.TLOutputChrono'), obj.Outputs); + secs = secs - obj.Outputs(idx).CurrSysTimeTimelineOffset; end function addInput(obj, name, channelID, measurement, terminalConfig, use) @@ -385,22 +394,22 @@ function stop(obj) outputClasses = arrayfun(@class, obj.Outputs, 'uni', false); chronoChan = []; nextChrono = []; acqLiveChan = []; useClock = false; clockF = []; clockD = []; LastClockSentSysTime = []; CurrSysTimeTimelineOffset = []; - chronoOutputIdx = find(strcmp(outputClasses, 'hw.tlOutputChrono'),1); + chronoOutputIdx = find(strcmp(outputClasses, 'hw.TLOutputChrono'),1); if ~isempty(chronoOutputIdx) - chronoChan = obj.Outputs(chronoOutputIdx).daqChannelID; + chronoChan = obj.Outputs(chronoOutputIdx).DaqChannelID; nextChrono = obj.Outputs(chronoOutputIdx).NextChronoSign; LastClockSentSysTime = obj.Outputs(chronoOutputIdx).LastClockSentSysTime; CurrSysTimeTimelineOffset = obj.Outputs(chronoOutputIdx).CurrSysTimeTimelineOffset; end - acqLiveOutputIdx = find(strcmp(outputClasses, 'hw.tlOutputAcqLive'),1); + acqLiveOutputIdx = find(strcmp(outputClasses, 'hw.TLOutputAcqLive'),1); if ~isempty(acqLiveOutputIdx) - acqLiveChan = obj.Outputs(acqLiveOutputIdx).daqChannelID; + acqLiveChan = obj.Outputs(acqLiveOutputIdx).DaqChannelID; end - clockOutputIdx = find(strcmp(outputClasses, 'hw.tlOutputClock'),1); + clockOutputIdx = find(strcmp(outputClasses, 'hw.TLOutputClock'),1); if ~isempty(clockOutputIdx) useClock = true; - clockF = obj.Outputs(clockOutputIdx).frequency; - clockD = obj.Outputs(clockOutputIdx).dutyCycle; + clockF = obj.Outputs(clockOutputIdx).Frequency; + clockD = obj.Outputs(clockOutputIdx).DutyCycle; end % legacy metadata @@ -422,7 +431,7 @@ function stop(obj) warning('off', 'MATLAB:structOnObject'); % sorry, don't care for outIdx = 1:numel(obj.Outputs) s = struct(obj.Outputs(outIdx)); - s.class = class(obj.Outputs(outIdx)); + s.Class = class(obj.Outputs(outIdx)); obj.Data.hw.Outputs{outIdx} = s; end warning('on', 'MATLAB:structOnObject'); @@ -588,10 +597,10 @@ function livePlot(obj, data) % TL.LIVEPLOT(source, event) plots the data aquired by the % DAQ while the PlotLive property is true. if isempty(obj.Axes) - f = figure(); % create a figure for plotting aquired data + f = figure('Units', 'Normalized', 'Position', [0 0 1 1]); % create a figure for plotting aquired data obj.Axes = gca; % store a handle to the axes - if isfield(obj.LivePlotParams, 'figPosition') && ~isempty(obj.LivePlotParams.figPosition) - set(f, 'Position', obj.LivePlotParams.figPosition); % set the figure position + if isprop(obj, 'FigurePosition') && ~isempty(obj.FigurePosition) + set(f, 'Position', obj.FigurePosition); % set the figure position end end From 789ed31267e5e81747d535e219596e5c073f6570 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 1 Feb 2018 13:50:43 +0000 Subject: [PATCH 059/393] Deletes ClockChan on stop --- +hw/TLOutputClock.m | 1 + 1 file changed, 1 insertion(+) diff --git a/+hw/TLOutputClock.m b/+hw/TLOutputClock.m index b258ae9e..624bb764 100644 --- a/+hw/TLOutputClock.m +++ b/+hw/TLOutputClock.m @@ -109,6 +109,7 @@ function stop(obj,~) stop(obj.Session); release(obj.Session); obj.Session = []; + obj.ClockChan = []; end end From 6b007d6e0b37607ac70c7e57d65ba44bf7c72c20 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 1 Feb 2018 14:07:42 +0000 Subject: [PATCH 060/393] Change to axes scaling --- +hw/Timeline.m | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/+hw/Timeline.m b/+hw/Timeline.m index 6b208f78..a4942e07 100644 --- a/+hw/Timeline.m +++ b/+hw/Timeline.m @@ -76,7 +76,7 @@ 'daqChannelID', 'ai0',... 'measurement', 'Voltage',... 'terminalConfig', 'SingleEnded',... - 'figureScale', 1) + 'axesScale', 1) % multiplicative vertical scaling for when live plotting the input UseInputs = {'chrono'} % array of inputs to record while tl is running StopDelay = 2 % currently pauses for at least 2 secs as 'hack' before stopping main DAQ session MaxExpectedDuration = 2*60*60 % expected experiment time so data structure is initialised to sensible size (in secs) @@ -259,7 +259,8 @@ function record(obj, name, event, t) secs = secs - obj.Outputs(idx).CurrSysTimeTimelineOffset; end - function addInput(obj, name, channelID, measurement, terminalConfig, use) + function addInput(obj, name, channelID, measurement,... + terminalConfig, axesScale, use) % Add a new input to the object's Input property % ADDINPUT(name, channelID, measurement, terminalConfig, use) % adds a new input 'name' to the Inputs list. If use is @@ -269,8 +270,11 @@ function addInput(obj, name, channelID, measurement, terminalConfig, use) % DAQ default for that port if nargin < 5; terminalConfig = []; end + % if use is not specified, assume user wants normal scaling + if nargin < 6; axesScale = 1; end + % if use is not specified, assume user wants to record input - if nargin < 6; use = true; end + if nargin < 7; use = true; end assert(~any(strcmp(name, {obj.Inputs.name})),... 'An input by the name of ''%s'' has already been added.', name); @@ -297,7 +301,8 @@ function addInput(obj, name, channelID, measurement, terminalConfig, use) 'arrayColumn', -1,... % -1 is default indicating unused 'daqChannelID', channelID,... 'measurement', measurement,... - 'terminalConfig', terminalConfig); + 'terminalConfig', terminalConfig,... + 'axesScale', axesScale); obj.Inputs = [obj.Inputs s]; % add the new input if use; obj.UseInputs = [obj.UseInputs {name}]; end % add to UseInputs @@ -616,10 +621,7 @@ function livePlot(obj, data) % (multiplicative) and can be set manually in the config. A % nicer future version would put a scroll wheel callback on the % figure and scale by scrolling the one that's hovered over - scales = ones(1, nChans); - if isfield(obj.LivePlotParams, 'figScales') && ~isempty(obj.LivePlotParams.figScales) - scales(1:numel(obj.LivePlotParams.figScales)) = obj.LivePlotParams.figScales; - end + scales = pick([obj.Inputs.axesScale], cellfun(@(x)find(strcmp({obj.Inputs.name}, x),1), obj.UseInputs)); traces = get(obj.Axes, 'Children'); if isempty(traces) From af54939731d905dbc2efd3e2f87defbc8cb292a8 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 1 Feb 2018 14:26:40 +0000 Subject: [PATCH 061/393] Better output defaults --- +hw/Timeline.m | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/+hw/Timeline.m b/+hw/Timeline.m index a4942e07..a1b7cedd 100644 --- a/+hw/Timeline.m +++ b/+hw/Timeline.m @@ -70,7 +70,7 @@ DaqIds = 'Dev1' % Device ID can be found with daq.getDevices() DaqSampleRate = 1000 % rate at which daq aquires data in Hz, see Rate DaqSamplesPerNotify % determines the number of data samples to be processed each time, see Timeline.process(), constructor and NotifyWhenDataAvailableExceeds - Outputs % array of output classes, defining any signals you desire to be sent from the daq. See Also HW.TLOUTPUT, HW.TLOUTPUTCLOCK + Outputs = hw.TLOutputChrono % array of output classes, defining any signals you desire to be sent from the daq. See Also HW.TLOUTPUT, HW.TLOUTPUTCLOCK Inputs = struct('name', 'chrono',... 'arrayColumn', -1,... % -1 is default indicating unused, this is update when the channels are added during tl.start() 'daqChannelID', 'ai0',... @@ -123,7 +123,6 @@ outputs = catStructs(hw.Outputs); obj.Outputs = objfun(@(o)eval([o.Class '(o)']), outputs, 'Uni', false); obj.Outputs = [obj.Outputs{:}]; - else end end From da45bd4195cf9b6f343577e1d89c201ae6a2a82f Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 1 Feb 2018 17:07:24 +0000 Subject: [PATCH 062/393] Stopping outputs now happens after stop delay! --- +hw/Timeline.m | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/+hw/Timeline.m b/+hw/Timeline.m index a1b7cedd..5b0a2de3 100644 --- a/+hw/Timeline.m +++ b/+hw/Timeline.m @@ -367,11 +367,10 @@ function stop(obj) warning('Nothing to do, Timeline is not running!') return end - - % kill acquisition output signals - arrayfun(@stop, obj.Outputs) - pause(obj.StopDelay) + + % stop acquisition output signals + arrayfun(@stop, obj.Outputs) % stop actual DAQ aquisition stop(obj.Sessions('main')); From 393c3468c631f27db0f52947d3b4fb70748a5dcd Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 1 Feb 2018 17:39:16 +0000 Subject: [PATCH 063/393] Updated the CW defaults to be more reasonable --- cortexlab/+exp/choiceWorldParams.m | 33 +++++++++++++++--------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/cortexlab/+exp/choiceWorldParams.m b/cortexlab/+exp/choiceWorldParams.m index 377678fc..cbd07930 100644 --- a/cortexlab/+exp/choiceWorldParams.m +++ b/cortexlab/+exp/choiceWorldParams.m @@ -25,17 +25,17 @@ '''Reward'' volume delivered after stimulus onset'); % trial temporal structure -setValue('onsetVisStimDelay', 0.1, 's',... +setValue('onsetVisStimDelay', 0, 's',... 'Duration between the start of the onset tone and visual stimulus presentation'); setValue('onsetToneDuration', 0.1, 's',... 'Duration of the onset tone'); setValue('onsetToneRampDuration', 0.01, 's',... 'Duration of the onset tone amplitude ramp (up and down each this length)'); -setValue('preStimQuiescentPeriod', [2; 3], 's',... +setValue('preStimQuiescentPeriod', [0.2; 0.6], 's',... 'Required period of no input before stimulus presentation'); -setValue('bgCueDelay', 0.3, 's',... +setValue('bgCueDelay', 0, 's',... 'Delay period between target column presentation and grating cue'); -setValue('cueInteractiveDelay', 1, 's',... +setValue('cueInteractiveDelay', 0, 's',... 'Delay period between grating cue presentation and interactive phase'); setValue('hideCueDelay', inf, 's',... 'Delay period between cue presentation onset to hide it'); @@ -47,7 +47,7 @@ 'Duration of negative feedback phase (with stimulus locked in response position)'); setValue('feedbackDeliveryDelay', 0, 's',... 'Delay period between response completion and feedback provided'); -setValue('negFeedbackSoundDuration', 2, 's',... +setValue('negFeedbackSoundDuration', 0.5, 's',... 'Duration of negative feedback noise burst'); setValue('interTrialDelay', 0, 's', 'Delay between trials'); setValue('waitOnEarlyResponse', false, 'logical',... @@ -58,7 +58,7 @@ % visual stimulus characteristics setValue('targetWidth', 35, '�',... 'Width of target columns (visual angle)'); -setValue('distBetweenTargets', 80, '�',... +setValue('distBetweenTargets', 180, '�',... 'Width between target column centres (visual angle)'); setValue('targetAltitude', 0, '�',... 'Visual angle of target centre above horizon'); @@ -66,31 +66,31 @@ 'Horizontal translation of targets to reach response threshold (visual angle)'); setValue('cueSpatialFrequency', 0.1, 'cyc/�',... 'Spatial frequency of grating cue at the initial location on the horizon'); -setValue('cueSigma', [5; 5], '�',... +setValue('cueSigma', [9; 9], '�',... 'Size (w,h) of the grating, in terms of Gabor ? parameter (visual angle)'); -setValue('targetOrientation', 90, '�',... +setValue('targetOrientation', 45, '�',... 'Orientation of gabor grating (cw from horizontal)'); setValue('bgColour', 0*255*ones(3, 1), 'rgb',... 'Colour of background area'); setValue('targetColour', 0.5*255*ones(3, 1), 'rgb',... 'Colour of target columns'); -setValue('visWheelGain', 3, '�/mm',... +setValue('visWheelGain', 3.5, '�/mm',... 'Visual stimulus translation per movement at wheel surface (for stimuli ahead)'); % audio setValue('onsetToneMaxAmp', 1, 'normalised',... 'Maximum amplitude of onset tone'); -setValue('onsetToneFreq', 12e3, 'Hz',... +setValue('onsetToneFreq', 11e3, 'Hz',... 'Frequency of the onset tone'); -setValue('negFeedbackSoundAmp', 0.025, 'normalised',... +setValue('negFeedbackSoundAmp', 0.01, 'normalised',... 'Amplitude of negative feedback noise burst'); % misc -setValue('quiescenceThreshold', 1, 'sensor units',... +setValue('quiescenceThreshold', 10, 'sensor units',... 'Input movement must be under this threshold to count as quiescent'); %% configure trial-specific parameters -contrast = [0.5 0.4 0.2 0.1 0.05 0]; % contrast list to use on one side or the other +contrast = [1 0.5 0.25 0.12 0.06 0]; % contrast list to use on one side or the other % compute contrast one each target - ones side has contrast, other has zero targetCon = [contrast, zeros(1, numel(contrast));... zeros(1, numel(contrast)), contrast]; @@ -100,12 +100,11 @@ -ones(1, numel(contrast)), ones(1, numel(contrast)); ... -ones(1, numel(contrast) - 1) 1 -ones(1, numel(contrast) - 1) 1]; % repeat all incorrect trials except zero contrast ones -repIncorrect = abs(diff(targetCon)) > 0; +repIncorrect = abs(diff(targetCon)) > 0.25; % by default only use only the 50% contrast condition -useConditions = abs(diff(targetCon)) == 0.5 | abs(diff(targetCon)) == 0.2... - | abs(diff(targetCon)) == 0.1; +useConditions = abs(diff(targetCon)) == 0.5 | abs(diff(targetCon)) == 1; % uniform repeats for at least 300 trials -nReps = ceil(300*useConditions./sum(useConditions)); +nReps = ceil(1000*useConditions./sum(useConditions)); setValue('visCueContrast', targetCon, 'normalised', 'Contrast of grating cue at each target'); setValue('feedbackForResponse', feedback, 'normalised', 'Feedback given for each target'); setValue('repeatIncorrectTrial', repIncorrect, 'logical',... From d6c5f2a38e23aa875ccadfc7fe6793a565a4bf73 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 1 Feb 2018 17:59:57 +0000 Subject: [PATCH 064/393] Little change to doc --- +hw/Timeline.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/+hw/Timeline.m b/+hw/Timeline.m index 5b0a2de3..e03e212a 100644 --- a/+hw/Timeline.m +++ b/+hw/Timeline.m @@ -40,7 +40,7 @@ % %Set tl to be started by default % timeline.UseTimeline = true; % %To set up chrono a wire must bridge the terminals defined in -% timeline.Outputs.daqChannelID and timeline.Inputs.daqChannelID +% timeline.Outputs(1).DaqChannelID and timeline.Inputs(1).daqChannelID % timeline.wiringInfo('chrono'); % %Add the rotary encoder % timeline.addInput('rotaryEncoder', 'ctr0', 'Position'); From 2adcb1546775688e80d6b20eaa184da0394f080e Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 2 Feb 2018 10:42:45 +0000 Subject: [PATCH 065/393] Added timer function for executing delayed outputs --- +hw/TLOutputAcqLive.m | 21 ++++++++++++++++++++- +hw/TLOutputClock.m | 21 +++++++++++++++++++-- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/+hw/TLOutputAcqLive.m b/+hw/TLOutputAcqLive.m index faf6a999..a08b2d4b 100644 --- a/+hw/TLOutputAcqLive.m +++ b/+hw/TLOutputAcqLive.m @@ -29,6 +29,10 @@ PulseDuration = Inf; % sec, time that the pulse is on at beginning and end end + properties (Transient, Access = private) + Timer + end + methods function obj = TLOutputAcqLive(hw) % TLOUTPUTCHRONO Constructor method @@ -69,6 +73,13 @@ function init(obj, ~) obj.Session.addDigitalChannel(obj.DaqDeviceID, obj.DaqChannelID, 'OutputOnly'); warning('on', 'daq:Session:onDemandOnlyChannelsAdded'); outputSingleScan(obj.Session, false); % start in the off/false state + % If the initial delay is greater than zero, create a timer for + % starting the signal late + if obj.InitialDelay > 0 + obj.Timer = timer('StartDelay', obj.InitialDelay); + obj.Timer.TimerFcn = @(~,~)obj.start(); + obj.Timer.StopFcn = @(src,~)delete(src); + end end end @@ -79,8 +90,15 @@ function start(obj, ~) % % See Also HW.TIMELINE/START if obj.Enable + % If the initial delay is greater than 0 and the timer is empty, + % create and start the timer + if ~isempty(obj.Timer) && obj.InitialDelay > 0 ... + && strcmp(obj.Timer.Running, 'off') + start(obj.Timer); % wait for some duration before starting + return + end + if obj.Verbose; fprintf(1, 'start %s\n', obj.Name); end - pause(obj.InitialDelay); % wait for some duration before starting outputSingleScan(obj.Session, true); % set digital output true: acquisition is "live" if obj.PulseDuration ~= Inf pause(obj.PulseDuration); @@ -118,6 +136,7 @@ function stop(obj,~) stop(obj.Session); release(obj.Session); obj.Session = []; + obj.Timer = []; end end diff --git a/+hw/TLOutputClock.m b/+hw/TLOutputClock.m index 624bb764..487b8e0c 100644 --- a/+hw/TLOutputClock.m +++ b/+hw/TLOutputClock.m @@ -28,6 +28,7 @@ properties (Transient, Hidden, Access = protected) ClockChan % Holds an instance of the PulseGeneration channel + Timer end methods @@ -71,6 +72,14 @@ function init(obj, ~) clocked.DutyCycle = obj.DutyCycle; clocked.InitialDelay = obj.InitialDelay; obj.ClockChan = clocked; + + % If the initial delay is greater than zero, create a timer for + % starting the signal late + if obj.InitialDelay > 0 + obj.Timer = timer('StartDelay', obj.InitialDelay); + obj.Timer.TimerFcn = @(~,~)obj.start(); + obj.Timer.StopFcn = @(src,~)delete(src); + end end end @@ -81,8 +90,15 @@ function start(obj, ~) % % See Also HW.TIMELINE/START if obj.Enable - if obj.Verbose; fprintf(1, 'start %s\n', obj.Name); end - startBackground(obj.Session); + % If the initial delay is greater than 0 and the timer is empty, + % create and start the timer + if ~isempty(obj.Timer) && obj.InitialDelay > 0 ... + && strcmp(obj.Timer.Running, 'off') + start(obj.Timer); % wait for some duration before starting + return + end + if obj.Verbose; fprintf(1, 'start %s\n', obj.Name); end + startBackground(obj.Session); end end @@ -110,6 +126,7 @@ function stop(obj,~) release(obj.Session); obj.Session = []; obj.ClockChan = []; + obj.Timer = []; end end From 5689c4b3ce8f6220cf0922f5db34cbf601f673b5 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 2 Feb 2018 19:19:21 +0000 Subject: [PATCH 066/393] Some minor changes dar.newExp now doesn't try to create any sessions when not logged in --- +dat/newExp.m | 131 +++++++++++++++++++++--------------------- +hw/TLOutputAcqLive.m | 4 +- +hw/TLOutputClock.m | 6 +- 3 files changed, 70 insertions(+), 71 deletions(-) diff --git a/+dat/newExp.m b/+dat/newExp.m index 6e2c0720..6a86e07f 100644 --- a/+dat/newExp.m +++ b/+dat/newExp.m @@ -1,9 +1,9 @@ function [expRef, expSeq, url] = newExp(subject, expDate, expParams, AlyxInstance) %DAT.NEWEXP Create a new unique experiment in the database -% [ref, seq, url] = DAT.NEWEXP(subject, expDate, expParams[, AlyxInstance]) +% [ref, seq, url] = DAT.NEWEXP(subject, expDate, expParams[, AlyxInstance]) % Create a new experiment by creating the relevant folder tree in the % local and main data repositories in the following format: -% +% % subject/ % |_ YYYY-MM-DD/ % |_ expSeq/ @@ -66,14 +66,14 @@ % now make the folder(s) to hold the new experiment assert(all(cellfun(@(p) mkdir(p), expPath)), 'Creating experiment directories failed'); -if ~strcmp(subject,'default') % Ignore fake subject +if ~strcmp(subject, 'default') % Ignore fake subject % if the Alyx Instance is set, find or create BASE session expDate = alyx.datestr(expDate); % date in Alyx format if ~isempty(AlyxInstance) % Get list of base sessions sessions = alyx.getData(AlyxInstance,... - ['sessions?type=Base&subject=' subject]); - + ['sessions?type=Base&subject=' subject]); + %If the date of this latest base session is not the same date as %today, then create a new base session for today if isempty(sessions) || ~strcmp(sessions{end}.start_time(1:10), expDate(1:10)) @@ -83,75 +83,74 @@ d.narrative = 'auto-generated session'; d.start_time = expDate; d.type = 'Base'; -% d.users = {AlyxInstance.username}; - + % d.users = {AlyxInstance.username}; + base_submit = alyx.postData(AlyxInstance, 'sessions', d); assert(isfield(base_submit,'subject'),... - 'Submitted base session did not return appropriate values'); - + 'Submitted base session did not return appropriate values'); + %Now retrieve the sessions again sessions = alyx.getData(AlyxInstance,... - ['sessions?type=Base&subject=' subject]); + ['sessions?type=Base&subject=' subject]); end latest_base = sessions{end}; - else % If not logged in to Alyx... - latest_base.url = []; % set the base url to null - end - %Now create a new SUBSESSION, using the same experiment number - d = struct; - d.subject = subject; - d.procedures = {'Behavior training/tasks'}; - d.narrative = 'auto-generated session'; - d.start_time = expDate; - d.type = 'Experiment'; - d.parent_session = latest_base.url; - d.number = expSeq; -% d.users = {AlyxInstance.username}; + %Now create a new SUBSESSION, using the same experiment number + d = struct; + d.subject = subject; + d.procedures = {'Behavior training/tasks'}; + d.narrative = 'auto-generated session'; + d.start_time = expDate; + d.type = 'Experiment'; + d.parent_session = latest_base.url; + d.number = expSeq; + % d.users = {AlyxInstance.username}; - subsession = alyx.postData(AlyxInstance, 'sessions', d); - assert(isfield(subsession,'subject'),... - 'Failed to create new sub-session in Alyx for %s', subject); - url = subsession.url; -else - url = []; -end - -% if the parameters had an experiment definition function, save a copy in -% the experiment's folder -if isfield(expParams, 'defFunction') - assert(file.exists(expParams.defFunction),... - 'Experiment definition function does not exist: %s', expParams.defFunction); - assert(all(cellfun(@(p)copyfile(expParams.defFunction, p),... - dat.expFilePath(expRef, 'expDefFun'))),... - 'Copying definition function to experiment folders failed'); -end - -% now save the experiment parameters variable -superSave(dat.expFilePath(expRef, 'parameters'), struct('parameters', expParams)); - -try % save a copy of parameters in json - % First, change all functions to strings - f_idx = structfun(@(s)isa(s, 'function_handle'), expParams); - fields = fieldnames(expParams); - paramCell = struct2cell(expParams); - paramCell(f_idx) = cellfun(@func2str, paramCell(f_idx),'UniformOutput', false); - expParams = cell2struct(paramCell, fields); - % Generate JSON path and save - jsonPath = fullfile(fileparts(dat.expFilePath(expRef, 'parameters', 'master')),... - [expRef, '_parameters.json']); - savejson('parameters', expParams, jsonPath); - % Register our JSON parameter set to Alyx - if ~strcmp(subject,'default') - alyx.registerFile(jsonPath, 'json', url, 'Parameters', [], AlyxInstance); + try + subsession = alyx.postData(AlyxInstance, 'sessions', d); + url = subsession.url; + catch + url = []; + end + else % If not logged in to Alyx... + url = []; % set the base url to null end -catch ex - warning(ex.identifier, 'Failed to save paramters as JSON: %s.\n Registering mat file instead', ex.message) - % Register our parameter set to Alyx - if ~strcmp(subject,'default') - alyx.registerFile(dat.expFilePath(expRef, 'parameters', 'master'), 'mat',... - url, 'Parameters', [], AlyxInstance); + + % if the parameters had an experiment definition function, save a copy in + % the experiment's folder + if isfield(expParams, 'defFunction') + assert(file.exists(expParams.defFunction),... + 'Experiment definition function does not exist: %s', expParams.defFunction); + assert(all(cellfun(@(p)copyfile(expParams.defFunction, p),... + dat.expFilePath(expRef, 'expDefFun'))),... + 'Copying definition function to experiment folders failed'); end -end - + + % now save the experiment parameters variable + superSave(dat.expFilePath(expRef, 'parameters'), struct('parameters', expParams)); + + try % save a copy of parameters in json + % First, change all functions to strings + f_idx = structfun(@(s)isa(s, 'function_handle'), expParams); + fields = fieldnames(expParams); + paramCell = struct2cell(expParams); + paramCell(f_idx) = cellfun(@func2str, paramCell(f_idx),'UniformOutput', false); + expParams = cell2struct(paramCell, fields); + % Generate JSON path and save + jsonPath = fullfile(fileparts(dat.expFilePath(expRef, 'parameters', 'master')),... + [expRef, '_parameters.json']); + savejson('parameters', expParams, jsonPath); + % Register our JSON parameter set to Alyx + if ~strcmp(subject,'default') + alyx.registerFile(jsonPath, 'json', url, 'Parameters', [], AlyxInstance); + end + catch ex + warning(ex.identifier, 'Failed to save paramters as JSON: %s.\n Registering mat file instead', ex.message) + % Register our parameter set to Alyx + if ~strcmp(subject,'default') + alyx.registerFile(dat.expFilePath(expRef, 'parameters', 'master'), 'mat',... + url, 'Parameters', [], AlyxInstance); + end + end + end \ No newline at end of file diff --git a/+hw/TLOutputAcqLive.m b/+hw/TLOutputAcqLive.m index a08b2d4b..6d2cd20e 100644 --- a/+hw/TLOutputAcqLive.m +++ b/+hw/TLOutputAcqLive.m @@ -25,8 +25,8 @@ DaqDeviceID % The name of the DAQ device ID, e.g. 'Dev1', see DAQ.GETDEVICES DaqChannelID % The name of the DAQ channel ID, e.g. 'port1/line0', see DAQ.GETDEVICES DaqVendor = 'ni' % Name of the DAQ vendor - InitialDelay = 0 % sec, time to wait before starting - PulseDuration = Inf; % sec, time that the pulse is on at beginning and end + InitialDelay double {mustBeNonnegative} = 0 % sec, time to wait before starting + PulseDuration {mustBeNonnegative} = Inf; % sec, time that the pulse is on at beginning and end end properties (Transient, Access = private) diff --git a/+hw/TLOutputClock.m b/+hw/TLOutputClock.m index 487b8e0c..74830732 100644 --- a/+hw/TLOutputClock.m +++ b/+hw/TLOutputClock.m @@ -21,9 +21,9 @@ DaqDeviceID % The name of the DAQ device ID, e.g. 'Dev1', see DAQ.GETDEVICES DaqChannelID % The name of the DAQ channel ID, e.g. 'ctr0', see DAQ.GETDEVICES DaqVendor = 'ni' % Name of the DAQ vendor - InitialDelay = 0 % delay from session start to clock output - Frequency = 60; % Hz, of the clocking pulse - DutyCycle = 0.2; % proportion of each cycle that the pulse is "true" + InitialDelay double {mustBeNonnegative} = 0 % delay from session start to clock output + Frequency double = 60; % Hz, of the clocking pulse + DutyCycle double = 0.2; % proportion of each cycle that the pulse is "true" end properties (Transient, Hidden, Access = protected) From e0a45abdd9c54026306ade2286b11fa79ff8184a Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 2 Feb 2018 23:27:48 +0000 Subject: [PATCH 067/393] MC and expServer Alyx object compatibility --- +dat/addLogEntry.m | 1 + +dat/updateLogEntry.m | 6 +- +eui/AlyxPanel.m | 1183 +++++++++++++++--------------- +eui/ExpPanel.m | 15 +- +eui/MControl.m | 23 +- +exp/Experiment.m | 18 +- +exp/SignalsExp.m | 100 ++- +exp/StartServices.m | 11 +- +hw/Timeline.m | 8 +- +srv/StimulusControl.m | 6 +- +srv/expServer.m | 4 +- cortexlab/+io/MpepUDPDataHosts.m | 69 +- 12 files changed, 746 insertions(+), 698 deletions(-) diff --git a/+dat/addLogEntry.m b/+dat/addLogEntry.m index fcbeb676..7ef7a240 100644 --- a/+dat/addLogEntry.m +++ b/+dat/addLogEntry.m @@ -26,6 +26,7 @@ %% create and store entry e = entry(nextidx); log(nextidx) = e; +% Store an instance of Alyx for narrative registration if nargin > 5; e.AlyxInstance = AlyxInstance; end %% store updated log to *all* repos locations diff --git a/+dat/updateLogEntry.m b/+dat/updateLogEntry.m index fefad909..bf3e94ee 100644 --- a/+dat/updateLogEntry.m +++ b/+dat/updateLogEntry.m @@ -11,10 +11,8 @@ function updateLogEntry(subject, id, newEntry) % 2013-03 CB created if isfield(newEntry, 'AlyxInstance')&&~isempty(newEntry.comments) - data = struct('subject', dat.parseExpRef(newEntry.value.ref),... - 'narrative', strrep(mat2DStrTo1D(newEntry.comments),newline,'\n')); - alyx.putData(newEntry.AlyxInstance,... - newEntry.AlyxInstance.subsessionURL, data); + % Update session narrative on Alyx + newEntry.AlyxInstance.updateNarrative(subject, obj.LogEntry.comments); newEntry = rmfield(newEntry, 'AlyxInstance'); end diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index 6b9704b5..86349026 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -1,626 +1,605 @@ classdef AlyxPanel < handle - % EUI.ALYXPANEL A GUI for interating with the Alyx database - % This class is emplyed by mc (but may also be used stand-alone) to - % post weights and water administations to the Alyx database. - % - % eui.AlyxPanel() opens a stand-alone GUI. eui.AlyxPanel(parent) - % constructs the panel inside a parent object. - % - % Use the login button to retrieve a token from the database. - % Use the subject drop-down to select the subject. - % Subject weights can be entered using the 'Manual weighing' button. - % Previous weighings and water infomation can be viewed by pressing - % the 'Subject history' button. - % Water administrations can be recorded by entering a value in ml - % into the text box. Pressing return does not post the water, but - % updates the text to the right of the box, showing the amount of - % water remaining (i.e. the amount below the subject's calculated - % minimum requirement for that day. The check box to the right of - % the text box is to indicate whether the water was liquid - % (unchecked) or gel (checked). To post the water to Alyx, press the - % 'Give water' button. - % To post gel for future date (for example weekend hydrogel), Click - % the 'Give gel in future' button and enter in all the values - % starting at tomorrow then the day after, etc. - % The 'All WR subjects' button shows the amount of water remaining - % today for all mice that are currently on water restriction. - % - % The 'default' subject is for testing and is usually ignored. - % - % See also ALYX, EUI.MCONTROL - % - % 2017-03 NS created - % 2017-10 MW made into class - properties (SetAccess = private) - AlyxInstance = []; % A struct containing the database URL, a token and the username of the who is logged in - QueuedWeights = {}; % Holds weighings until someone logs in, to be posted - SubjectList % List of active subjects from database - Subject = 'default' % The name of the currently selected subject - end - - properties (Access = private) - LoggingDisplay % Control for showing log output - RootContainer % Handle of the uix.Panel object named 'Alyx' - NewExpSubject % Drop-down menu subject list - LoginText % Text displaying whether/which user is logged in - LoginButton % Button to log in to Alyx - WaterEntry % Text box for entering the amout of water to give - IsHydrogel % UI checkbox indicating whether to water to be given is in gel form - WaterRequiredText % Handle to text UI element displaying the water required - WaterRemainingText % Handle to text UI element displaying the water remaining - LoginTimer % Timer to keep track of how long the user has been logged in, when this expires the user is automatically logged out - WaterRemaining % Holds the current water required for the selected subject + % EUI.ALYXPANEL A GUI for interating with the Alyx database + % This class is emplyed by mc (but may also be used stand-alone) to + % post weights and water administations to the Alyx database. + % + % eui.AlyxPanel() opens a stand-alone GUI. eui.AlyxPanel(parent) + % constructs the panel inside a parent object. + % + % Use the login button to retrieve a token from the database. + % Use the subject drop-down to select the subject. + % Subject weights can be entered using the 'Manual weighing' button. + % Previous weighings and water infomation can be viewed by pressing + % the 'Subject history' button. + % Water administrations can be recorded by entering a value in ml + % into the text box. Pressing return does not post the water, but + % updates the text to the right of the box, showing the amount of + % water remaining (i.e. the amount below the subject's calculated + % minimum requirement for that day. The check box to the right of + % the text box is to indicate whether the water was liquid + % (unchecked) or gel (checked). To post the water to Alyx, press the + % 'Give water' button. + % To post gel for future date (for example weekend hydrogel), Click + % the 'Give gel in future' button and enter in all the values + % starting at tomorrow then the day after, etc. + % The 'All WR subjects' button shows the amount of water remaining + % today for all mice that are currently on water restriction. + % + % The 'default' subject is for testing and is usually ignored. + % + % See also ALYX, EUI.MCONTROL + % + % 2017-03 NS created + % 2017-10 MW made into class + properties (SetAccess = private) + AlyxInstance = Alyx; % An Alyx object to interfacing with the database + SubjectList % List of active subjects from database + Subject = 'default' % The name of the currently selected subject + end + + properties (Access = private) + LoggingDisplay % Control for showing log output + RootContainer % Handle of the uix.Panel object named 'Alyx' + NewExpSubject % Drop-down menu subject list + LoginText % Text displaying whether/which user is logged in + LoginButton % Button to log in to Alyx + WaterEntry % Text box for entering the amout of water to give + IsHydrogel % UI checkbox indicating whether to water to be given is in gel form + WaterRequiredText % Handle to text UI element displaying the water required + WaterRemainingText % Handle to text UI element displaying the water remaining + LoginTimer % Timer to keep track of how long the user has been logged in, when this expires the user is automatically logged out + WaterRemaining % Holds the current water required for the selected subject + end + + events (NotifyAccess = 'protected') + Connected % Notified when logged in to database + Disconnected % Notified when logged out of database + end + + methods + function obj = AlyxPanel(parent) + % Constructor to build all the UI elements and set callbacks to + % the relevant functions. If a handle to parant UI object is + % not specified, a seperate figure is created. An optional + % handle to a logging display panal may be provided, otherwise + % one is created. + + if ~nargin % No parant object: create new figure + f = figure('Name', 'alyx GUI',... + 'MenuBar', 'none',... + 'Toolbar', 'none',... + 'NumberTitle', 'off',... + 'Units', 'normalized',... + 'OuterPosition', [0.1 0.1 0.4 .4]); + parent = uiextras.VBox('Parent', f,... + 'Visible', 'on'); + % subject selector + sbox = uix.HBox('Parent', parent); + bui.label('Select subject: ', sbox); + obj.NewExpSubject = bui.Selector(sbox, {'default'}); % Subject dropdown box + % set a callback on subject selection so that we can show water + % requirements for new mice as they are selected. This should + % be set by any other GUI that instantiates this object (e.g. + % MControl using this as a panel. + obj.NewExpSubject.addlistener('SelectionChanged', @(src, evt)obj.dispWaterReq(src, evt)); + end + + obj.RootContainer = uix.Panel('Parent', parent, 'Title', 'Alyx'); + alyxbox = uiextras.VBox('Parent', obj.RootContainer); + + loginbox = uix.HBox('Parent', alyxbox); + % Login infomation + obj.LoginText = bui.label('Not logged in', loginbox); + % Button to log in and out of Alyx + obj.LoginButton = uicontrol('Parent', loginbox,... + 'Style', 'pushbutton', ... + 'String', 'Login', ... + 'Enable', 'on',... + 'Callback', @(~,~)obj.login); + loginbox.Widths = [-1 75]; + + waterReqbox = uix.HBox('Parent', alyxbox); + obj.WaterRequiredText = bui.label('Log in to see water requirements', waterReqbox); % water required text + % Button to refresh all data retrieved from Alyx + uicontrol('Parent', waterReqbox,... + 'Style', 'pushbutton', ... + 'String', 'Refresh', ... + 'Enable', 'off',... + 'Callback', @(~,~)obj.dispWaterReq); + waterReqbox.Widths = [-1 75]; + + waterbox = uix.HBox('Parent', alyxbox); + % Button to launch a dialog displaying water and weight info for a given mouse + uicontrol('Parent', waterbox,... + 'Style', 'pushbutton', ... + 'String', 'Subject history', ... + 'Enable', 'off',... + 'Callback', @(~,~)obj.viewSubjectHistory); + % Button to launch a dialog displaying water and weight info for all mice + uicontrol('Parent', waterbox,... + 'Style', 'pushbutton', ... + 'String', 'All WR subjects', ... + 'Enable', 'off',... + 'Callback', @(~,~)obj.viewAllSubjects); + % Button to open a dialog for manually submitting a mouse weight + uicontrol('Parent', waterbox,... + 'Style', 'pushbutton', ... + 'String', 'Manual weighing', ... + 'Enable', 'off',... + 'Callback', @(~,~)obj.recordWeight); + % Button to launch dialog for submitting gel administrations + % for future dates + uicontrol('Parent', waterbox,... + 'Style', 'pushbutton', ... + 'String', 'Give gel in future', ... + 'Enable', 'off',... + 'Callback', @(~,~)obj.giveFutureGel); + % Check box to indicate whether water was gel or liquid + obj.IsHydrogel = uicontrol('Parent', waterbox,... + 'Style', 'checkbox', ... + 'String', 'Hydrogel?', ... + 'HorizontalAlignment', 'right',... + 'Value', true, ... + 'Enable', 'off'); + % Input for submitting amount of water + obj.WaterEntry = uicontrol('Parent', waterbox,... + 'Style', 'edit',... + 'BackgroundColor', [1 1 1],... + 'HorizontalAlignment', 'right',... + 'Enable', 'off',... + 'String', '0.00', ... + 'Callback', @(src, evt)obj.changeWaterText(src, evt)); + % Button for submitting water administration + uicontrol('Parent', waterbox,... + 'Style', 'pushbutton', ... + 'String', 'Give water', ... + 'Enable', 'off',... + 'Callback', @(~,~)giveWater(obj)); + % Label Indicating the amount of water remaining + obj.WaterRemainingText = bui.label('[]', waterbox); + waterbox.Widths = [100 100 100 100 75 75 75 75]; + + launchbox = uix.HBox('Parent', alyxbox); + % Button for launching subject page in browser + uicontrol('Parent', launchbox,... + 'Style', 'pushbutton', ... + 'String', 'Launch webpage for Subject', ... + 'Enable', 'off',... + 'Callback', @(~,~)obj.launchSubjectURL); + % Button for launching (and creating) a session for a given subject in the browser + uicontrol('Parent', launchbox,... + 'Style', 'pushbutton', ... + 'String', 'Launch webpage for Session', ... + 'Enable', 'off',... + 'Callback', @(~,~)obj.launchSessionURL); + + if ~nargin + % logging message area + obj.LoggingDisplay = uicontrol('Parent', parent, 'Style', 'listbox',... + 'Enable', 'inactive', 'String', {}); + parent.Sizes = [50 150 150]; + else + % Use parent's logging display + obj.LoggingDisplay = findobj('Tag', 'Logging Display'); + end end - events (NotifyAccess = 'protected') - Connected % Notified when logged in to database - Disconnected % Notified when logged out of database + function delete(obj) + % To be called before destroying AlyxPanel object. Deletes the + % loggin timer + disp('AlyxPanel destructor called'); + if obj.RootContainer.isvalid; delete(obj.RootContainer); end + if ~isempty(obj.LoginTimer) % If there is a timer object + stop(obj.LoginTimer) % Stop the timer... + delete(obj.LoginTimer) % ... delete it... + obj.LoginTimer = []; % ... and remove it + end end - methods - function obj = AlyxPanel(parent) - % Constructor to build all the UI elements and set callbacks to - % the relevant functions. If a handle to parant UI object is - % not specified, a seperate figure is created. An optional - % handle to a logging display panal may be provided, otherwise - % one is created. - - if ~nargin % No parant object: create new figure - f = figure('Name', 'alyx GUI',... - 'MenuBar', 'none',... - 'Toolbar', 'none',... - 'NumberTitle', 'off',... - 'Units', 'normalized',... - 'OuterPosition', [0.1 0.1 0.4 .4]); - parent = uiextras.VBox('Parent', f,... - 'Visible', 'on'); - % subject selector - sbox = uix.HBox('Parent', parent); - bui.label('Select subject: ', sbox); - obj.NewExpSubject = bui.Selector(sbox, {'default'}); % Subject dropdown box - % set a callback on subject selection so that we can show water - % requirements for new mice as they are selected. This should - % be set by any other GUI that instantiates this object (e.g. - % MControl using this as a panel. - obj.NewExpSubject.addlistener('SelectionChanged', @(src, evt)obj.dispWaterReq(src, evt)); - end - - obj.RootContainer = uix.Panel('Parent', parent, 'Title', 'Alyx'); - alyxbox = uiextras.VBox('Parent', obj.RootContainer); - - loginbox = uix.HBox('Parent', alyxbox); - % Login infomation - obj.LoginText = bui.label('Not logged in', loginbox); - % Button to log in and out of Alyx - obj.LoginButton = uicontrol('Parent', loginbox,... - 'Style', 'pushbutton', ... - 'String', 'Login', ... - 'Enable', 'on',... - 'Callback', @(~,~)obj.login); - loginbox.Widths = [-1 75]; - - waterReqbox = uix.HBox('Parent', alyxbox); - obj.WaterRequiredText = bui.label('Log in to see water requirements', waterReqbox); % water required text - % Button to refresh all data retrieved from Alyx - uicontrol('Parent', waterReqbox,... - 'Style', 'pushbutton', ... - 'String', 'Refresh', ... - 'Enable', 'off',... - 'Callback', @(~,~)obj.dispWaterReq); - waterReqbox.Widths = [-1 75]; - - waterbox = uix.HBox('Parent', alyxbox); - % Button to launch a dialog displaying water and weight info for a given mouse - uicontrol('Parent', waterbox,... - 'Style', 'pushbutton', ... - 'String', 'Subject history', ... - 'Enable', 'off',... - 'Callback', @(~,~)obj.viewSubjectHistory); - % Button to launch a dialog displaying water and weight info for all mice - uicontrol('Parent', waterbox,... - 'Style', 'pushbutton', ... - 'String', 'All WR subjects', ... - 'Enable', 'off',... - 'Callback', @(~,~)obj.viewAllSubjects); - % Button to open a dialog for manually submitting a mouse weight - uicontrol('Parent', waterbox,... - 'Style', 'pushbutton', ... - 'String', 'Manual weighing', ... - 'Enable', 'off',... - 'Callback', @(~,~)obj.recordWeight); - % Button to launch dialog for submitting gel administrations - % for future dates - uicontrol('Parent', waterbox,... - 'Style', 'pushbutton', ... - 'String', 'Give gel in future', ... - 'Enable', 'off',... - 'Callback', @(~,~)obj.giveFutureGel); - % Check box to indicate whether water was gel or liquid - obj.IsHydrogel = uicontrol('Parent', waterbox,... - 'Style', 'checkbox', ... - 'String', 'Hydrogel?', ... - 'HorizontalAlignment', 'right',... - 'Value', true, ... - 'Enable', 'off'); - % Input for submitting amount of water - obj.WaterEntry = uicontrol('Parent', waterbox,... - 'Style', 'edit',... - 'BackgroundColor', [1 1 1],... - 'HorizontalAlignment', 'right',... - 'Enable', 'off',... - 'String', '0.00', ... - 'Callback', @(src, evt)obj.changeWaterText(src, evt)); - % Button for submitting water administration - uicontrol('Parent', waterbox,... - 'Style', 'pushbutton', ... - 'String', 'Give water', ... - 'Enable', 'off',... - 'Callback', @(~,~)giveWater(obj)); - % Label Indicating the amount of water remaining - obj.WaterRemainingText = bui.label('[]', waterbox); - waterbox.Widths = [100 100 100 100 75 75 75 75]; - - launchbox = uix.HBox('Parent', alyxbox); - % Button for launching subject page in browser - uicontrol('Parent', launchbox,... - 'Style', 'pushbutton', ... - 'String', 'Launch webpage for Subject', ... - 'Enable', 'off',... - 'Callback', @(~,~)obj.launchSubjectURL); - % Button for launching (and creating) a session for a given subject in the browser - uicontrol('Parent', launchbox,... - 'Style', 'pushbutton', ... - 'String', 'Launch webpage for Session', ... - 'Enable', 'off',... - 'Callback', @(~,~)obj.launchSessionURL); - - if ~nargin - % logging message area - obj.LoggingDisplay = uicontrol('Parent', parent, 'Style', 'listbox',... - 'Enable', 'inactive', 'String', {}); - parent.Sizes = [50 150 150]; - else - % Use parent's logging display - obj.LoggingDisplay = findobj('Tag', 'Logging Display'); + function login(obj) + % Used both to log in and out of Alyx. Logging means to + % generate an Alyx token with which to send/request data. + % Logging out does not cause the token to expire, instead the + % token is simply deleted from this object. + + % Are we logging in or out? + if ~obj.AlyxInstance.IsLoggedIn % logging in + % attempt login + obj.AlyxInstance.login(); % returns an instance if success, empty if you cancel + if obj.AlyxInstance.IsLoggedIn % successful + % Start log in timer, to automatically log out after 30 + % minutes of 'inactivity' (defined as not calling + % dispWaterReq) + obj.LoginTimer = timer('StartDelay', 30*60, 'TimerFcn', @(~,~)obj.login); + start(obj.LoginTimer) + % Enable all buttons + set(findall(obj.RootContainer, '-property', 'Enable'), 'Enable', 'on'); + set(obj.LoginText, 'String', ['You are logged in as ', obj.AlyxInstance.User]); % display which user is logged in + set(obj.LoginButton, 'String', 'Logout'); + + % try updating the subject selectors in other panels + s = obj.AlyxInstance.getData('subjects?stock=False&alive=True'); + + respUser = cellfun(@(x)x.responsible_user, s, 'uni', false); + subjNames = cellfun(@(x)x.nickname, s, 'uni', false); + + thisUserSubs = sort(subjNames(strcmp(respUser, obj.AlyxInstance.User))); + otherUserSubs = sort(subjNames); + % note that we leave this User's mice also in + % otherUserSubs, in case they get confused and look + % there. + + newSubs = [{'default'}, thisUserSubs, otherUserSubs]; + obj.NewExpSubject.Option = newSubs; + obj.SubjectList = newSubs; + + notify(obj, 'Connected'); % Notify listeners of login + obj.log('Logged into Alyx successfully as %s', obj.AlyxInstance.User); + + % any database subjects that weren't in the old list of + % subjects will need a folder in expInfo. + firstTimeSubs = newSubs(~ismember(newSubs, dat.listSubjects)); + for fts = 1:length(firstTimeSubs) + thisDir = fullfile(dat.reposPath('expInfo', 'master'), firstTimeSubs{fts}); + if ~exist(thisDir, 'dir') + fprintf(1, 'making expInfo directory for %s\n', firstTimeSubs{fts}); + mkdir(thisDir); end + end + else + obj.log('Did not log into Alyx'); end - - function delete(obj) - % To be called before destroying AlyxPanel object. Deletes the - % loggin timer - disp('AlyxPanel destructor called'); - if obj.RootContainer.isvalid; delete(obj.RootContainer); end - if ~isempty(obj.LoginTimer) % If there is a timer object - stop(obj.LoginTimer) % Stop the timer... - delete(obj.LoginTimer) % ... delete it... - obj.LoginTimer = []; % ... and remove it - end + else % logging out + obj.AlyxInstance.logout; + if ~isempty(obj.LoginTimer) % If there is a timer object + stop(obj.LoginTimer) % Stop the timer... + delete(obj.LoginTimer) % ... delete it... + obj.LoginTimer = []; % ... and remove it end - - function login(obj) - % Used both to log in and out of Alyx. Logging means to - % generate an Alyx token with which to send/request data. - % Logging out does not cause the token to expire, instead the - % token is simply deleted from this object. - - % Are we logging in or out? - if isempty(obj.AlyxInstance) % logging in - % attempt login - [ai, username] = alyx.loginWindow(); % returns an instance if success, empty if you cancel - if ~isempty(ai) % successful - obj.AlyxInstance = ai; - obj.AlyxInstance.username = username; - % Start log in timer, to automatically log out after 30 - % minutes of 'inactivity' (defined as not calling - % dispWaterReq) - obj.LoginTimer = timer('StartDelay', 30*60, 'TimerFcn', @(~,~)obj.login); - start(obj.LoginTimer) - % Enable all buttons - set(findall(obj.RootContainer, '-property', 'Enable'), 'Enable', 'on'); - set(obj.LoginText, 'String', ['You are logged in as ', username]); % display which user is logged in - set(obj.LoginButton, 'String', 'Logout'); - - % try updating the subject selectors in other panels - s = alyx.getData(ai, 'subjects?stock=False&alive=True'); - - respUser = cellfun(@(x)x.responsible_user, s, 'uni', false); - subjNames = cellfun(@(x)x.nickname, s, 'uni', false); - - thisUserSubs = sort(subjNames(strcmp(respUser, username))); - otherUserSubs = sort(subjNames); - % note that we leave this User's mice also in - % otherUserSubs, in case they get confused and look - % there. - - newSubs = [{'default'}, thisUserSubs, otherUserSubs]; - obj.NewExpSubject.Option = newSubs; - obj.SubjectList = newSubs; - - notify(obj, 'Connected'); % Notify listeners of login - obj.log('Logged into Alyx successfully as %s', username); - - % any database subjects that weren't in the old list of - % subjects will need a folder in expInfo. - firstTimeSubs = newSubs(~ismember(newSubs, dat.listSubjects)); - for fts = 1:length(firstTimeSubs) - thisDir = fullfile(dat.reposPath('expInfo', 'master'), firstTimeSubs{fts}); - if ~exist(thisDir, 'dir') - fprintf(1, 'making expInfo directory for %s\n', firstTimeSubs{fts}); - mkdir(thisDir); - end - end - - % post any un-posted weighings - if ~isempty(obj.QueuedWeights) - try - for w = 1:length(obj.QueuedWeights) - d = obj.QueuedWeights{w}; - wobj = alyx.postData(obj.AlyxInstance, 'weighings/', d); - obj.log('Alyx weight posting succeeded: %.2f for %s', wobj.weight, wobj.subject); - end - obj.QueuedWeights = {}; - catch - obj.log('Failed to post stored weighings') - end - end - else - obj.log('Did not log into Alyx'); - end - else % logging out - obj.AlyxInstance = []; - obj.SubjectList = []; - if ~isempty(obj.LoginTimer) % If there is a timer object - stop(obj.LoginTimer) % Stop the timer... - delete(obj.LoginTimer) % ... delete it... - obj.LoginTimer = []; % ... and remove it - end - set(obj.LoginText, 'String', 'Not logged in') - % Disable all buttons - set(findall(obj.RootContainer, '-property', 'Enable'), 'Enable', 'off') - set(obj.LoginButton, 'Enable', 'on', 'String', 'Login') % ... except the login button - notify(obj, 'Disconnected'); % Notify listeners of logout - obj.log('Logged out of Alyx'); - end + set(obj.LoginText, 'String', 'Not logged in') + % Disable all buttons + set(findall(obj.RootContainer, '-property', 'Enable'), 'Enable', 'off') + set(obj.LoginButton, 'Enable', 'on', 'String', 'Login') % ... except the login button + notify(obj, 'Disconnected'); % Notify listeners of logout + obj.log('Logged out of Alyx'); + end + end + + function giveWater(obj) + % Callback to the give water button. Posts the value entered + % in the text box as either liquid or gel depending on the + % state of the 'is hydrogel' check box + thisDate = now; + amount = str2double(get(obj.WaterEntry, 'String')); + isHydrogel = logical(get(obj.IsHydrogel, 'Value')); + if obj.AlyxInstance.IsLoggedIn && amount~=0 && ~isnan(amount) + wa = obj.AlyxInstance.postWater(obj.Subject, amount, thisDate, isHydrogel); + if ~isempty(wa) % returned us a created water administration object successfully + wstr = iff(isHydrogel, 'Hydrogel', 'Water'); + obj.log('%s administration of %.2f for %s posted successfully to alyx', wstr, amount, obj.Subject); end - - function giveWater(obj) - % Callback to the give water button. Posts the value entered - % in the text box as either liquid or gel depending on the - % state of the 'is hydrogel' check box - ai = obj.AlyxInstance; - thisDate = now; - amount = str2double(get(obj.WaterEntry, 'String')); - isHydrogel = logical(get(obj.IsHydrogel, 'Value')); - if ~isempty(ai)&&amount~=0&&~isnan(amount) - wa = alyx.postWater(ai, obj.Subject, amount, thisDate, isHydrogel); - if ~isempty(wa) % returned us a created water administration object successfully - wstr = iff(isHydrogel, 'Hydrogel', 'Water'); - obj.log('%s administration of %.2f for %s posted successfully to alyx', wstr, amount, obj.Subject); - end - end - % update the water required text - dispWaterReq(obj); + end + % update the water required text + dispWaterReq(obj); + end + + function giveFutureGel(obj) + % Open a dialog allowing one to input water submissions for + % future dates + thisDate = now; + prompt=sprintf('Enter space-separated numbers \n[tomorrow, day after that, day after that.. etc] \nEnter 0 to skip a day'); + answer = inputdlg(prompt,'Future Gel Amounts', [1 50]); + if isempty(answer)||~obj.AlyxInstance.IsLoggedIn + return % user pressed 'Close' or 'x' + end + amount = str2num(answer{:}); %#ok<ST2NM> + weekendDates = thisDate + (1:length(amount)); + for d = 1:length(weekendDates) + if amount(d) > 0 + obj.AlyxInstance.postWater(obj.Subject, amount(d), weekendDates(d), 1); + obj.log(['Hydrogel administration of %.2f for %s posted successfully to alyx for '... + datestr(weekendDates(d))], amount(d), obj.Subject); end - - function giveFutureGel(obj) - % Open a dialog allowing one to input water submissions for - % future dates - ai = obj.AlyxInstance; - thisDate = now; - prompt=sprintf('Enter space-separated numbers \n[tomorrow, day after that, day after that.. etc] \nEnter 0 to skip a day'); - answer = inputdlg(prompt,'Future Gel Amounts', [1 50]); - if isempty(answer)||isempty(ai); return; end % user pressed 'Close' or 'x' - amount = str2num(answer{:}); %#ok<ST2NM> - weekendDates = thisDate + (1:length(amount)); - for d = 1:length(weekendDates) - if amount(d) > 0 - alyx.postWater(ai, obj.Subject, amount(d), weekendDates(d), 1); - obj.log(['Hydrogel administration of %.2f for %s posted successfully to alyx for ' datestr(weekendDates(d))], amount(d), obj.Subject); - end - end + end + end + + function dispWaterReq(obj, src, ~) + % Display the amount of water required by the selected subject + % for it to reach its minimum requirement. This function is + % also used to update the selected subject, for example it is + % this funtion to use as a callback to subject dropdown + % listeners + ai = obj.AlyxInstance; + % Set the selected subject if it is an input + if nargin>1; obj.Subject = src.Selected; end + if ~ai.IsLoggedIn + set(obj.WaterRequiredText, 'String', 'Log in to see water requirements'); + return + end + % Refresh the timer as the user isn't inactive + stop(obj.LoginTimer); start(obj.LoginTimer) + try + s = ai.getData(ai.makeEndpoint(['subjects/' obj.Subject])); % struct with data about the subject + if s.water_requirement_total==0 + set(obj.WaterRequiredText, 'String', sprintf('Subject %s not on water restriction', obj.Subject)); + else + set(obj.WaterRequiredText, 'String', ... + sprintf('Subject %s requires %.2f of %.2f today', ... + obj.Subject, s.water_requirement_remaining, s.water_requirement_total)); + obj.WaterRemaining = s.water_requirement_remaining; end - - function dispWaterReq(obj, src, ~) - % Display the amount of water required by the selected subject - % for it to reach its minimum requirement. This function is - % also used to update the selected subject, for example it is - % this funtion to use as a callback to subject dropdown - % listeners - ai = obj.AlyxInstance; - % Set the selected subject if it is an input - if nargin>1; obj.Subject = src.Selected; end - if isempty(ai) - set(obj.WaterRequiredText, 'String', 'Log in to see water requirements'); + catch me + d = loadjson(me.message); + if isfield(d, 'detail') && strcmp(d.detail, 'Not found.') + set(obj.WaterRequiredText, 'String', sprintf('Subject %s not found in alyx', obj.Subject)); + end + end + end + + function changeWaterText(obj, src, ~) + % Update the panel text to show the amount of water still + % required for the subject to reach its minimum requirement. + % This text is updated before the value in the water text box + % has been posted to Alyx. For example if the user is unsure + % how much gel over the minimum they have weighed out, pressing + % return will display this without posting to Alyx + % + % See also DISPWATERREQ, GIVEWATER + if ~obj.AlyxInstance.IsLoggedIn && ~isempty(obj.WaterRemaining) + rem = obj.WaterRemaining; + curr = str2double(src.String); + set(obj.WaterRemainingText, 'String', sprintf('(%.2f)', rem-curr)); + end + end + + function recordWeight(obj, weight, subject) + % Post a subject's weight to Alyx. If no inputs are provided, + % create an input dialog for the user to input a weight. If no + % subject is provided, use this object's currently selected + % subject. + % + % See also VIEWSUBJECTHISTORY, VIEWALLSUBJECTS + ai = obj.AlyxInstance; + if nargin < 3; subject = obj.Subject; end + if nargin < 2 + prompt = {sprintf('weight of %s:', subject)}; + dlgTitle = 'Manual weight logging'; + numLines = 1; + defaultAns = {'',''}; + weight = inputdlg(prompt, dlgTitle, numLines, defaultAns); + if isempty(weight); return; end + end + % inputdlg returns weight as a cell, otherwise it may now be + weight = ensureCell(weight); % ensure it's a cell + % convert to double if weight is a string + weight = iff(ischar(weight{1}), str2double(weight{1}), weight{1}); + try + w = postWeight(ai, weight, subject); %FIXME: If multiple things flushed, length(w)>1 + obj.log('Alyx weight posting succeeded: %.2f for %s', w.weight, w.subject); + catch + if ~ai.IsLoggedIn % if not logged in, save the weight for later + obj.log('Warning: Weight not posted to Alyx; will be posted upon login.'); + else + obj.log('Warning: Alyx weight posting failed!'); + end + end + % Update weight and refresh login timer + obj.dispWaterReq + end + + function launchSessionURL(obj) + % Launch the Webpage for the current base session in the + % default Web browser. If no session exists for today's date, + % a new base session is created accordingly. + % + % See also LAUNCHSUBJECTURL + ai = obj.AlyxInstance; + % determine whether there is a session for this subj and date + thisDate = ai.datestr(now); + sessions = ai.getData(['sessions?type=Base&subject=' obj.Subject]); + + % If the date of this latest base session is not the same date + % as today, then create a new one for today + if isempty(sessions) || ~strcmp(sessions{end}.start_time(1:10), thisDate(1:10)) + % Ask user whether he/she wants to create new session + % Construct a questdlg with three options + choice = questdlg('Would you like to create a new base session?', ... + ['No base session exists for ' datestr(now, 'yyyy-mm-dd')], ... + 'Yes','No','No'); + % Handle response + switch choice + case 'Yes' + % Create our base session + d = struct; + d.subject = obj.Subject; + d.procedures = {'Behavior training/tasks'}; + d.narrative = 'auto-generated session'; + d.start_time = thisDate; + d.type = 'Base'; + + thisSess = ai.postData('sessions', d); + if ~isfield(thisSess,'subject') % fail + warning('Submitted base session did not return appropriate values'); + warning('Submitted data below:'); + disp(d) + warning('Return values below:'); + disp(thisSess) return + else % success + obj.log(['Created new base session in Alyx for ' obj.Subject]); end - % Refresh the timer as the user isn't inactive - stop(obj.LoginTimer); start(obj.LoginTimer) - try - s = alyx.getData(ai, alyx.makeEndpoint(ai, ['subjects/' obj.Subject])); % struct with data about the subject - if s.water_requirement_total==0 - set(obj.WaterRequiredText, 'String', sprintf('Subject %s not on water restriction', obj.Subject)); - else - set(obj.WaterRequiredText, 'String', ... - sprintf('Subject %s requires %.2f of %.2f today', ... - obj.Subject, s.water_requirement_remaining, s.water_requirement_total)); - obj.WaterRemaining = s.water_requirement_remaining; - end - catch me - d = loadjson(me.message); - if isfield(d, 'detail') && strcmp(d.detail, 'Not found.') - set(obj.WaterRequiredText, 'String', sprintf('Subject %s not found in alyx', obj.Subject)); - end - end + case 'No' + return end + else + thisSess = sessions{end}; + end + + % parse the uuid from the url in the session object + u = thisSess.url; + uuid = u(find(u=='/', 1, 'last')+1:end); + + % make the admin url + adminURL = fullfile(ai.baseURL, 'admin', 'actions', 'session', uuid, 'change'); + + % launch the website + web(adminURL, '-browser'); + end + + function launchSubjectURL(obj) + ai = obj.AlyxInstance; + if ~ai.IsLoggedIn + s = ai.getData(ai.makeEndpoint(['subjects/' obj.Subject])); + subjURL = fullfile(ai.BaseURL, 'admin', 'subjects', 'subject', s.id, 'change'); % this is wrong - need uuid + web(subjURL, '-browser'); + end + end + + function viewSubjectHistory(obj, ax) + % View historical information about a subject. + % Opens a new window and plots a set of weight graphs as well + % as displaying a table with the water and weight entries for + % the selected subject. If an axes handle is provided, this + % function plots a single weight graph + + % If not logged in or 'default' is selected, return + if ~obj.AlyxInstance.IsLoggedIn||strcmp(obj.Subject, 'default'); return; end + % collect the data for the table + endpnt = sprintf('water-requirement/%s?start_date=2016-01-01&end_date=%s', obj.Subject, datestr(now, 'yyyy-mm-dd')); + wr = obj.AlyxInstance.getData(endpnt); + records = catStructs(wr.records, nan); + % no weighings found + if isempty(wr.records) + obj.log('No weight data found for subject %s', obj.Subject); + return + end + dates = cellfun(@(x)datenum(x), {records.date}); + + % build the figure to show it + if nargin==1 + f = figure('Name', obj.Subject, 'NumberTitle', 'off'); % popup a new figure for this + p = get(f, 'Position'); + set(f, 'Position', [p(1) p(2) 1100 p(4)]); + histbox = uix.HBox('Parent', f, 'BackgroundColor', 'w'); + plotBox = uix.VBox('Parent', histbox, 'BackgroundColor', 'w'); + ax = axes('Parent', plotBox); + end + + plot(ax, dates, [records.weight_measured], '.-'); + hold(ax, 'on'); + plot(ax, dates, [records.weight_expected]*0.7, 'r', 'LineWidth', 2.0); + plot(ax, dates, [records.weight_expected]*0.8, 'LineWidth', 2.0, 'Color', [244, 191, 66]/255); + box(ax, 'off'); + if numel(dates) > 1; xlim(ax, [min(dates) max(dates)]); end + if nargin == 1 + set(ax, 'XTickLabel', arrayfun(@(x)datestr(x, 'dd-mmm'), get(ax, 'XTick'), 'uni', false)) + else + ax.XTickLabel = arrayfun(@(x)datestr(x, 'dd-mmm'), get(ax, 'XTick'), 'uni', false); + end + ylabel(ax, 'weight (g)'); + + if nargin==1 + ax = axes('Parent', plotBox); + plot(ax, dates, [records.weight_measured]./[records.weight_expected], '.-'); + hold(ax, 'on'); + plot(ax, dates, 0.7*ones(size(dates)), 'r', 'LineWidth', 2.0); + plot(ax, dates, 0.8*ones(size(dates)), 'LineWidth', 2.0, 'Color', [244, 191, 66]/255); + box(ax, 'off'); + xlim(ax, [min(dates) max(dates)]); + set(ax, 'XTickLabel', arrayfun(@(x)datestr(x, 'dd-mmm'), get(ax, 'XTick'), 'uni', false)) + ylabel(ax, 'weight as pct (%)'); - function changeWaterText(obj, src, ~) - % Update the panel text to show the amount of water still - % required for the subject to reach its minimum requirement. - % This text is updated before the value in the water text box - % has been posted to Alyx. For example if the user is unsure - % how much gel over the minimum they have weighed out, pressing - % return will display this without posting to Alyx - % - % See also DISPWATERREQ, GIVEWATER - ai = obj.AlyxInstance; - if ~isempty(ai) && ~isempty(obj.WaterRemaining) - rem = obj.WaterRemaining; - curr = str2double(src.String); - set(obj.WaterRemainingText, 'String', sprintf('(%.2f)', rem-curr)); - end - end + axWater = axes('Parent',plotBox); + plot(axWater, dates, [records.water_given]+[records.hydrogel_given], '.-'); + hold(axWater, 'on'); + plot(axWater, dates, [records.hydrogel_given], '.-'); + plot(axWater, dates, [records.water_given], '.-'); + plot(axWater, dates, [records.water_expected], 'r', 'LineWidth', 2.0); + box(axWater, 'off'); + xlim(axWater, [min(dates) max(dates)]); + set(axWater, 'XTickLabel', arrayfun(@(x)datestr(x, 'dd-mmm'), get(axWater, 'XTick'), 'uni', false)) + ylabel(axWater, 'water/hydrogel (mL)'); - function recordWeight(obj, weight, subject) - % Post a subject's weight to Alyx. If no inputs are provided, - % create an input dialog for the user to input a weight. If no - % subject is provided, use this object's currently selected - % subject. - % - % See also VIEWSUBJECTHISTORY, VIEWALLSUBJECTS - ai = obj.AlyxInstance; - if nargin < 3; subject = obj.Subject; end - if nargin < 2 - prompt = {sprintf('weight of %s:', subject)}; - dlgTitle = 'Manual weight logging'; - numLines = 1; - defaultAns = {'',''}; - weight = inputdlg(prompt, dlgTitle, numLines, defaultAns); - if isempty(weight); return; end - end - % inputdlg returns weight as a cell, otherwise it may now be - weight = ensureCell(weight); % ensure it's a cell - % convert to double if weight is a string - weight = iff(ischar(weight{1}), str2double(weight{1}), weight{1}); - d.subject = subject; - d.weight = weight; - if isempty(ai) % if not logged in, save the weight for later - obj.QueuedWeights{end+1} = d; - obj.log('Warning: Weight not posted to Alyx; will be posted upon login.'); - else % otherwise immediately post to Alyx - d.user = ai.username; - try - w = alyx.postData(ai, 'weighings/', d); - obj.log('Alyx weight posting succeeded: %.2f for %s', w.weight, w.subject); - catch - obj.log('Warning: Alyx weight posting failed!'); - end - end - end + % Create table of useful weight and water information, + % sorted by date + histTable = uitable('Parent', histbox,... + 'FontName', 'Consolas',... + 'RowName', []); + weightsByDate = num2cell([records.weight_measured]); + weightsByDate = cellfun(@(x)sprintf('%.1f', x), weightsByDate, 'uni', false); + weightsByDate(isnan([records.weight_measured])) = {[]}; + weightPctByDate = num2cell([records.weight_measured]./[records.weight_expected]); + weightPctByDate = cellfun(@(x)sprintf('%.1f', x*100), weightPctByDate, 'uni', false); + weightPctByDate(isnan([records.weight_measured])) = {[]}; - function launchSessionURL(obj) - % Launch the Webpage for the current base session in the - % default Web browser. If no session exists for today's date, - % a new base session is created accordingly. - % - % See also LAUNCHSUBJECTURL - ai = obj.AlyxInstance; - % determine whether there is a session for this subj and date - thisDate = alyx.datestr(now); - sessions = alyx.getData(ai, ['sessions?type=Base&subject=' obj.Subject]); - - % If the date of this latest base session is not the same date - % as today, then create a new one for today - if isempty(sessions) || ~strcmp(sessions{end}.start_time(1:10), thisDate(1:10)) - % Ask user whether he/she wants to create new session - % Construct a questdlg with three options - choice = questdlg('Would you like to create a new base session?', ... - ['No base session exists for ' datestr(now, 'yyyy-mm-dd')], ... - 'Yes','No','No'); - % Handle response - switch choice - case 'Yes' - % Create our base session - d = struct; - d.subject = obj.Subject; - d.procedures = {'Behavior training/tasks'}; - d.narrative = 'auto-generated session'; - d.start_time = thisDate; - d.type = 'Base'; - - thisSess = alyx.postData(ai, 'sessions', d); - if ~isfield(thisSess,'subject') % fail - warning('Submitted base session did not return appropriate values'); - warning('Submitted data below:'); - disp(d) - warning('Return values below:'); - disp(thisSess) - return - else % success - obj.log(['Created new base session in Alyx for ' obj.Subject]); - end - case 'No' - return - end - else - thisSess = sessions{end}; - end - - % parse the uuid from the url in the session object - u = thisSess.url; - uuid = u(find(u=='/', 1, 'last')+1:end); - - % make the admin url - adminURL = fullfile(ai.baseURL, 'admin', 'actions', 'session', uuid, 'change'); - - % launch the website - web(adminURL, '-browser'); - end + dat = horzcat(... + arrayfun(@(x)datestr(x), dates', 'uni', false), ... + weightsByDate', ... + arrayfun(@(x)sprintf('%.1f', 0.8*x), [records.weight_expected]', 'uni', false), ... + weightPctByDate'); + waterDat = (... + num2cell(horzcat([records.water_given]', [records.hydrogel_given]', ... + [records.water_given]'+[records.hydrogel_given]', [records.water_expected]',... + [records.water_given]'+[records.hydrogel_given]'-[records.water_expected]'))); + waterDat = cellfun(@(x)sprintf('%.2f', x), waterDat, 'uni', false); + dat = horzcat(dat, waterDat); - function launchSubjectURL(obj) - ai = obj.AlyxInstance; - if ~isempty(ai) - s = alyx.getData(ai, alyx.makeEndpoint(ai, ['subjects/' obj.Subject])); - subjURL = fullfile(ai.baseURL, 'admin', 'subjects', 'subject', s.id, 'change'); % this is wrong - need uuid - web(subjURL, '-browser'); - end - end + set(histTable, 'ColumnName', {'date', 'meas. weight', '80% weight', 'weight pct', 'water', 'hydrogel', 'total', 'min water', 'excess'}, ... + 'Data', dat(end:-1:1,:),... + 'ColumnEditable', false(1,5)); + histbox.Widths = [ -1 725]; + end + end + + function viewAllSubjects(obj) + ai = obj.AlyxInstance; + if ai.IsLoggedIn + wr = ai.getData(ai.makeEndpoint('water-restricted-subjects')); - function viewSubjectHistory(obj, ax) - % View historical information about a subject. - % Opens a new window and plots a set of weight graphs as well - % as displaying a table with the water and weight entries for - % the selected subject. If an axes handle is provided, this - % function plots a single weight graph - ai = obj.AlyxInstance; - % If not logged in or 'default' is selected, return - if isempty(ai)||strcmp(obj.Subject, 'default'); return; end - % collect the data for the table - endpnt = sprintf('water-requirement/%s?start_date=2016-01-01&end_date=%s', obj.Subject, datestr(now, 'yyyy-mm-dd')); - wr = alyx.getData(ai, endpnt); - records = catStructs(wr.records, nan); - % no weighings found - if isempty(wr.records) - obj.log('No weight data found for subject %s', obj.Subject); - return - end - dates = cellfun(@(x)datenum(x), {records.date}); - - % build the figure to show it - if nargin==1 - f = figure('Name', obj.Subject, 'NumberTitle', 'off'); % popup a new figure for this - p = get(f, 'Position'); - set(f, 'Position', [p(1) p(2) 1100 p(4)]); - histbox = uix.HBox('Parent', f, 'BackgroundColor', 'w'); - plotBox = uix.VBox('Parent', histbox, 'BackgroundColor', 'w'); - ax = axes('Parent', plotBox); - end - - plot(ax, dates, [records.weight_measured], '.-'); - hold(ax, 'on'); - plot(ax, dates, [records.weight_expected]*0.7, 'r', 'LineWidth', 2.0); - plot(ax, dates, [records.weight_expected]*0.8, 'LineWidth', 2.0, 'Color', [244, 191, 66]/255); - box(ax, 'off'); - if numel(dates) > 1; xlim(ax, [min(dates) max(dates)]); end - if nargin == 1 - set(ax, 'XTickLabel', arrayfun(@(x)datestr(x, 'dd-mmm'), get(ax, 'XTick'), 'uni', false)) - else - ax.XTickLabel = arrayfun(@(x)datestr(x, 'dd-mmm'), get(ax, 'XTick'), 'uni', false); - end - ylabel(ax, 'weight (g)'); - - if nargin==1 - ax = axes('Parent', plotBox); - plot(ax, dates, [records.weight_measured]./[records.weight_expected], '.-'); - hold(ax, 'on'); - plot(ax, dates, 0.7*ones(size(dates)), 'r', 'LineWidth', 2.0); - plot(ax, dates, 0.8*ones(size(dates)), 'LineWidth', 2.0, 'Color', [244, 191, 66]/255); - box(ax, 'off'); - xlim(ax, [min(dates) max(dates)]); - set(ax, 'XTickLabel', arrayfun(@(x)datestr(x, 'dd-mmm'), get(ax, 'XTick'), 'uni', false)) - ylabel(ax, 'weight as pct (%)'); - - axWater = axes('Parent',plotBox); - plot(axWater, dates, [records.water_given]+[records.hydrogel_given], '.-'); - hold(axWater, 'on'); - plot(axWater, dates, [records.hydrogel_given], '.-'); - plot(axWater, dates, [records.water_given], '.-'); - plot(axWater, dates, [records.water_expected], 'r', 'LineWidth', 2.0); - box(axWater, 'off'); - xlim(axWater, [min(dates) max(dates)]); - set(axWater, 'XTickLabel', arrayfun(@(x)datestr(x, 'dd-mmm'), get(axWater, 'XTick'), 'uni', false)) - ylabel(axWater, 'water/hydrogel (mL)'); - - % Create table of useful weight and water information, - % sorted by date - histTable = uitable('Parent', histbox,... - 'FontName', 'Consolas',... - 'RowName', []); - weightsByDate = num2cell([records.weight_measured]); - weightsByDate = cellfun(@(x)sprintf('%.1f', x), weightsByDate, 'uni', false); - weightsByDate(isnan([records.weight_measured])) = {[]}; - weightPctByDate = num2cell([records.weight_measured]./[records.weight_expected]); - weightPctByDate = cellfun(@(x)sprintf('%.1f', x*100), weightPctByDate, 'uni', false); - weightPctByDate(isnan([records.weight_measured])) = {[]}; - - dat = horzcat(... - arrayfun(@(x)datestr(x), dates', 'uni', false), ... - weightsByDate', ... - arrayfun(@(x)sprintf('%.1f', 0.8*x), [records.weight_expected]', 'uni', false), ... - weightPctByDate'); - waterDat = (... - num2cell(horzcat([records.water_given]', [records.hydrogel_given]', ... - [records.water_given]'+[records.hydrogel_given]', [records.water_expected]',... - [records.water_given]'+[records.hydrogel_given]'-[records.water_expected]'))); - waterDat = cellfun(@(x)sprintf('%.2f', x), waterDat, 'uni', false); - dat = horzcat(dat, waterDat); - - set(histTable, 'ColumnName', {'date', 'meas. weight', '80% weight', 'weight pct', 'water', 'hydrogel', 'total', 'min water', 'excess'}, ... - 'Data', dat(end:-1:1,:),... - 'ColumnEditable', false(1,5)); - histbox.Widths = [ -1 725]; - end - end + subjs = cellfun(@(x)x.nickname, wr, 'uni', false); + waterReqTotal = cellfun(@(x)x.water_requirement_total, wr, 'uni', false); + waterReqRemain = cellfun(@(x)x.water_requirement_remaining, wr, 'uni', false); - function viewAllSubjects(obj) - ai = obj.AlyxInstance; - if ~isempty(ai) - - wr = alyx.getData(ai, alyx.makeEndpoint(ai, 'water-restricted-subjects')); - - subjs = cellfun(@(x)x.nickname, wr, 'uni', false); - waterReqTotal = cellfun(@(x)x.water_requirement_total, wr, 'uni', false); - waterReqRemain = cellfun(@(x)x.water_requirement_remaining, wr, 'uni', false); - - % build a figure to show it - f = figure; % popup a new figure for this - wrBox = uix.VBox('Parent', f); - wrTable = uitable('Parent', wrBox,... - 'FontName', 'Consolas',... - 'RowName', []); - - htmlColor = @(colorNum)reshape(dec2hex(round(colorNum'*255),2)',1,6); - % colorgen = @(colorNum,text) ['<html><table border=0 width=400 bgcolor=#',htmlColor(colorNum),'><TR><TD>',text,'</TD></TR> </table></html>']; - colorgen = @(colorNum,text) ['<html><body bgcolor=#',htmlColor(colorNum),'>',text,'</body></html>']; - - wrdat = cellfun(@(x)colorgen(1-double(x>0)*[0 0.3 0.3], sprintf('%.2f',x)), waterReqRemain, 'uni', false); - - set(wrTable, 'ColumnName', {'Name', 'Water Required', 'Remaining Requirement'}, ... - 'Data', horzcat(subjs', ... - cellfun(@(x)sprintf('%.2f',x),waterReqTotal', 'uni', false), ... - wrdat'), ... - 'ColumnEditable', false(1,3)); - end - end + % build a figure to show it + f = figure; % popup a new figure for this + wrBox = uix.VBox('Parent', f); + wrTable = uitable('Parent', wrBox,... + 'FontName', 'Consolas',... + 'RowName', []); - function log(obj, varargin) - % Function for displaying timestamped information about - % occurrences. If the LoggingDisplay property is unset, the - % message is printed to the command prompt. - % log(formatSpec, A1,... An) - % - % See also FPRINTF - message = sprintf(varargin{:}); - if ~isempty(obj.LoggingDisplay) - timestamp = datestr(now, 'dd-mm-yyyy HH:MM:SS'); - str = sprintf('[%s] %s', timestamp, message); - current = get(obj.LoggingDisplay, 'String'); - %NB: If more that one instance of MATLAB is open, we use - %the last opened LoggingDisplay - set(obj.LoggingDisplay(end), 'String', [current; str], 'Value', numel(current) + 1); - else - fprintf(message) - end - end + htmlColor = @(colorNum)reshape(dec2hex(round(colorNum'*255),2)',1,6); + % colorgen = @(colorNum,text) ['<html><table border=0 width=400 bgcolor=#',htmlColor(colorNum),'><TR><TD>',text,'</TD></TR> </table></html>']; + colorgen = @(colorNum,text) ['<html><body bgcolor=#',htmlColor(colorNum),'>',text,'</body></html>']; + + wrdat = cellfun(@(x)colorgen(1-double(x>0)*[0 0.3 0.3], sprintf('%.2f',x)), waterReqRemain, 'uni', false); + + set(wrTable, 'ColumnName', {'Name', 'Water Required', 'Remaining Requirement'}, ... + 'Data', horzcat(subjs', ... + cellfun(@(x)sprintf('%.2f',x),waterReqTotal', 'uni', false), ... + wrdat'), ... + 'ColumnEditable', false(1,3)); + end end + function log(obj, varargin) + % Function for displaying timestamped information about + % occurrences. If the LoggingDisplay property is unset, the + % message is printed to the command prompt. + % log(formatSpec, A1,... An) + % + % See also FPRINTF + message = sprintf(varargin{:}); + if ~isempty(obj.LoggingDisplay) + timestamp = datestr(now, 'dd-mm-yyyy HH:MM:SS'); + str = sprintf('[%s] %s', timestamp, message); + current = get(obj.LoggingDisplay, 'String'); + %NB: If more that one instance of MATLAB is open, we use + %the last opened LoggingDisplay + set(obj.LoggingDisplay(end), 'String', [current; str], 'Value', numel(current) + 1); + else + fprintf(message) + end + end + end + end \ No newline at end of file diff --git a/+eui/ExpPanel.m b/+eui/ExpPanel.m index 5ccb95b6..4e6c2cb0 100644 --- a/+eui/ExpPanel.m +++ b/+eui/ExpPanel.m @@ -11,7 +11,6 @@ % experiment type, for example CHOICEEXPPANEL for ChoiceWorld and % SQUEAKEXPPANEL for Signals experiments. % - % % % See also SQUEAKEXPPANEL, CHOICEEXPPANEL, MCONTROL, MC % @@ -207,12 +206,12 @@ function expStarted(obj, rig, evt) end end - function expStopped(obj, rig, evt) + function expStopped(obj, rig, ~) % EXPSTOPPED Callback for the ExpStopped event. - % Updates the ExpRunning flag, the panel title and status label to - % show that the experiment has ended. This function also records to Alyx the - % amount of water, if any, that the subject received during the - % task. + % expStopped(obj, rig, event) Updates the ExpRunning flag, the + % panel title and status label to show that the experiment has + % ended. This function also records to Alyx the amount of water, + % if any, that the subject received during the task. % % See also EXPSTARTED, ALYX.POSTWATER set(obj.StatusLabel, 'String', 'Completed'); %staus to completed @@ -245,7 +244,7 @@ function expStopped(obj, rig, evt) end if ~any(amount); return; end % Return if no water was given try - alyx.postWater(ai, subject, amount*0.001, now, false); + ai.postWater(subject, amount*0.001, now, false); catch warning('Failed to post the water %s recieved during the experiment to Alyx', amount*0.001, subject); end @@ -299,7 +298,7 @@ function saveLogEntry(obj) % subsession's narrative field. % % See also DAT.UPDATELOGENTRY, COMMENTSCHANGED - dat.updateLogEntry(obj.SubjectRef, obj.LogEntry.id, obj.LogEntry); + dat.updateLogEntry(dat.parseExpRef(obj.SubjectRef), obj.LogEntry.id, obj.LogEntry); end function viewParams(obj) diff --git a/+eui/MControl.m b/+eui/MControl.m index 0f09f9ff..9d706860 100644 --- a/+eui/MControl.m +++ b/+eui/MControl.m @@ -121,7 +121,7 @@ function newScalesReading(obj, ~, ~) function tabChanged(obj) % Function to change which subject Alyx uses when user changes tab - if isempty(obj.AlyxPanel.AlyxInstance); return; end + if ~obj.AlyxPanel.AlyxInstance.IsLoggedIn; return; end if obj.TabPanel.SelectedChild == 1 % Log tab obj.AlyxPanel.dispWaterReq(obj.LogSubject); else % SelectedChild == 2 Experiment tab @@ -335,11 +335,10 @@ function rigExpStopped(obj, rig, evt) % Announce that the experiment has stopped set([obj.BeginExpButton obj.RigOptionsButton], 'Enable', 'on'); % Re-enable 'Start' button so a new experiment can be started on that rig end % Alyx water reporting: indicate amount of water this mouse still needs - if ~isempty(rig.AlyxInstance) + if rig.AlyxInstance.IsLoggedIn try subject = dat.parseExpRef(evt.Ref); - sd = alyx.getData(rig.AlyxInstance, ... - sprintf('subjects/%s', subject)); + sd = rig.AlyxInstance.getData(sprintf('subjects/%s', subject)); obj.log('Water requirement remaining for %s: %.2f (%.2f already given)', ... subject, sd.water_requirement_remaining, ... sd.water_requirement_total-sd.water_requirement_remaining); @@ -347,7 +346,9 @@ function rigExpStopped(obj, rig, evt) % Announce that the experiment has stopped subject = dat.parseExpRef(evt.Ref); obj.log('Warning: unable to query Alyx about %s''s water requirements', subject); end - rig.AlyxInstance = []; % remove AlyxInstance from rig; no longer required + % Remove AlyxInstance from rig; no longer required + delete(rig.AlyxInstance); + rig.AlyxInstance = []; end end @@ -532,7 +533,8 @@ function beginExp(obj) set([obj.BeginExpButton obj.RigOptionsButton], 'Enable', 'off'); % Grey out buttons rig = obj.RemoteRigs.Selected; % Find which rig is selected % Save the current instance of Alyx so that eui.ExpPanel can register water to the correct account - if isempty(obj.AlyxPanel.AlyxInstance)&&~strcmp(obj.NewExpSubject.Selected,'default') + ai = obj.AlyxPanel.AlyxInstance; + if ~ai.IsLoggedIn && ~strcmp(obj.NewExpSubject.Selected,'default') try obj.AlyxPanel.login(); catch @@ -546,12 +548,11 @@ function beginExp(obj) obj.Parameters.set('services', services(:),... 'List of experiment services to use during the experiment'); % Create new experiment reference - [expRef, ~, url] = dat.newExp(obj.NewExpSubject.Selected, now,... - obj.Parameters.Struct, obj.AlyxPanel.AlyxInstance); + [expRef, ~] = ai.newExp(obj.NewExpSubject.Selected, now,... + obj.Parameters.Struct); % Add a copy of the AlyxInstance to the rig object for later % water registration, &c. - rig.AlyxInstance = obj.AlyxPanel.AlyxInstance; - rig.AlyxInstance.subsessionURL = url; + rig.AlyxInstance = ai.copy; panel = eui.ExpPanel.live(obj.ActiveExpsGrid, expRef, rig, obj.Parameters.Struct); obj.LastExpPanel = panel; @@ -569,7 +570,7 @@ function updateWeightPlot(obj) entries = obj.Log.entriesByType('weight-grams'); datenums = floor([entries.date]); obj.WeightAxes.clear(); - if ~isempty(obj.AlyxPanel.AlyxInstance)&&~strcmp(obj.LogSubject.Selected,'default') + if obj.AlyxPanel.AlyxInstance.IsLoggedIn && ~strcmp(obj.LogSubject.Selected,'default') obj.AlyxPanel.viewSubjectHistory(obj.WeightAxes.Handle) rotateticklabel(obj.WeightAxes.Handle, 45); else diff --git a/+exp/Experiment.m b/+exp/Experiment.m index b0229875..e26860a0 100644 --- a/+exp/Experiment.m +++ b/+exp/Experiment.m @@ -773,20 +773,20 @@ function saveData(obj) savepaths = dat.expFilePath(obj.Data.expRef, 'block'); superSave(savepaths, struct('block', obj.Data)); - if isempty(obj.AlyxInstance) + if ~obj.AlyxInstance.IsLoggedIn warning('No Alyx token set'); else try - [subject,~,~] = dat.parseExpRef(obj.Data.expRef); - if strcmp(subject,'default'); return; end + subject = dat.parseExpRef(obj.Data.expRef); + if strcmp(subject, 'default'); return; end % Register saved files - alyx.registerFile(savepaths{end}, 'mat',... - obj.AlyxInstance.subsessionURL, 'Block', [], obj.AlyxInstance); + obj.AlyxInstance.registerFile(savepaths{end}, 'mat',... + obj.AlyxInstance.subsessionURL, 'Block', []); % Save the session end time - alyx.putData(obj.AlyxInstance, obj.AlyxInstance.subsessionURL,... - struct('end_time', alyx.datestr(now), 'subject', subject)); - catch - warning('couldnt register files to alyx because no subsession found'); + obj.AlyxInstance.putData(obj.AlyxInstance.SessionURL,... + struct('end_time', obj.AlyxInstance.datestr(now), 'subject', subject)); + catch ex + warning(ex.identifer, 'Failed to register files to Alyx: %s', ex.message); end end end diff --git a/+exp/SignalsExp.m b/+exp/SignalsExp.m index 74c3e059..96d8a62f 100644 --- a/+exp/SignalsExp.m +++ b/+exp/SignalsExp.m @@ -1,5 +1,5 @@ classdef SignalsExp < handle - %exp.SignalsExp Base class for stimuli-delivering experiments + %EXP.SIGNALSEXP Base class for stimuli-delivering experiments % The class defines a framework for event- and state-based experiments. % Visual and auditory stimuli can be controlled by experiment phases. % Phases changes are managed by an event-handling system. @@ -33,6 +33,8 @@ %saved into the block data field 'rigName'. RigName + %Communcator object for sending signals updates to mc. Set by + %expServer Communicator = io.DummyCommunicator %Delay (secs) before starting main experiment phase after experiment @@ -44,21 +46,30 @@ %wasn't requested). PostDelay = 0 - IsPaused = false %flag indicating whether the experiment is paused + %Flag indicating whether the experiment is paused + IsPaused = false + %Holds the wheel object, 'mouseInput' from the rig object. See also + %USERIG, HW.DAQROTARYENCODER Wheel + %Holds the object for interating with the lick detector. See also + %HW.DAQEDGECOUNTER LickDetector + %Holds the object for interating with the DAQ outputs (reward valve, + %etc.) See also HW.DAQCONTROLLER DaqController + %Get the handle to the PTB window opened by expServer StimWindowPtr TextureById LayersByStim - Occ % occulus model + %Occulus viewing model + Occ Time @@ -70,20 +81,28 @@ Visual - Audio + Audio % = aud.AudioRegistry + %Holds the parameters structure for this experiment Params ParamsLog + %The bounds for the photodiode square SyncBounds + %Sync colour cycle (usually [0, 255]) - cycles through these each + %time the screen flips. SyncColourCycle - NextSyncIdx %index into SyncColourCycle for next sync colour -% Audio = aud.AudioRegistry + %Index into SyncColourCycle for next sync colour + NextSyncIdx + + %Holds the session for the DAQ photodiode echo, used if + %rig.stimWindow.DaqSyncEchoPort is defined. See also USERIG + DaqSyncEcho - %AlyxToken from client + % Alyx instance from client. See also SAVEDATA AlyxInstance = [] end @@ -94,6 +113,9 @@ %Data from the currently running experiment, if any. Data = struct + %Data binary file ID and pars file ID for intermittent saving + DataFID + %Currently active phases of the experiment. Cell array of their names %(i.e. strings) ActivePhases = {} @@ -104,7 +126,6 @@ SignalUpdates = struct('name', cell(500,1), 'value', cell(500,1), 'timestamp', cell(500,1)) NumSignalUpdates = 0 - end properties (Access = protected) @@ -112,7 +133,8 @@ %are awaiting activation pending completion of their delay period. Pending - IsLooping = false %flag indicating whether to continue in experiment loop + %Flag indicating whether to continue in experiment loop + IsLooping = false AsyncFlipping = false @@ -191,19 +213,48 @@ end function useRig(obj, rig) + % USERIG(OBJ, RIG) Initialize all hardware for experiment + % Takes the rig hardware structure and loads the relevant + % parameters into the class properties, namely the DAQ output + % channels and the stimWindow properties. + % + % See Also HW.PTB.WINDOW, DAQCONTROLLER + obj.Clock = rig.clock; obj.Data.rigName = rig.name; + % Sync bounds parameter for photodiode square obj.SyncBounds = rig.stimWindow.SyncBounds; + % Sync colour cycle (usually [0, 255]) - cycles through these each + % time the screen flips. obj.SyncColourCycle = rig.stimWindow.SyncColourCycle; + % If the DaqSyncEchoPort is defined, each time the screen flips, the + % DAQ will output an alternating high or low. This signals will + % follow the photodiode signal. + if ~isempty(rig.stimWindow.DaqSyncEchoPort) + % Create the DAQ session for the sync echo + obj.DaqSyncEcho = daq.createSession(rig.stimWindow.DaqVendor); + % Add the digital channel using parameters defined in + % rig.stimWindow + obj.DaqSession.addDigitalChannel(rig.stimWindow.DaqDev,... + rig.stimWindow.DaqSyncEchoPort, 'OutputOnly'); + % Output an initial 0V signal + obj.DaqSession.outputSingleScan(false); + end + % Initialize the sync square colour to be index 1 obj.NextSyncIdx = 1; + % Get the handle to the PTB window opened by expServer obj.StimWindowPtr = rig.stimWindow.PtbHandle; + % Generate the viewing model based on the screen information from the + % rig struct, if availiable obj.Occ = vis.init(obj.StimWindowPtr); if isfield(rig, 'screens') obj.Occ.screens = rig.screens; else warning('squeak:hw', 'No screen configuration specified. Visual locations will be wrong.'); end + % Load the DAQ outputs obj.DaqController = rig.daqController; + % Load the wheel obj.Wheel = rig.mouseInput; if isfield(rig, 'lickDetector') obj.LickDetector = rig.lickDetector; @@ -562,6 +613,15 @@ function init(obj) outlist = mapToCell(@(n,v)queuefun(['outputs.' n],v),... fieldnames(obj.Outputs), struct2cell(obj.Outputs)); obj.Listeners = vertcat(obj.Listeners, evtlist(:), outlist(:)); + + % open binary file for saving block data. This can later be retrieved + % in case of a crash + fprintf(1, 'opening binary file for writing\n'); + localPath = dat.expFilePath(obj.Data.expRef, 'block', 'local'); % get the local exp data path + obj.DataFID = fopen([localPath(1:end-4) '.dat'], 'w'); % open a binary data file + % save params now so if things crash later you at least have this record of the data type and size so you can load the dat + obj.DataFID(2) = fopen([localPath(1:end-4) '.par'], 'w'); % open a parameter file + end function cleanup(obj) @@ -642,7 +702,6 @@ function mainLoop(obj) %% check for and process any input checkInput(obj); - %% execute pending event handlers that have become due for i = 1:ndue due = obj.Pending(dueIdx(i)); @@ -680,6 +739,10 @@ function mainLoop(obj) Screen('FillRect', obj.StimWindowPtr, col, obj.SyncBounds); % cyclically increment the next sync idx obj.NextSyncIdx = mod(obj.NextSyncIdx, size(obj.SyncColourCycle, 1)) + 1; + if ~isempty(obj.DaqSyncEcho) + % update sync echo + outputSingleScan(obj.DaqSyncEcho, mean(col) > 0); + end end renderTime = now(obj.Clock); % start the 'flip' of the frame onto the screen @@ -828,21 +891,20 @@ function saveData(obj) savepaths = dat.expFilePath(obj.Data.expRef, 'block'); superSave(savepaths, struct('block', obj.Data)); - if isempty(obj.AlyxInstance) + if ~obj.AlyxInstance.IsLoggedIn warning('No Alyx token set'); else try - [subject,~,~] = dat.parseExpRef(obj.Data.expRef); - if strcmp(subject,'default'); return; end + subject = dat.parseExpRef(obj.Data.expRef); + if strcmp(subject, 'default'); return; end % Register saved files - alyx.registerFile(savepaths{end}, 'mat',... - obj.AlyxInstance.subsessionURL, 'Block', [], obj.AlyxInstance); + obj.AlyxInstance.registerFile(savepaths{end}, 'mat',... + obj.AlyxInstance.subsessionURL, 'Block', []); % Save the session end time - alyx.putData(obj.AlyxInstance, obj.AlyxInstance.subsessionURL,... - struct('end_time', alyx.datestr(now), 'subject', subject)); + obj.AlyxInstance.putData(obj.AlyxInstance.SessionURL,... + struct('end_time', obj.AlyxInstance.datestr(now), 'subject', subject)); catch ex - warning('couldnt register files to alyx'); - disp(ex) + warning(ex.identifer, 'Failed to register files to Alyx: %s', ex.message); end end diff --git a/+exp/StartServices.m b/+exp/StartServices.m index 0c3ac13c..8c8b72bb 100644 --- a/+exp/StartServices.m +++ b/+exp/StartServices.m @@ -27,13 +27,16 @@ obj.Services = value; end - function perform(obj, eventInfo, dueTime) - ref = dat.parseAlyxInstance(eventInfo.Experiment.Data.expRef,... - eventInfo.Experiment.AlyxInstance); + function perform(obj, eventInfo, ~) + %PERFORM Starts each service sequentially + % perform(obj, eventInfo, dueTime) + % + expRef = eventInfo.Experiment.Data.expRef; + ai = eventInfo.Experiment.AlyxInstance; n = numel(obj.Services); for i = 1:n try - obj.Services{i}.start(ref); + obj.Services{i}.start(expRef, ai); fprintf('Started ''%s''\n', obj.Services{i}.Title); catch ex %stop services that were started up till now diff --git a/+hw/Timeline.m b/+hw/Timeline.m index e03e212a..e83b455e 100644 --- a/+hw/Timeline.m +++ b/+hw/Timeline.m @@ -463,11 +463,11 @@ function stop(obj) end % register Timeline.mat file to Alyx database - [subject,~,~] = dat.parseExpRef(obj.Data.expRef); - if ~isempty(obj.AlyxInstance) && ~strcmp(subject,'default') + subject = dat.parseExpRef(obj.Data.expRef); + if obj.AlyxInstance.IsLoggedIn && ~strcmp(subject,'default') try - alyx.registerFile(obj.Data.savePaths{end}, 'mat',... - obj.AlyxInstance.subsessionURL, 'Timeline', [], obj.AlyxInstance); + obj.AlyxInstance.registerFile(obj.Data.savePaths{end}, 'mat',... + obj.AlyxInstance.SessionURL, 'Timeline', []); catch warning('couldn''t register files to alyx'); end diff --git a/+srv/StimulusControl.m b/+srv/StimulusControl.m index a8b4e1b8..34db88fe 100644 --- a/+srv/StimulusControl.m +++ b/+srv/StimulusControl.m @@ -268,8 +268,10 @@ function send(obj, id, data) assert(~isNil(msg), 'Timed out waiting for message with id ''%s''', id); remove(obj.Responses, id); % no longer waiting, remove place holder end - - function errorOnFail(obj, r) + end + + methods (Static) + function errorOnFail(r) if iscell(r) && strcmp(r{1}, 'fail') error(r{3}); end diff --git a/+srv/expServer.m b/+srv/expServer.m index 4e4bec05..1ac91302 100644 --- a/+srv/expServer.m +++ b/+srv/expServer.m @@ -7,7 +7,7 @@ function expServer(useTimelineOverride, bgColour) % 2013-06 CB created %% Parameters -global AGL GL GLU +global AGL GL GLU %#ok<NUSED> listenPort = io.WSJCommunicator.DefaultListenPort; quitKey = KbName('q'); rewardToggleKey = KbName('w'); @@ -23,7 +23,7 @@ function expServer(useTimelineOverride, bgColour) rng('shuffle'); % communicator for receiving commands from clients communicator = io.WSJCommunicator.server(listenPort); -listener = event.listener(communicator, 'MessageReceived',... +addlistener(communicator, 'MessageReceived',... @(~,msg) handleMessage(msg.Id, msg.Data, msg.Sender)); communicator.EventMode = false; communicator.open(); diff --git a/cortexlab/+io/MpepUDPDataHosts.m b/cortexlab/+io/MpepUDPDataHosts.m index 1f476d97..07bd3cea 100644 --- a/cortexlab/+io/MpepUDPDataHosts.m +++ b/cortexlab/+io/MpepUDPDataHosts.m @@ -20,7 +20,7 @@ DigitalOutDaqChannelId Verbose = false % whether to output I/O messages etc Timeline % An instance of timeline for for recording UDP messages - AlyxInstance + AlyxInstance % An instance of Alyx for registering files, etc. end properties (SetAccess = protected) @@ -41,8 +41,8 @@ properties (Access = private) ExpRef pResponseTimeout = 10 - %When an mpep UDP is sent out, the message is saved here to later check - %the same message is received back + % When an mpep UDP is sent out, the message is saved here to later check + % the same message is received back LastSentMessage DigitalOutSession Timer @@ -62,11 +62,11 @@ function open(obj) obj.Socket = pnet('udpsocket', obj.LocalPort); % bind listening socket - % set timeout to intial value + % Set timeout to intial value pnet(obj.Socket, 'setreadtimeout', obj.ResponseTimeout); - % save IP addresses for remote hosts + % Save IP addresses for remote hosts obj.RemoteIPs = ipaddress(obj.RemoteHosts); - % open the DAQ session, if configured + % Open the DAQ session, if configured if ~isempty(obj.DigitalOutDaqChannelId) obj.DigitalOutSession = daq.createSession(obj.DaqVendor); obj.DigitalOutSession.addDigitalChannel(... @@ -76,7 +76,7 @@ function open(obj) end function close(obj) - % cleanup timeout timer, close network socket and release DAQ session + % Cleanup timeout timer, close network socket and release DAQ session if ~isempty(obj.Timer) stop(obj.Timer); delete(obj.Timer); @@ -109,12 +109,12 @@ function delete(obj) function expStarted(obj, ref) obj.ExpRef = ref; % save the experiment reference - %% send the ExpStart UDP + % Send the ExpStart UDP [subject, seriesNum, expNum] = dat.expRefToMpep(obj.ExpRef); expStartMsg = sprintf('ExpStart %s %d %d', subject, seriesNum, expNum); confirmedBroadcast(obj, expStartMsg); - %% send the BlockStart UDP + % Send the BlockStart UDP % start a block (we only use one per experiment) blockStartMsg = sprintf('BlockStart %s %d %d 1', subject, seriesNum, expNum); confirmedBroadcast(obj, blockStartMsg); @@ -123,16 +123,16 @@ function expStarted(obj, ref) function stimStarted(obj, num, duration) validateResponses(obj); % validate any outstanding responses obj.StimNum = num; - %% send the StimStart UDP + % Send the StimStart UDP [subject, seriesNum, expNum] = dat.expRefToMpep(obj.ExpRef); msg = sprintf('StimStart %s %d %d 1 %d %d',... %2014/4/8 DS: why trial number is always 1??? subject, seriesNum, expNum, obj.StimNum, duration + 1); confirmedBroadcast(obj, msg); - % create a timeout timer + % Create a timeout timer timeoutTimer = timer('StartDelay', duration, 'TimerFcn', @(t,d) stimEnded(obj, num)); - %% set digital out line up if any + % Set digital out line up if any if ~isempty(obj.DigitalOutSession) obj.DigitalOutSession.outputSingleScan(true); if obj.Verbose @@ -159,14 +159,14 @@ function stimEnded(obj, num) delete(obj.Timer); obj.Timer = []; end - %% set digital out line down if any + % Set digital out line down if any if ~isempty(obj.DigitalOutSession) obj.DigitalOutSession.outputSingleScan(false); if obj.Verbose fprintf('DAQ digital out -> false\n'); end end - %% send the StimEnd UDP + % Send the StimEnd UDPS [subject, seriesNum, expNum] = dat.expRefToMpep(obj.ExpRef); msg = sprintf('StimEnd %s %d %d 1 %d', subject, seriesNum, expNum, num); broadcast(obj, msg); @@ -182,38 +182,41 @@ function stimEnded(obj, num) end function expEnded(obj) - %% If StimStart was sent without corresponding StimEnd, send it now + % If StimStart was sent without corresponding StimEnd, send it now if obj.StimOn stimEnded(obj); end validateResponses(obj); % validate any outstanding responses - %% send the BlockEnd UDP + % Send the BlockEnd UDP [subject, seriesNum, expNum] = dat.expRefToMpep(obj.ExpRef); blockEndMsg = sprintf('BlockEnd %s %d %d 1', subject, seriesNum, expNum); confirmedBroadcast(obj, blockEndMsg); - %% send the ExpEnd UDP - % start a block (we only use one per experiment) + % Send the ExpEnd UDP + % Start a block (we only use one per experiment) expEndMsg = sprintf('ExpEnd %s %d %d', subject, seriesNum, expNum); confirmedBroadcast(obj, expEndMsg); obj.ExpRef = []; end - function start(obj, ref) - [expRef, ai] = dat.parseAlyxInstance(ref); - obj.AlyxInstance = ai; - [subject, seriesNum, expNum] = dat.expRefToMpep(expRef); - alyxmsg = sprintf('alyx %s %d %d %s', subject, seriesNum, expNum, ref); - confirmedBroadcast(obj, alyxmsg); + function start(obj, expRef, ai) + % Deal with Alyx instance first + if ~isempty(ai) + obj.AlyxInstance = ai; + UDP_msg = ai.parseAlyxInstance; + [subject, seriesNum, expNum] = dat.expRefToMpep(expRef); + alyxmsg = sprintf('alyx %s %d %d %s', subject, seriesNum, expNum, UDP_msg); + confirmedBroadcast(obj, alyxmsg); + end - % equivalent to startExp(expRef) + % Equivalent to startExp(expRef) expStarted(obj, expRef); end function stop(obj) - % equivalent to endExp() + % Equivalent to endExp() expEnded(obj); end @@ -221,7 +224,7 @@ function stop(obj) obj.broadcast('hello'); try ok = awaitResponses(obj); - catch ex + catch ok = false; end end @@ -243,14 +246,14 @@ function confirmedBroadcast(obj, msg) end function broadcast(obj, msg) - % send UDP to all remote hosts + % Send UDP to all remote hosts cellfun(@(host) obj.sendPacket(msg, host), obj.RemoteHosts); obj.LastSentMessage = msg; end function validateResponses(obj) - %If a recent UDP message was sent and the response hasn't been - %received and checked that it matches what was sent, check it now. + % If a recent UDP message was sent and the response hasn't been + % received and checked that it matches what was sent, check it now. if ~isempty(obj.LastSentMessage) ok = awaitResponses(obj); assert(all(ok), 'A valid UDP confirmation was not received within timeout period'); @@ -258,12 +261,12 @@ function validateResponses(obj) end function ok = awaitResponses(obj) - %% check response is what we sent (with Timeout) + % Check response is what we sent (with Timeout) expecting = obj.LastSentMessage; % IP address of remote hosts we are expecting confirmation from waiting = ipaddress(obj.RemoteHosts); ok = false(size(waiting)); - % receive 'num remote hosts' of packets + % Receive 'num remote hosts' of packets for i = 1:numel(obj.RemoteHosts) tic [msg, ip] = obj.readPacket; @@ -279,7 +282,7 @@ function validateResponses(obj) end function sendPacket(obj, msg, host) - % send the packet with 'msg' + % Send the packet with 'msg' pnet(obj.Socket, 'write', msg); pnet(obj.Socket, 'writepacket', host, obj.RemotePort); if obj.Verbose From c84aecbb3419556469b71b1e2faef2343abbb0e9 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Mon, 5 Feb 2018 12:18:13 +0000 Subject: [PATCH 068/393] Moved non-alyx related changes to different branch --- +exp/SignalsExp.m | 63 +++++------------------------------------------ 1 file changed, 6 insertions(+), 57 deletions(-) diff --git a/+exp/SignalsExp.m b/+exp/SignalsExp.m index 96d8a62f..cbcacb1f 100644 --- a/+exp/SignalsExp.m +++ b/+exp/SignalsExp.m @@ -69,7 +69,7 @@ LayersByStim %Occulus viewing model - Occ + Occ Time @@ -96,13 +96,9 @@ SyncColourCycle %Index into SyncColourCycle for next sync colour - NextSyncIdx + NextSyncIdx - %Holds the session for the DAQ photodiode echo, used if - %rig.stimWindow.DaqSyncEchoPort is defined. See also USERIG - DaqSyncEcho - - % Alyx instance from client. See also SAVEDATA + %Alyx instance from client. See also SAVEDATA AlyxInstance = [] end @@ -113,9 +109,6 @@ %Data from the currently running experiment, if any. Data = struct - %Data binary file ID and pars file ID for intermittent saving - DataFID - %Currently active phases of the experiment. Cell array of their names %(i.e. strings) ActivePhases = {} @@ -126,6 +119,7 @@ SignalUpdates = struct('name', cell(500,1), 'value', cell(500,1), 'timestamp', cell(500,1)) NumSignalUpdates = 0 + end properties (Access = protected) @@ -133,8 +127,7 @@ %are awaiting activation pending completion of their delay period. Pending - %Flag indicating whether to continue in experiment loop - IsLooping = false + IsLooping = false %flag indicating whether to continue in experiment loop AsyncFlipping = false @@ -213,48 +206,19 @@ end function useRig(obj, rig) - % USERIG(OBJ, RIG) Initialize all hardware for experiment - % Takes the rig hardware structure and loads the relevant - % parameters into the class properties, namely the DAQ output - % channels and the stimWindow properties. - % - % See Also HW.PTB.WINDOW, DAQCONTROLLER - obj.Clock = rig.clock; obj.Data.rigName = rig.name; - % Sync bounds parameter for photodiode square obj.SyncBounds = rig.stimWindow.SyncBounds; - % Sync colour cycle (usually [0, 255]) - cycles through these each - % time the screen flips. obj.SyncColourCycle = rig.stimWindow.SyncColourCycle; - % If the DaqSyncEchoPort is defined, each time the screen flips, the - % DAQ will output an alternating high or low. This signals will - % follow the photodiode signal. - if ~isempty(rig.stimWindow.DaqSyncEchoPort) - % Create the DAQ session for the sync echo - obj.DaqSyncEcho = daq.createSession(rig.stimWindow.DaqVendor); - % Add the digital channel using parameters defined in - % rig.stimWindow - obj.DaqSession.addDigitalChannel(rig.stimWindow.DaqDev,... - rig.stimWindow.DaqSyncEchoPort, 'OutputOnly'); - % Output an initial 0V signal - obj.DaqSession.outputSingleScan(false); - end - % Initialize the sync square colour to be index 1 obj.NextSyncIdx = 1; - % Get the handle to the PTB window opened by expServer obj.StimWindowPtr = rig.stimWindow.PtbHandle; - % Generate the viewing model based on the screen information from the - % rig struct, if availiable obj.Occ = vis.init(obj.StimWindowPtr); if isfield(rig, 'screens') obj.Occ.screens = rig.screens; else warning('squeak:hw', 'No screen configuration specified. Visual locations will be wrong.'); end - % Load the DAQ outputs obj.DaqController = rig.daqController; - % Load the wheel obj.Wheel = rig.mouseInput; if isfield(rig, 'lickDetector') obj.LickDetector = rig.lickDetector; @@ -613,15 +577,6 @@ function init(obj) outlist = mapToCell(@(n,v)queuefun(['outputs.' n],v),... fieldnames(obj.Outputs), struct2cell(obj.Outputs)); obj.Listeners = vertcat(obj.Listeners, evtlist(:), outlist(:)); - - % open binary file for saving block data. This can later be retrieved - % in case of a crash - fprintf(1, 'opening binary file for writing\n'); - localPath = dat.expFilePath(obj.Data.expRef, 'block', 'local'); % get the local exp data path - obj.DataFID = fopen([localPath(1:end-4) '.dat'], 'w'); % open a binary data file - % save params now so if things crash later you at least have this record of the data type and size so you can load the dat - obj.DataFID(2) = fopen([localPath(1:end-4) '.par'], 'w'); % open a parameter file - end function cleanup(obj) @@ -739,10 +694,6 @@ function mainLoop(obj) Screen('FillRect', obj.StimWindowPtr, col, obj.SyncBounds); % cyclically increment the next sync idx obj.NextSyncIdx = mod(obj.NextSyncIdx, size(obj.SyncColourCycle, 1)) + 1; - if ~isempty(obj.DaqSyncEcho) - % update sync echo - outputSingleScan(obj.DaqSyncEcho, mean(col) > 0); - end end renderTime = now(obj.Clock); % start the 'flip' of the frame onto the screen @@ -907,9 +858,7 @@ function saveData(obj) warning(ex.identifer, 'Failed to register files to Alyx: %s', ex.message); end end - end end -end - +end \ No newline at end of file From 71115a34a1b9b153402b539e94f1b503546b6c27 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Mon, 5 Feb 2018 13:19:43 +0000 Subject: [PATCH 069/393] Fix for new experiments without alyx instance --- +dat/newExp.m | 108 +++++++++++++++++++++++++------------------------- 1 file changed, 53 insertions(+), 55 deletions(-) diff --git a/+dat/newExp.m b/+dat/newExp.m index 6a86e07f..acd3c48e 100644 --- a/+dat/newExp.m +++ b/+dat/newExp.m @@ -30,9 +30,9 @@ expParams = []; end -if nargin < 4 +if nargin < 4 || isempty(AlyxInstance) % no instance of Alyx, don't create session on Alyx - AlyxInstance = []; + AlyxInstance = alyx.loginWindow; end if ischar(expDate) @@ -66,69 +66,67 @@ % now make the folder(s) to hold the new experiment assert(all(cellfun(@(p) mkdir(p), expPath)), 'Creating experiment directories failed'); -if ~strcmp(subject, 'default') % Ignore fake subject +if ~strcmp(subject,'default') % Ignore fake subject % if the Alyx Instance is set, find or create BASE session expDate = alyx.datestr(expDate); % date in Alyx format - if ~isempty(AlyxInstance) - % Get list of base sessions - sessions = alyx.getData(AlyxInstance,... - ['sessions?type=Base&subject=' subject]); - - %If the date of this latest base session is not the same date as - %today, then create a new base session for today - if isempty(sessions) || ~strcmp(sessions{end}.start_time(1:10), expDate(1:10)) - d = struct; - d.subject = subject; - d.procedures = {'Behavior training/tasks'}; - d.narrative = 'auto-generated session'; - d.start_time = expDate; - d.type = 'Base'; - % d.users = {AlyxInstance.username}; - - base_submit = alyx.postData(AlyxInstance, 'sessions', d); - assert(isfield(base_submit,'subject'),... - 'Submitted base session did not return appropriate values'); - - %Now retrieve the sessions again - sessions = alyx.getData(AlyxInstance,... - ['sessions?type=Base&subject=' subject]); - end - latest_base = sessions{end}; - - %Now create a new SUBSESSION, using the same experiment number + % Get list of base sessions + sessions = alyx.getData(AlyxInstance,... + ['sessions?type=Base&subject=' subject]); + + %If the date of this latest base session is not the same date as + %today, then create a new base session for today + if isempty(sessions) || ~strcmp(sessions{end}.start_time(1:10), expDate(1:10)) d = struct; d.subject = subject; d.procedures = {'Behavior training/tasks'}; d.narrative = 'auto-generated session'; d.start_time = expDate; - d.type = 'Experiment'; - d.parent_session = latest_base.url; - d.number = expSeq; - % d.users = {AlyxInstance.username}; + d.type = 'Base'; + % d.users = {AlyxInstance.username}; - try - subsession = alyx.postData(AlyxInstance, 'sessions', d); - url = subsession.url; - catch - url = []; - end - else % If not logged in to Alyx... - url = []; % set the base url to null - end - - % if the parameters had an experiment definition function, save a copy in - % the experiment's folder - if isfield(expParams, 'defFunction') - assert(file.exists(expParams.defFunction),... - 'Experiment definition function does not exist: %s', expParams.defFunction); - assert(all(cellfun(@(p)copyfile(expParams.defFunction, p),... - dat.expFilePath(expRef, 'expDefFun'))),... - 'Copying definition function to experiment folders failed'); + base_submit = alyx.postData(AlyxInstance, 'sessions', d); + assert(isfield(base_submit,'subject'),... + 'Submitted base session did not return appropriate values'); + + %Now retrieve the sessions again + sessions = alyx.getData(AlyxInstance,... + ['sessions?type=Base&subject=' subject]); end + latest_base = sessions{end}; - % now save the experiment parameters variable - superSave(dat.expFilePath(expRef, 'parameters'), struct('parameters', expParams)); + %Now create a new SUBSESSION, using the same experiment number + d = struct; + d.subject = subject; + d.procedures = {'Behavior training/tasks'}; + d.narrative = 'auto-generated session'; + d.start_time = expDate; + d.type = 'Experiment'; + d.parent_session = latest_base.url; + d.number = expSeq; + % d.users = {AlyxInstance.username}; + subsession = alyx.postData(AlyxInstance, 'sessions', d); + assert(isfield(subsession,'subject'),... + 'Failed to create new sub-session in Alyx for %s', subject); + url = subsession.url; +else + url = []; +end + +% if the parameters had an experiment definition function, save a copy in +% the experiment's folder +if isfield(expParams, 'defFunction') + assert(file.exists(expParams.defFunction),... + 'Experiment definition function does not exist: %s', expParams.defFunction); + assert(all(cellfun(@(p)copyfile(expParams.defFunction, p),... + dat.expFilePath(expRef, 'expDefFun'))),... + 'Copying definition function to experiment folders failed'); +end + +% now save the experiment parameters variable +superSave(dat.expFilePath(expRef, 'parameters'), struct('parameters', expParams)); + +if ~isempty(expParams) try % save a copy of parameters in json % First, change all functions to strings f_idx = structfun(@(s)isa(s, 'function_handle'), expParams); @@ -152,5 +150,5 @@ url, 'Parameters', [], AlyxInstance); end end - +end end \ No newline at end of file From 9621ddcb42fbd68a43de46164e46b7c8136ec470 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Mon, 5 Feb 2018 13:47:05 +0000 Subject: [PATCH 070/393] Doesn't force login if subject is 'default' --- +dat/newExp.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/+dat/newExp.m b/+dat/newExp.m index acd3c48e..2358ac04 100644 --- a/+dat/newExp.m +++ b/+dat/newExp.m @@ -30,7 +30,7 @@ expParams = []; end -if nargin < 4 || isempty(AlyxInstance) +if (nargin < 4 || isempty(AlyxInstance)) && ~strcmp(subject, 'default') % no instance of Alyx, don't create session on Alyx AlyxInstance = alyx.loginWindow; end @@ -66,7 +66,7 @@ % now make the folder(s) to hold the new experiment assert(all(cellfun(@(p) mkdir(p), expPath)), 'Creating experiment directories failed'); -if ~strcmp(subject,'default') % Ignore fake subject +if ~strcmp(subject, 'default') % Ignore fake subject % if the Alyx Instance is set, find or create BASE session expDate = alyx.datestr(expDate); % date in Alyx format % Get list of base sessions @@ -139,13 +139,13 @@ [expRef, '_parameters.json']); savejson('parameters', expParams, jsonPath); % Register our JSON parameter set to Alyx - if ~strcmp(subject,'default') + if ~strcmp(subject, 'default') alyx.registerFile(jsonPath, 'json', url, 'Parameters', [], AlyxInstance); end catch ex warning(ex.identifier, 'Failed to save paramters as JSON: %s.\n Registering mat file instead', ex.message) % Register our parameter set to Alyx - if ~strcmp(subject,'default') + if ~strcmp(subject, 'default') alyx.registerFile(dat.expFilePath(expRef, 'parameters', 'master'), 'mat',... url, 'Parameters', [], AlyxInstance); end From 87184782e29a7ad7beb2ee1f69cb304ac9f0982c Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 6 Feb 2018 18:19:24 +0000 Subject: [PATCH 071/393] Fixes for file registration Fix'd some typos --- +exp/Experiment.m | 4 ++-- +exp/SignalsExp.m | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/+exp/Experiment.m b/+exp/Experiment.m index e26860a0..5671b983 100644 --- a/+exp/Experiment.m +++ b/+exp/Experiment.m @@ -781,12 +781,12 @@ function saveData(obj) if strcmp(subject, 'default'); return; end % Register saved files obj.AlyxInstance.registerFile(savepaths{end}, 'mat',... - obj.AlyxInstance.subsessionURL, 'Block', []); + obj.AlyxInstance.SessionURL, 'Block', []); % Save the session end time obj.AlyxInstance.putData(obj.AlyxInstance.SessionURL,... struct('end_time', obj.AlyxInstance.datestr(now), 'subject', subject)); catch ex - warning(ex.identifer, 'Failed to register files to Alyx: %s', ex.message); + warning(ex.identifier, 'Failed to register files to Alyx: %s', ex.message); end end end diff --git a/+exp/SignalsExp.m b/+exp/SignalsExp.m index cbcacb1f..f83e238b 100644 --- a/+exp/SignalsExp.m +++ b/+exp/SignalsExp.m @@ -850,12 +850,12 @@ function saveData(obj) if strcmp(subject, 'default'); return; end % Register saved files obj.AlyxInstance.registerFile(savepaths{end}, 'mat',... - obj.AlyxInstance.subsessionURL, 'Block', []); + obj.AlyxInstance.SessionURL, 'Block', []); % Save the session end time obj.AlyxInstance.putData(obj.AlyxInstance.SessionURL,... struct('end_time', obj.AlyxInstance.datestr(now), 'subject', subject)); catch ex - warning(ex.identifer, 'Failed to register files to Alyx: %s', ex.message); + warning(ex.identifier, 'Failed to register files to Alyx: %s', ex.message); end end end From 48d13432569b08d0686552145f60996fa3508cce Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 7 Feb 2018 13:10:15 +0000 Subject: [PATCH 072/393] Fix'd expServer quit bug Listeners properly deleted and expServer now ends when q key is pressed, as before. --- +srv/expServer.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/+srv/expServer.m b/+srv/expServer.m index 1ac91302..a4c6149d 100644 --- a/+srv/expServer.m +++ b/+srv/expServer.m @@ -23,7 +23,7 @@ function expServer(useTimelineOverride, bgColour) rng('shuffle'); % communicator for receiving commands from clients communicator = io.WSJCommunicator.server(listenPort); -addlistener(communicator, 'MessageReceived',... +listener = event.listener(communicator, 'MessageReceived',... @(~,msg) handleMessage(msg.Id, msg.Data, msg.Sender)); communicator.EventMode = false; communicator.open(); @@ -46,6 +46,7 @@ function expServer(useTimelineOverride, bgColour) cleanup = onCleanup(@() fun.applyForce({ @() communicator.close(),... + @() delete(listener),... @ShowCursor,... @KbQueueRelease,... @() rig.stimWindow.close(),... From 2d464423799b26edb9ee0830dfee78b32d2d9d47 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 7 Feb 2018 18:59:49 +0000 Subject: [PATCH 073/393] Fixes to AlyxPanel and ExpPanel * Comments now saved properly in ExpPanel * More detail given about subject in stand-alone AlyxPanel * PutData now a public method :'( --- +dat/updateLogEntry.m | 6 ++++-- +eui/AlyxPanel.m | 36 ++++++++++++++++++++++++++++++------ +eui/ExpPanel.m | 2 +- +eui/MControl.m | 3 ++- 4 files changed, 37 insertions(+), 10 deletions(-) diff --git a/+dat/updateLogEntry.m b/+dat/updateLogEntry.m index bf3e94ee..ecbf3265 100644 --- a/+dat/updateLogEntry.m +++ b/+dat/updateLogEntry.m @@ -10,9 +10,11 @@ function updateLogEntry(subject, id, newEntry) % 2013-03 CB created -if isfield(newEntry, 'AlyxInstance')&&~isempty(newEntry.comments) +if isfield(newEntry, 'AlyxInstance') % Update session narrative on Alyx - newEntry.AlyxInstance.updateNarrative(subject, obj.LogEntry.comments); + if ~isempty(newEntry.comments) + newEntry.comments = newEntry.AlyxInstance.updateNarrative(subject, newEntry.comments); + end newEntry = rmfield(newEntry, 'AlyxInstance'); end diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index 86349026..48fb4f70 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -263,6 +263,7 @@ function login(obj) notify(obj, 'Disconnected'); % Notify listeners of logout obj.log('Logged out of Alyx'); end + obj.dispWaterReq() end function giveWater(obj) @@ -320,12 +321,35 @@ function dispWaterReq(obj, src, ~) stop(obj.LoginTimer); start(obj.LoginTimer) try s = ai.getData(ai.makeEndpoint(['subjects/' obj.Subject])); % struct with data about the subject - if s.water_requirement_total==0 - set(obj.WaterRequiredText, 'String', sprintf('Subject %s not on water restriction', obj.Subject)); + if s.water_requirement_total==0 % Subject not on water restriction + set(obj.WaterRequiredText, 'ForegroundColor', 'black',... + 'String', sprintf('Subject %s not on water restriction', obj.Subject)); else - set(obj.WaterRequiredText, 'String', ... - sprintf('Subject %s requires %.2f of %.2f today', ... - obj.Subject, s.water_requirement_remaining, s.water_requirement_total)); + % Get information on weight and water given + endpnt = sprintf('water-requirement/%s?start_date=%s&end_date=%s',... + obj.Subject, datestr(now, 'yyyy-mm-dd'),datestr(now, 'yyyy-mm-dd')); + wr = ai.getData(endpnt); % Get today's weight and water record + record = wr.records{1}; + weight = getOr(record, 'weight_measured', NaN); % Get today's measured weight + water = getOr(record, 'water_given', 0); % Get total water given + gel = getOr(record, 'hydrogel_given', 0); % Get total gel given + % Set colour based on weight percentage + weight_pct = weight/record.weight_expected; + if weight_pct < 0.8 % Mouse below 80% original weight + colour = [0.91, 0.41, 0.17]; % Orange + weight_pct = '< 80%'; + elseif weight_pct < 0.7 % Mouse below 70% original weight + colour = 'red'; + weight_pct = '< 70%'; + else + colour = 'black'; % Mouse above 80% or no weight measured today + weight_pct = '> 80%'; + end + % Set text + set(obj.WaterRequiredText, 'ForegroundColor', colour, 'String', ... + sprintf('Subject %s requires %.2f of %.2f today\n\t Weight today: %.2f (%s) Water today: %.2f', ... + obj.Subject, s.water_requirement_remaining, s.water_requirement_total, weight, weight_pct, sum([water gel]))); + % Set WaterRemaining attribute for changeWaterText callback obj.WaterRemaining = s.water_requirement_remaining; end catch me @@ -345,7 +369,7 @@ function changeWaterText(obj, src, ~) % return will display this without posting to Alyx % % See also DISPWATERREQ, GIVEWATER - if ~obj.AlyxInstance.IsLoggedIn && ~isempty(obj.WaterRemaining) + if obj.AlyxInstance.IsLoggedIn && ~isempty(obj.WaterRemaining) rem = obj.WaterRemaining; curr = str2double(src.String); set(obj.WaterRemainingText, 'String', sprintf('(%.2f)', rem-curr)); diff --git a/+eui/ExpPanel.m b/+eui/ExpPanel.m index 4e6c2cb0..7878f6da 100644 --- a/+eui/ExpPanel.m +++ b/+eui/ExpPanel.m @@ -298,7 +298,7 @@ function saveLogEntry(obj) % subsession's narrative field. % % See also DAT.UPDATELOGENTRY, COMMENTSCHANGED - dat.updateLogEntry(dat.parseExpRef(obj.SubjectRef), obj.LogEntry.id, obj.LogEntry); + dat.updateLogEntry(obj.SubjectRef, obj.LogEntry.id, obj.LogEntry); end function viewParams(obj) diff --git a/+eui/MControl.m b/+eui/MControl.m index 9d706860..23beda8f 100644 --- a/+eui/MControl.m +++ b/+eui/MControl.m @@ -537,8 +537,9 @@ function beginExp(obj) if ~ai.IsLoggedIn && ~strcmp(obj.NewExpSubject.Selected,'default') try obj.AlyxPanel.login(); + assert(ai.IsLoggedIn); catch - log('Warning: Must be logged in to Alyx before running an experiment') + obj.log('Warning: Must be logged in to Alyx before running an experiment') return end end From 4f9eaeae0732d6e8fdaf53d331dddad057f5d6a2 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 8 Feb 2018 11:29:46 +0000 Subject: [PATCH 074/393] Headless flag set by expServer * expServer now won't try to show dialogs * update to posting Alyx narrative --- +dat/updateLogEntry.m | 2 +- +srv/expServer.m | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/+dat/updateLogEntry.m b/+dat/updateLogEntry.m index ecbf3265..7970edea 100644 --- a/+dat/updateLogEntry.m +++ b/+dat/updateLogEntry.m @@ -13,7 +13,7 @@ function updateLogEntry(subject, id, newEntry) if isfield(newEntry, 'AlyxInstance') % Update session narrative on Alyx if ~isempty(newEntry.comments) - newEntry.comments = newEntry.AlyxInstance.updateNarrative(subject, newEntry.comments); + newEntry.comments = newEntry.AlyxInstance.updateNarrative(newEntry.comments); end newEntry = rmfield(newEntry, 'AlyxInstance'); end diff --git a/+srv/expServer.m b/+srv/expServer.m index a4c6149d..f43a1749 100644 --- a/+srv/expServer.m +++ b/+srv/expServer.m @@ -171,6 +171,7 @@ function handleMessage(id, data, host) case 'run' % exp run request [expRef, preDelay, postDelay, Alyx] = args{:}; + Alyx.Headless = true; % Supress all dialog prompts if dat.expExists(expRef) log('Starting experiment ''%s''', expRef); communicator.send(id, []); From 0b1b5c61327ec528033ae876a9c548ad3353b22c Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 9 Feb 2018 14:45:52 +0000 Subject: [PATCH 075/393] Fix for when record empty in dispWaterReq --- +eui/AlyxPanel.m | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index 48fb4f70..1340d164 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -314,7 +314,8 @@ function dispWaterReq(obj, src, ~) % Set the selected subject if it is an input if nargin>1; obj.Subject = src.Selected; end if ~ai.IsLoggedIn - set(obj.WaterRequiredText, 'String', 'Log in to see water requirements'); + set(obj.WaterRequiredText, 'ForegroundColor', 'black',... + 'String', 'Log in to see water requirements'); return end % Refresh the timer as the user isn't inactive @@ -329,12 +330,17 @@ function dispWaterReq(obj, src, ~) endpnt = sprintf('water-requirement/%s?start_date=%s&end_date=%s',... obj.Subject, datestr(now, 'yyyy-mm-dd'),datestr(now, 'yyyy-mm-dd')); wr = ai.getData(endpnt); % Get today's weight and water record - record = wr.records{1}; + if ~isempty(wr.records) + record = wr.records{end}; + else + record = struct(); + end weight = getOr(record, 'weight_measured', NaN); % Get today's measured weight water = getOr(record, 'water_given', 0); % Get total water given gel = getOr(record, 'hydrogel_given', 0); % Get total gel given + weight_expected = getOr(record, 'weight_expected', NaN); % Set colour based on weight percentage - weight_pct = weight/record.weight_expected; + weight_pct = weight/weight_expected; if weight_pct < 0.8 % Mouse below 80% original weight colour = [0.91, 0.41, 0.17]; % Orange weight_pct = '< 80%'; @@ -355,7 +361,8 @@ function dispWaterReq(obj, src, ~) catch me d = loadjson(me.message); if isfield(d, 'detail') && strcmp(d.detail, 'Not found.') - set(obj.WaterRequiredText, 'String', sprintf('Subject %s not found in alyx', obj.Subject)); + set(obj.WaterRequiredText, 'ForegroundColor', 'black',... + 'String', sprintf('Subject %s not found in alyx', obj.Subject)); end end end From d813ff04c25ae26d738c415afed32f105af6bc5e Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 13 Feb 2018 10:52:52 +0000 Subject: [PATCH 076/393] Alyx now a value class We need it to be copyable and there are no plans for events --- +eui/AlyxPanel.m | 9 +++++---- +eui/MControl.m | 14 +++++++------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index 1340d164..ee9618af 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -205,12 +205,13 @@ function login(obj) % Are we logging in or out? if ~obj.AlyxInstance.IsLoggedIn % logging in % attempt login - obj.AlyxInstance.login(); % returns an instance if success, empty if you cancel + obj.AlyxInstance = obj.AlyxInstance.login(); % returns an instance if success, empty if you cancel if obj.AlyxInstance.IsLoggedIn % successful % Start log in timer, to automatically log out after 30 % minutes of 'inactivity' (defined as not calling % dispWaterReq) - obj.LoginTimer = timer('StartDelay', 30*60, 'TimerFcn', @(~,~)obj.login); + obj.LoginTimer = timer('StartDelay', 30*60, 'TimerFcn',... + @(~,~)obj.login, 'BusyMode', 'queue'); start(obj.LoginTimer) % Enable all buttons set(findall(obj.RootContainer, '-property', 'Enable'), 'Enable', 'on'); @@ -250,7 +251,7 @@ function login(obj) obj.log('Did not log into Alyx'); end else % logging out - obj.AlyxInstance.logout; + obj = obj.AlyxInstance.logout; if ~isempty(obj.LoginTimer) % If there is a timer object stop(obj.LoginTimer) % Stop the timer... delete(obj.LoginTimer) % ... delete it... @@ -319,7 +320,7 @@ function dispWaterReq(obj, src, ~) return end % Refresh the timer as the user isn't inactive - stop(obj.LoginTimer); start(obj.LoginTimer) + stop(obj.LoginTimer); start(obj.LoginTimer) try s = ai.getData(ai.makeEndpoint(['subjects/' obj.Subject])); % struct with data about the subject if s.water_requirement_total==0 % Subject not on water restriction diff --git a/+eui/MControl.m b/+eui/MControl.m index 23beda8f..d0dbc286 100644 --- a/+eui/MControl.m +++ b/+eui/MControl.m @@ -347,7 +347,8 @@ function rigExpStopped(obj, rig, evt) % Announce that the experiment has stopped obj.log('Warning: unable to query Alyx about %s''s water requirements', subject); end % Remove AlyxInstance from rig; no longer required - delete(rig.AlyxInstance); +% delete(rig.AlyxInstance); % Line invalid now Alyx no longer +% handel class rig.AlyxInstance = []; end end @@ -533,11 +534,10 @@ function beginExp(obj) set([obj.BeginExpButton obj.RigOptionsButton], 'Enable', 'off'); % Grey out buttons rig = obj.RemoteRigs.Selected; % Find which rig is selected % Save the current instance of Alyx so that eui.ExpPanel can register water to the correct account - ai = obj.AlyxPanel.AlyxInstance; - if ~ai.IsLoggedIn && ~strcmp(obj.NewExpSubject.Selected,'default') + if ~obj.AlyxPanel.AlyxInstance.IsLoggedIn && ~strcmp(obj.NewExpSubject.Selected,'default') try obj.AlyxPanel.login(); - assert(ai.IsLoggedIn); + assert(obj.AlyxPanel.AlyxInstance.IsLoggedIn); catch obj.log('Warning: Must be logged in to Alyx before running an experiment') return @@ -549,11 +549,11 @@ function beginExp(obj) obj.Parameters.set('services', services(:),... 'List of experiment services to use during the experiment'); % Create new experiment reference - [expRef, ~] = ai.newExp(obj.NewExpSubject.Selected, now,... - obj.Parameters.Struct); + [expRef, ~] = obj.AlyxPanel.AlyxInstance.newExp(... + obj.NewExpSubject.Selected, now, obj.Parameters.Struct); % Add a copy of the AlyxInstance to the rig object for later % water registration, &c. - rig.AlyxInstance = ai.copy; + rig.AlyxInstance = obj.AlyxPanel.AlyxInstance; panel = eui.ExpPanel.live(obj.ActiveExpsGrid, expRef, rig, obj.Parameters.Struct); obj.LastExpPanel = panel; From 723986c2dea77e9c6285a34d5cf8e19646e433e3 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 13 Feb 2018 13:28:16 +0000 Subject: [PATCH 077/393] Log out bug fix --- +eui/AlyxPanel.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index ee9618af..a5bff35b 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -251,7 +251,7 @@ function login(obj) obj.log('Did not log into Alyx'); end else % logging out - obj = obj.AlyxInstance.logout; + obj.AlyxInstance = obj.AlyxInstance.logout; if ~isempty(obj.LoginTimer) % If there is a timer object stop(obj.LoginTimer) % Stop the timer... delete(obj.LoginTimer) % ... delete it... From daa0e0d2a70d3235816a53c56614579f704d5516 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 14 Feb 2018 10:47:01 +0000 Subject: [PATCH 078/393] Registration without sessionURL --- +eui/MControl.m | 3 ++- +exp/Experiment.m | 12 ++++++++---- +exp/SignalsExp.m | 12 ++++++++---- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/+eui/MControl.m b/+eui/MControl.m index d0dbc286..3dd65802 100644 --- a/+eui/MControl.m +++ b/+eui/MControl.m @@ -549,11 +549,12 @@ function beginExp(obj) obj.Parameters.set('services', services(:),... 'List of experiment services to use during the experiment'); % Create new experiment reference - [expRef, ~] = obj.AlyxPanel.AlyxInstance.newExp(... + [expRef, ~, url] = obj.AlyxPanel.AlyxInstance.newExp(... obj.NewExpSubject.Selected, now, obj.Parameters.Struct); % Add a copy of the AlyxInstance to the rig object for later % water registration, &c. rig.AlyxInstance = obj.AlyxPanel.AlyxInstance; + rig.AlyxInstance.SessionURL = url; panel = eui.ExpPanel.live(obj.ActiveExpsGrid, expRef, rig, obj.Parameters.Struct); obj.LastExpPanel = panel; diff --git a/+exp/Experiment.m b/+exp/Experiment.m index 5671b983..a4c6bf33 100644 --- a/+exp/Experiment.m +++ b/+exp/Experiment.m @@ -777,14 +777,18 @@ function saveData(obj) warning('No Alyx token set'); else try - subject = dat.parseExpRef(obj.Data.expRef); + [subject, expDate, seq] = dat.parseExpRef(obj.Data.expRef); if strcmp(subject, 'default'); return; end % Register saved files obj.AlyxInstance.registerFile(savepaths{end}, 'mat',... - obj.AlyxInstance.SessionURL, 'Block', []); + {subject, expDate, seq}, 'Block', []); % Save the session end time - obj.AlyxInstance.putData(obj.AlyxInstance.SessionURL,... - struct('end_time', obj.AlyxInstance.datestr(now), 'subject', subject)); + if ~isempty(obj.AlyxInstance.SessionURL) + obj.AlyxInstance.putData(obj.AlyxInstance.SessionURL,... + struct('end_time', obj.AlyxInstance.datestr(now), 'subject', subject)); + else + % Infer from date session and retrieve using expFilePath + end catch ex warning(ex.identifier, 'Failed to register files to Alyx: %s', ex.message); end diff --git a/+exp/SignalsExp.m b/+exp/SignalsExp.m index f83e238b..12309f75 100644 --- a/+exp/SignalsExp.m +++ b/+exp/SignalsExp.m @@ -846,14 +846,18 @@ function saveData(obj) warning('No Alyx token set'); else try - subject = dat.parseExpRef(obj.Data.expRef); + [subject, expDate, seq] = dat.parseExpRef(obj.Data.expRef); if strcmp(subject, 'default'); return; end % Register saved files obj.AlyxInstance.registerFile(savepaths{end}, 'mat',... - obj.AlyxInstance.SessionURL, 'Block', []); + {subject, expDate, seq}, 'Block', []); % Save the session end time - obj.AlyxInstance.putData(obj.AlyxInstance.SessionURL,... - struct('end_time', obj.AlyxInstance.datestr(now), 'subject', subject)); + if ~isempty(obj.AlyxInstance.SessionURL) + obj.AlyxInstance.putData(obj.AlyxInstance.SessionURL,... + struct('end_time', obj.AlyxInstance.datestr(now), 'subject', subject)); + else + % Infer from date session and retrieve using expFilePath + end catch ex warning(ex.identifier, 'Failed to register files to Alyx: %s', ex.message); end From 1593968d6c52b9283bf230aad4150ba6455885b5 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 14 Feb 2018 10:48:50 +0000 Subject: [PATCH 079/393] Timeline registers files without URL --- +hw/Timeline.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/+hw/Timeline.m b/+hw/Timeline.m index e83b455e..50593790 100644 --- a/+hw/Timeline.m +++ b/+hw/Timeline.m @@ -463,11 +463,11 @@ function stop(obj) end % register Timeline.mat file to Alyx database - subject = dat.parseExpRef(obj.Data.expRef); + [subject, expDate, seq] = dat.parseExpRef(obj.Data.expRef); if obj.AlyxInstance.IsLoggedIn && ~strcmp(subject,'default') try obj.AlyxInstance.registerFile(obj.Data.savePaths{end}, 'mat',... - obj.AlyxInstance.SessionURL, 'Timeline', []); + {subject, expDate, seq}, 'Timeline', []); catch warning('couldn''t register files to alyx'); end From 6addcfbc56693729c148c758fa5269f485e0b90f Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 14 Feb 2018 11:23:33 +0000 Subject: [PATCH 080/393] UI Changes to MC * Rig list alphabetized * Can no longer begin experiment without loading params --- +eui/MControl.m | 17 ++++++++++++++--- +srv/stimulusControllers.m | 4 +++- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/+eui/MControl.m b/+eui/MControl.m index 3dd65802..367f9428 100644 --- a/+eui/MControl.m +++ b/+eui/MControl.m @@ -264,6 +264,7 @@ function loadParamProfile(obj, profile) matchTypes = factory(strcmp({factory.label}, typeName)).matchTypes(); subject = obj.NewExpSubject.Selected; % Find which subject is selected label = 'none'; + set(obj.BeginExpButton, 'Enable', 'off') % Can't run experiment without params! switch lower(profile) case '<defaults>' % if strcmp(obj.NewExpType.Selected, '<custom...>') @@ -308,6 +309,7 @@ function loadParamProfile(obj, profile) if ~isempty(paramStruct) % Now parameters are loaded, pass to ParamEditor for display, etc. obj.ParamEditor = eui.ParamEditor(obj.Parameters, obj.ParamPanel); % Build parameter list in Global panel by calling eui.ParamEditor obj.ParamEditor.addlistener('Changed', @(src,~) obj.paramChanged); + set(obj.BeginExpButton, 'Enable', 'on') % Re-enable start button end end @@ -332,7 +334,10 @@ function rigExpStarted(obj, rig, evt) % Announce that the experiment has started function rigExpStopped(obj, rig, evt) % Announce that the experiment has stopped in the log box obj.log('''%s'' on ''%s'' stopped', evt.Ref, rig.Name); if rig == obj.RemoteRigs.Selected - set([obj.BeginExpButton obj.RigOptionsButton], 'Enable', 'on'); % Re-enable 'Start' button so a new experiment can be started on that rig + set(obj.RigOptionsButton, 'Enable', 'on'); % Enable 'Options' + end + if obj.Parameters.Struct~=nil % If params loaded + set(obj.BeginExpButton, 'Enable', 'on'); % Re-enable 'Start' button so a new experiment can be started on that rig end % Alyx water reporting: indicate amount of water this mouse still needs if rig.AlyxInstance.IsLoggedIn @@ -394,7 +399,10 @@ function rigConnected(obj, rig, ~) else % The rig is idle... obj.log('Connected to ''%s''', rig.Name); % ...say so in the log box if rig == obj.RemoteRigs.Selected - set([obj.BeginExpButton obj.RigOptionsButton], 'Enable', 'on'); % Enable 'Start' button + set(obj.RigOptionsButton, 'Enable', 'on'); % Enable 'Options' button + end + if obj.Parameters.Struct~=nil % If parameters loaded + set(obj.BeginExpButton, 'Enable', 'on'); end end end @@ -445,7 +453,10 @@ function remoteRigChanged(obj) obj.log('Could not connect to ''%s'' (%s)', rig.Name, errmsg); end elseif strcmp(rig.Status, 'idle') - set([obj.BeginExpButton obj.RigOptionsButton], 'Enable', 'on'); + set(obj.RigOptionsButton, 'Enable', 'on'); + if obj.Parameters.Struct~=nil + set(obj.BeginExpButton, 'Enable', 'on'); + end else obj.rigConnected(rig); end diff --git a/+srv/stimulusControllers.m b/+srv/stimulusControllers.m index 05406f51..0ac89a83 100644 --- a/+srv/stimulusControllers.m +++ b/+srv/stimulusControllers.m @@ -9,6 +9,8 @@ p = dat.paths; sc = loadVar(fullfile(p.globalConfig, 'remote.mat'), 'stimulusControllers'); - +% Order alphabetically +[~, I] = sort(arrayfun(@(o)o.Name, sc, 'UniformOutput', false)); +sc = sc(I); end From efa4a5a8960bf0de6bf3f06ce579c2488b76f1af Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 14 Feb 2018 19:30:50 +0000 Subject: [PATCH 081/393] Fixes to BasicUDPService --- +hw/Timeline.m | 4 ++-- +srv/BasicUDPService.m | 33 ++++++++++++++++++++++----------- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/+hw/Timeline.m b/+hw/Timeline.m index 50593790..e9a6aac2 100644 --- a/+hw/Timeline.m +++ b/+hw/Timeline.m @@ -468,8 +468,8 @@ function stop(obj) try obj.AlyxInstance.registerFile(obj.Data.savePaths{end}, 'mat',... {subject, expDate, seq}, 'Timeline', []); - catch - warning('couldn''t register files to alyx'); + catch ex + warning(ex.identifier, 'couldn''t register files to Alyx: %s', ex.message); end end %TODO: Register ALF components to alyx, incl TimelineHW.json diff --git a/+srv/BasicUDPService.m b/+srv/BasicUDPService.m index f384704b..91779d86 100644 --- a/+srv/BasicUDPService.m +++ b/+srv/BasicUDPService.m @@ -131,13 +131,15 @@ function bind(obj) fopen(obj.Socket); end - function start(obj, expRef) + function start(obj, expRef, ai) + ref = Alyx.parseAlyxInstance(expRef, ai); % Send start message to remotehost and await confirmation - obj.confirmedSend(sprintf('GOGO%s*%s', expRef, hostname)); + obj.confirmedSend(sprintf('GOGO%s*%s', ref, hostname)); while obj.AwaitingConfirmation&&... ~strcmp(obj.LastReceivedMessage, obj.LastSentMessage) pause(0.2); end + if strcmp(obj.Status, 'exception'); error('Confirmation failed'); end end function stop(obj) @@ -163,14 +165,13 @@ function confirmedSend(obj, msg) % Add timer to impose a response timeout if ~isinf(obj.ResponseTimeout) obj.ResponseTimer = timer('StartDelay', obj.ResponseTimeout,... - 'TimerFcn', @(src,evt)obj.processMsg(src,evt), 'StopFcn', @(src,~)delete(src)); + 'TimerFcn', @(src,evt)obj.processMsg(src,evt), 'StopFcn', @(src,~)stop(src)); start(obj.ResponseTimer) % start the timer end end function receiveUDP(obj) obj.LastReceivedMessage = strtrim(fscanf(obj.Socket)); - % Remove any more accumulated inputs to the listener % obj.Socket.flushinput(); notify(obj, 'MessageReceived') @@ -196,16 +197,24 @@ function processMsg(obj, src, evt) return end % We no longer need the timer, stop it - if ~isempty(obj.ResponseTimer); stop(obj.ResponseTimer); end + if ~isempty(obj.ResponseTimer) + stop(obj.ResponseTimer); + delete(obj.ResponseTimer); + obj.ResponseTimer = []; + end if obj.AwaitingConfirmation % Reset AwaitingConfirmation obj.AwaitingConfirmation = false; - % Check the confirmation message is the same as the sent message - assert(~isempty(response)&&... % something received - strcmp(response.status, 'WHAT')||... % status update - strcmp(obj.LastReceivedMessage, obj.LastSentMessage),... % is echo - 'Confirmation failed') + try % Check the confirmation message is the same as the sent message + assert(~isempty(response)&&... % something received + strcmp(response.status, 'WHAT')||... % status update + strcmp(obj.LastReceivedMessage, obj.LastSentMessage),... % is echo + 'Confirmation failed') + catch ex + obj.Status = 'exception'; + rethrow(ex) + end end % At the moment we just disply some stuff, other functions listening % to the MessageReceived event can do their thing @@ -224,6 +233,7 @@ function processMsg(obj, src, evt) obj.LocalStatus = 'running'; obj.sendUDP(obj.LastReceivedMessage) catch ex + obj.LocalStatus = 'exception'; error('Failed to start service: %s', ex.message) end end @@ -241,6 +251,7 @@ function processMsg(obj, src, evt) obj.LocalStatus = 'idle'; obj.sendUDP(obj.LastReceivedMessage) catch + obj.LocalStatus = 'exception'; error('Failed to stop service') end end @@ -261,7 +272,7 @@ function processMsg(obj, src, evt) obj.Status = 'unavailable'; end else - % Ignore if it is our on + % Ignore if it is our own if strcmp(response.host, hostname); return; end try obj.sendUDP(['WHAT' parsed.id obj.LocalStatus '*' obj.RemoteHost]) From dc3d43de84f440dc264ad09ae7c6d78386927ac4 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 15 Feb 2018 16:24:45 +0000 Subject: [PATCH 082/393] List subjects now added to Alyx --- +eui/AlyxPanel.m | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index a5bff35b..4a097d5a 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -217,20 +217,8 @@ function login(obj) set(findall(obj.RootContainer, '-property', 'Enable'), 'Enable', 'on'); set(obj.LoginText, 'String', ['You are logged in as ', obj.AlyxInstance.User]); % display which user is logged in set(obj.LoginButton, 'String', 'Logout'); - - % try updating the subject selectors in other panels - s = obj.AlyxInstance.getData('subjects?stock=False&alive=True'); - - respUser = cellfun(@(x)x.responsible_user, s, 'uni', false); - subjNames = cellfun(@(x)x.nickname, s, 'uni', false); - - thisUserSubs = sort(subjNames(strcmp(respUser, obj.AlyxInstance.User))); - otherUserSubs = sort(subjNames); - % note that we leave this User's mice also in - % otherUserSubs, in case they get confused and look - % there. - - newSubs = [{'default'}, thisUserSubs, otherUserSubs]; + + newSubs = obj.AlyxInstance.listSubjects; obj.NewExpSubject.Option = newSubs; obj.SubjectList = newSubs; From b266dd454f6f553a302d77b3c7b49bd3a321cbf8 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 15 Feb 2018 17:06:55 +0000 Subject: [PATCH 083/393] AlyxPanel enhancement now more accurately determines whether a mouse is on water restriction by comparing with data from water-restricted-subjects endpoint --- +eui/AlyxPanel.m | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index 4a097d5a..a391f2da 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -217,7 +217,8 @@ function login(obj) set(findall(obj.RootContainer, '-property', 'Enable'), 'Enable', 'on'); set(obj.LoginText, 'String', ['You are logged in as ', obj.AlyxInstance.User]); % display which user is logged in set(obj.LoginButton, 'String', 'Logout'); - + + % try updating the subject selectors in other panels newSubs = obj.AlyxInstance.listSubjects; obj.NewExpSubject.Option = newSubs; obj.SubjectList = newSubs; @@ -310,8 +311,9 @@ function dispWaterReq(obj, src, ~) % Refresh the timer as the user isn't inactive stop(obj.LoginTimer); start(obj.LoginTimer) try - s = ai.getData(ai.makeEndpoint(['subjects/' obj.Subject])); % struct with data about the subject - if s.water_requirement_total==0 % Subject not on water restriction + s = catStructs(ai.getData('water-restricted-subjects')); % struct with data about restricted subjects + idx = strcmp(obj.Subject, {s.nickname}); + if ~any(idx) % Subject not on water restriction set(obj.WaterRequiredText, 'ForegroundColor', 'black',... 'String', sprintf('Subject %s not on water restriction', obj.Subject)); else @@ -343,9 +345,9 @@ function dispWaterReq(obj, src, ~) % Set text set(obj.WaterRequiredText, 'ForegroundColor', colour, 'String', ... sprintf('Subject %s requires %.2f of %.2f today\n\t Weight today: %.2f (%s) Water today: %.2f', ... - obj.Subject, s.water_requirement_remaining, s.water_requirement_total, weight, weight_pct, sum([water gel]))); + obj.Subject, s(idx).water_requirement_remaining, s(idx).water_requirement_total, weight, weight_pct, sum([water gel]))); % Set WaterRemaining attribute for changeWaterText callback - obj.WaterRemaining = s.water_requirement_remaining; + obj.WaterRemaining = s(idx).water_requirement_remaining; end catch me d = loadjson(me.message); From 5926288265d9fa5de1ee29b4f3f70c9340a9d625 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 15 Feb 2018 17:08:47 +0000 Subject: [PATCH 084/393] doesn't try to post 'default' comments Won't post default's comments to Alyx --- +dat/updateLogEntry.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/+dat/updateLogEntry.m b/+dat/updateLogEntry.m index 7970edea..0882ba2a 100644 --- a/+dat/updateLogEntry.m +++ b/+dat/updateLogEntry.m @@ -12,7 +12,7 @@ function updateLogEntry(subject, id, newEntry) if isfield(newEntry, 'AlyxInstance') % Update session narrative on Alyx - if ~isempty(newEntry.comments) + if ~isempty(newEntry.comments) && ~strcmp(subject, 'default') newEntry.comments = newEntry.AlyxInstance.updateNarrative(newEntry.comments); end newEntry = rmfield(newEntry, 'AlyxInstance'); From fcf2a2b1326a632ab3437bfce2ae0501e67831f6 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Mon, 19 Feb 2018 14:24:27 +0000 Subject: [PATCH 085/393] Temp roll-back register with url until alyx dev merge --- +exp/SignalsExp.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/+exp/SignalsExp.m b/+exp/SignalsExp.m index 12309f75..6723095d 100644 --- a/+exp/SignalsExp.m +++ b/+exp/SignalsExp.m @@ -850,7 +850,9 @@ function saveData(obj) if strcmp(subject, 'default'); return; end % Register saved files obj.AlyxInstance.registerFile(savepaths{end}, 'mat',... - {subject, expDate, seq}, 'Block', []); + obj.AlyxInstance.SessionURL, 'Block', []); +% obj.AlyxInstance.registerFile(savepaths{end}, 'mat',... +% {subject, expDate, seq}, 'Block', []); % Save the session end time if ~isempty(obj.AlyxInstance.SessionURL) obj.AlyxInstance.putData(obj.AlyxInstance.SessionURL,... From b5fa0a02d2729a1e89dc2dfb694f4dc0675b7ff2 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 20 Feb 2018 17:45:29 +0000 Subject: [PATCH 086/393] Fix'd beginExp bug can no longer start experiment on rig already running one --- +eui/MControl.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/+eui/MControl.m b/+eui/MControl.m index 367f9428..f46c4e96 100644 --- a/+eui/MControl.m +++ b/+eui/MControl.m @@ -309,7 +309,9 @@ function loadParamProfile(obj, profile) if ~isempty(paramStruct) % Now parameters are loaded, pass to ParamEditor for display, etc. obj.ParamEditor = eui.ParamEditor(obj.Parameters, obj.ParamPanel); % Build parameter list in Global panel by calling eui.ParamEditor obj.ParamEditor.addlistener('Changed', @(src,~) obj.paramChanged); - set(obj.BeginExpButton, 'Enable', 'on') % Re-enable start button + if strcmp(obj.RemoteRigs.Selected.Status, 'idle') + set(obj.BeginExpButton, 'Enable', 'on') % Re-enable start button + end end end From 7ff31c20291cb283f2ed3d7f5197825b79f05946 Mon Sep 17 00:00:00 2001 From: jjlee42 <somewhatvaguely@gmail.com> Date: Wed, 21 Feb 2018 20:20:06 +0000 Subject: [PATCH 087/393] Errors clearer in MpepUDPDataHosts --- cortexlab/+io/MpepUDPDataHosts.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cortexlab/+io/MpepUDPDataHosts.m b/cortexlab/+io/MpepUDPDataHosts.m index 07bd3cea..c6b4f34f 100644 --- a/cortexlab/+io/MpepUDPDataHosts.m +++ b/cortexlab/+io/MpepUDPDataHosts.m @@ -273,8 +273,8 @@ function validateResponses(obj) dt = toc; match = find(strcmp(waiting, ip), 1); assert(~isempty(match),... - 'Received UDP packet after %.2fs from unexpected IP address ''%s'',\nmessage was ''%s''',... - dt, ip, msg); + 'Received UDP packet after %.2fs from unexpected IP address ''%s'',\nmessage was ''%s''\nAwaiting response from %s',... + dt, ip, msg, strjoin(obj.RemoteHosts, ', ')); waiting(match) = []; % remove matching IP from confirmation list ok(i) = isequal(expecting, msg); end From 8e98c779e306983aaace80d11dd22a20d3975f3a Mon Sep 17 00:00:00 2001 From: jjlee42 <somewhatvaguely@gmail.com> Date: Wed, 21 Feb 2018 20:26:09 +0000 Subject: [PATCH 088/393] Fix'd parseAlyxInstance --- cortexlab/+io/MpepUDPDataHosts.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cortexlab/+io/MpepUDPDataHosts.m b/cortexlab/+io/MpepUDPDataHosts.m index c6b4f34f..1f743832 100644 --- a/cortexlab/+io/MpepUDPDataHosts.m +++ b/cortexlab/+io/MpepUDPDataHosts.m @@ -205,7 +205,7 @@ function start(obj, expRef, ai) % Deal with Alyx instance first if ~isempty(ai) obj.AlyxInstance = ai; - UDP_msg = ai.parseAlyxInstance; + UDP_msg = Alyx.parseAlyxInstance(expRef, ai); [subject, seriesNum, expNum] = dat.expRefToMpep(expRef); alyxmsg = sprintf('alyx %s %d %d %s', subject, seriesNum, expNum, UDP_msg); confirmedBroadcast(obj, alyxmsg); From 93c69f0f4a98eee45f0832d5181ebf4eceb92874 Mon Sep 17 00:00:00 2001 From: Michael <mkrumin@gmail.com> Date: Wed, 21 Feb 2018 20:29:17 +0000 Subject: [PATCH 089/393] Updated bindMpepServer --- +hw/Timeline.m | 10 +++++----- cortexlab/+dat/subjectSelector.m | 4 ++-- cortexlab/+tl/bindMpepServer.m | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/+hw/Timeline.m b/+hw/Timeline.m index e9a6aac2..0613a3b5 100644 --- a/+hw/Timeline.m +++ b/+hw/Timeline.m @@ -126,9 +126,9 @@ end end - function start(obj, expRef, Alyx) + function start(obj, expRef, ai) % START Starts timeline data acquisition - % START(obj, ref, Alyx) starts all DAQ sessions and adds + % START(obj, ref, AlyxInstance) starts all DAQ sessions and adds % the relevent output and input channels. % % See Also HW.TLOUTPUT/START @@ -138,7 +138,7 @@ function start(obj, expRef, Alyx) obj.stop(); end obj.Ref = expRef; % set the current experiment ref - obj.AlyxInstance = Alyx; % set the current instance of Alyx + obj.AlyxInstance = ai; % set the current instance of Alyx init(obj); % start the relevent sessions and add channels obj.Listener = obj.Sessions('main').addlistener('DataAvailable', @obj.process); % add listener @@ -463,11 +463,11 @@ function stop(obj) end % register Timeline.mat file to Alyx database - [subject, expDate, seq] = dat.parseExpRef(obj.Data.expRef); + [subject, ~, ~] = dat.parseExpRef(obj.Data.expRef); if obj.AlyxInstance.IsLoggedIn && ~strcmp(subject,'default') try obj.AlyxInstance.registerFile(obj.Data.savePaths{end}, 'mat',... - {subject, expDate, seq}, 'Timeline', []); + obj.AlyxInstance.SessionURL, 'Timeline', []); catch ex warning(ex.identifier, 'couldn''t register files to Alyx: %s', ex.message); end diff --git a/cortexlab/+dat/subjectSelector.m b/cortexlab/+dat/subjectSelector.m index 23df89b9..17097fe7 100644 --- a/cortexlab/+dat/subjectSelector.m +++ b/cortexlab/+dat/subjectSelector.m @@ -6,7 +6,7 @@ % from alyx; otherwise, from dat.listSubjects % % example usage: -% >> alyxInstance = alyx.loginWindow(); +% >> alyxInstance = Alyx.login; % >> [subj, expNum] = subjectSelector([], alyxInstance); % % Created by NS 2017 @@ -44,7 +44,7 @@ if nargin>1 ai = varargin{2}; - set(subjectDropdown, 'String', dat.listSubjects(ai)); + set(subjectDropdown, 'String', ai.listSubjects); else set(subjectDropdown, 'String', dat.listSubjects); end diff --git a/cortexlab/+tl/bindMpepServer.m b/cortexlab/+tl/bindMpepServer.m index 533e375b..9e15219e 100644 --- a/cortexlab/+tl/bindMpepServer.m +++ b/cortexlab/+tl/bindMpepServer.m @@ -81,7 +81,7 @@ function processMpep(listener, msg) case 'alyx' fprintf(1, 'received alyx token message\n'); idx = find(msg==' ', 1, 'last'); - [~, ai] = dat.parseAlyxInstance(msg(idx+1:end)); + [~, ai] = Alyx.parseAlyxInstance(msg(idx+1:end)); tls.AlyxInstance = ai; case 'expstart' % create a file path & experiment ref based on experiment info @@ -132,7 +132,7 @@ function listen() if isempty(tls.AlyxInstance) % first get an alyx instance - ai = alyx.loginWindow(); + ai = Alyx; else ai = tls.AlyxInstance; end @@ -142,8 +142,8 @@ function listen() if ~isempty(mouseName) clear expParams; expParams.experimentType = 'timelineManualStart'; - [newExpRef, ~, subsessionURL] = dat.newExp(mouseName, now, expParams, ai); - ai.subsessionURL = subsessionURL; + [newExpRef, ~, subsessionURL] = ai.newExp(mouseName, now, expParams); + ai.SessionURL = subsessionURL; tls.AlyxInstance = ai; tlObj.start(newExpRef, ai); end From 4999899343876473bcf6d50947838ce21e37e7ba Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 22 Feb 2018 21:25:34 +0000 Subject: [PATCH 090/393] Fixed Web page launch buttons in AlyxPanel --- +eui/AlyxPanel.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index a391f2da..5bfdeec6 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -462,7 +462,7 @@ function launchSessionURL(obj) uuid = u(find(u=='/', 1, 'last')+1:end); % make the admin url - adminURL = fullfile(ai.baseURL, 'admin', 'actions', 'session', uuid, 'change'); + adminURL = fullfile(ai.BaseURL, 'admin', 'actions', 'session', uuid, 'change'); % launch the website web(adminURL, '-browser'); @@ -470,7 +470,7 @@ function launchSessionURL(obj) function launchSubjectURL(obj) ai = obj.AlyxInstance; - if ~ai.IsLoggedIn + if ai.IsLoggedIn s = ai.getData(ai.makeEndpoint(['subjects/' obj.Subject])); subjURL = fullfile(ai.BaseURL, 'admin', 'subjects', 'subject', s.id, 'change'); % this is wrong - need uuid web(subjURL, '-browser'); From 185a3617083077726502f6ac019dce80551f88b0 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 28 Feb 2018 11:11:47 +0000 Subject: [PATCH 091/393] Optimized sqeakExpPanel & reverted newExp * Removed for loop in SqueakExpPanel * Improved plotting of discrimination task * dat.newExp no longer interacts with Alyx. For this, use Alyx.newExp. --- +dat/newExp.m | 113 +++++------------------------- +eui/SqueakExpPanel.m | 6 +- cortexlab/+psy/plot2AUFC.m | 140 ++++++++++++++++++++++--------------- 3 files changed, 100 insertions(+), 159 deletions(-) diff --git a/+dat/newExp.m b/+dat/newExp.m index 6a86e07f..7618d371 100644 --- a/+dat/newExp.m +++ b/+dat/newExp.m @@ -1,4 +1,4 @@ -function [expRef, expSeq, url] = newExp(subject, expDate, expParams, AlyxInstance) +function [expRef, expSeq] = newExp(subject, expDate, expParams) %DAT.NEWEXP Create a new unique experiment in the database % [ref, seq, url] = DAT.NEWEXP(subject, expDate, expParams[, AlyxInstance]) % Create a new experiment by creating the relevant folder tree in the @@ -9,10 +9,7 @@ % |_ expSeq/ % % If experiment parameters are passed into the function, they are saved -% here, as a mat and in JSON (if possible). If an instance of Alyx is -% passed and a base session for the experiment date is not found, one is -% created in the Alyx database. A corresponding subsession is also -% created and the parameters file is registered with the sub-session. +% here. % % See also DAT.PATHS % @@ -30,11 +27,6 @@ expParams = []; end -if nargin < 4 - % no instance of Alyx, don't create session on Alyx - AlyxInstance = []; -end - if ischar(expDate) % if the passed expDate is a string, parse it into a datenum expDate = datenum(expDate, 'yyyy-mm-dd'); @@ -66,91 +58,18 @@ % now make the folder(s) to hold the new experiment assert(all(cellfun(@(p) mkdir(p), expPath)), 'Creating experiment directories failed'); -if ~strcmp(subject, 'default') % Ignore fake subject - % if the Alyx Instance is set, find or create BASE session - expDate = alyx.datestr(expDate); % date in Alyx format - if ~isempty(AlyxInstance) - % Get list of base sessions - sessions = alyx.getData(AlyxInstance,... - ['sessions?type=Base&subject=' subject]); - - %If the date of this latest base session is not the same date as - %today, then create a new base session for today - if isempty(sessions) || ~strcmp(sessions{end}.start_time(1:10), expDate(1:10)) - d = struct; - d.subject = subject; - d.procedures = {'Behavior training/tasks'}; - d.narrative = 'auto-generated session'; - d.start_time = expDate; - d.type = 'Base'; - % d.users = {AlyxInstance.username}; - - base_submit = alyx.postData(AlyxInstance, 'sessions', d); - assert(isfield(base_submit,'subject'),... - 'Submitted base session did not return appropriate values'); - - %Now retrieve the sessions again - sessions = alyx.getData(AlyxInstance,... - ['sessions?type=Base&subject=' subject]); - end - latest_base = sessions{end}; - - %Now create a new SUBSESSION, using the same experiment number - d = struct; - d.subject = subject; - d.procedures = {'Behavior training/tasks'}; - d.narrative = 'auto-generated session'; - d.start_time = expDate; - d.type = 'Experiment'; - d.parent_session = latest_base.url; - d.number = expSeq; - % d.users = {AlyxInstance.username}; - - try - subsession = alyx.postData(AlyxInstance, 'sessions', d); - url = subsession.url; - catch - url = []; - end - else % If not logged in to Alyx... - url = []; % set the base url to null - end - - % if the parameters had an experiment definition function, save a copy in - % the experiment's folder - if isfield(expParams, 'defFunction') - assert(file.exists(expParams.defFunction),... - 'Experiment definition function does not exist: %s', expParams.defFunction); - assert(all(cellfun(@(p)copyfile(expParams.defFunction, p),... - dat.expFilePath(expRef, 'expDefFun'))),... - 'Copying definition function to experiment folders failed'); - end - - % now save the experiment parameters variable - superSave(dat.expFilePath(expRef, 'parameters'), struct('parameters', expParams)); - - try % save a copy of parameters in json - % First, change all functions to strings - f_idx = structfun(@(s)isa(s, 'function_handle'), expParams); - fields = fieldnames(expParams); - paramCell = struct2cell(expParams); - paramCell(f_idx) = cellfun(@func2str, paramCell(f_idx),'UniformOutput', false); - expParams = cell2struct(paramCell, fields); - % Generate JSON path and save - jsonPath = fullfile(fileparts(dat.expFilePath(expRef, 'parameters', 'master')),... - [expRef, '_parameters.json']); - savejson('parameters', expParams, jsonPath); - % Register our JSON parameter set to Alyx - if ~strcmp(subject,'default') - alyx.registerFile(jsonPath, 'json', url, 'Parameters', [], AlyxInstance); - end - catch ex - warning(ex.identifier, 'Failed to save paramters as JSON: %s.\n Registering mat file instead', ex.message) - % Register our parameter set to Alyx - if ~strcmp(subject,'default') - alyx.registerFile(dat.expFilePath(expRef, 'parameters', 'master'), 'mat',... - url, 'Parameters', [], AlyxInstance); - end - end - + +% if the parameters had an experiment definition function, save a copy in +% the experiment's folder +if isfield(expParams, 'defFunction') + assert(file.exists(expParams.defFunction),... + 'Experiment definition function does not exist: %s', expParams.defFunction); + assert(all(cellfun(@(p)copyfile(expParams.defFunction, p),... + dat.expFilePath(expRef, 'expDefFun'))),... + 'Copying definition function to experiment folders failed'); +end + +% now save the experiment parameters variable +superSave(dat.expFilePath(expRef, 'parameters'), struct('parameters', expParams)); + end \ No newline at end of file diff --git a/+eui/SqueakExpPanel.m b/+eui/SqueakExpPanel.m index 865a0c0d..39320746 100644 --- a/+eui/SqueakExpPanel.m +++ b/+eui/SqueakExpPanel.m @@ -23,12 +23,8 @@ function update(obj) update@eui.ExpPanel(obj); processUpdates(obj); % update labels with latest signal values -% labels = cell2mat(values(obj.LabelsMap))'; labelsMapVals = values(obj.LabelsMap)'; - labels = gobjects(size(values(obj.LabelsMap))); - for i=1:length(labelsMapVals) % using for loop (sorry Chris!) to populate object array 2017-02-14 MW - labels(i) = labelsMapVals{i}; - end + labels = deal([labelsMapVals{:}]); if ~isempty(labels) % colour decay by recency on labels dt = cellfun(@(t)etime(clock,t),... ensureCell(get(labels, 'UserData'))); diff --git a/cortexlab/+psy/plot2AUFC.m b/cortexlab/+psy/plot2AUFC.m index 0c260f24..66037cb6 100644 --- a/cortexlab/+psy/plot2AUFC.m +++ b/cortexlab/+psy/plot2AUFC.m @@ -1,73 +1,99 @@ function plot2AUFC(ax, block) - -% numCompletedTrials = block.numCompletedTrials; - -[block.trial(arrayfun(@(a)isempty(a.contrast), block.trial)).contrast] = deal(nan); +[block.trial(arrayfun(@(a)isempty(a.contrastLeft), block.trial)).contrastLeft] = deal(nan); +[block.trial(arrayfun(@(a)isempty(a.contrastRight), block.trial)).contrastRight] = deal(nan); [block.trial(arrayfun(@(a)isempty(a.response), block.trial)).response] = deal(nan); [block.trial(arrayfun(@(a)isempty(a.repeatNum), block.trial)).repeatNum] = deal(nan); [block.trial(arrayfun(@(a)isempty(a.feedback), block.trial)).feedback] = deal(nan); -contrast = [block.trial.contrast]; +contrast = []; +contrast(1,:) = [block.trial.contrastLeft]; +contrast(2,:) = [block.trial.contrastRight]; +% contrast = diff(contrast); response = [block.trial.response]; repeatNum = [block.trial.repeatNum]; -% feedback = [block.trial.feedback]; -if any(structfun(@isempty, block.trial(end))) % strip incomplete trials - contrast = contrast(1:end-1); - response = response(1:end-1); - repeatNum = repeatNum(1:end-1); +if any(structfun(@isnan, block.trial(end))) % strip incomplete trials + contrast = contrast(:,1:end-1); + response = response(1:end-1); + repeatNum = repeatNum(1:end-1); end respTypes = unique(response); numRespTypes = numel(respTypes); -cVals = unique(contrast); +if size(contrast, 1) > 1 + allContrast = contrast; + contrast = diff(contrast, [], 1); +else + allContrast = [0;0]; +end -psychoM = zeros(numRespTypes,length(cVals)); -psychoMCI = zeros(numRespTypes,length(cVals)); -numTrials = zeros(1,length(cVals)); -numChooseR = zeros(numRespTypes, length(cVals)); -for r = 1:numRespTypes - for c = 1:length(cVals) - incl = repeatNum==1&contrast==cVals(c); - numTrials(c) = sum(incl); - numChooseR(r,c) = sum(response==respTypes(r)&incl); - - psychoM(r, c) = numChooseR(r,c)/numTrials(c); - psychoMCI(r, c) = 1.96*sqrt(psychoM(r, c)*(1-psychoM(r, c))/numTrials(c)); +if any(allContrast(1,:)>0 & allContrast(2,:)>0) + + % mode for plotting task with two stimuli at once + cValsLeft = unique(allContrast(1,:)); + cValsRight = unique(allContrast(2,:)); + nCL = numel(cValsLeft); + nCR = numel(cValsRight); + % pedVals = cVals(1:end-1); + % numPeds = numel(pedVals); + + respTypes = unique(response); + numRespTypes = numel(respTypes); + numTrials = nan(1, nCL, nCR); + numChooseR = nan(numRespTypes, nCL, nCR); + psychoM = nan(numRespTypes, nCL, nCR); + for r = 1:numRespTypes + for c1 = 1:nCL + for c2 = 1:nCR + incl = repeatNum==1&allContrast(1,:)==cValsLeft(c1)&allContrast(2,:) == cValsRight(c2); + numTrials(1,c1,c2) = sum(incl); + numChooseR(r,c1,c2) = sum(response==respTypes(r)&incl); + psychoM(r,c1,c2) = numChooseR(r,c1,c2)/numTrials(1,c1,c2); + %psychoMCI(r, c,las) = 1.96*sqrt(psychoM(r, c,las)*(1-psychoM(r, c,las))/numTrials(1,c,las)); + end end -end - -colors = [0 1 1 - 1 0 0 - 0 1 0];%hsv(numRespTypes); -% hsv(3) - -for r = 1:numRespTypes - - xdata = 100*cVals; - ydata = 100*psychoM(r,:); -% errBars = 100*psychoMCI(r,:); - - plot(ax, xdata, ydata, '-o', 'Color', colors(r,:), 'LineWidth', 1.0); - - % set all NaN values to 0 so the fill function can proceed just - % skipping over those points. -% ydata(isnan(ydata)) = 0; -% errBars(isnan(errBars)) = 0; - - %TODO:update to use plt.hshade -% fillhandle = fill([xdata xdata(end:-1:1)],... -% [ydata+errBars ydata(end:-1:1)-errBars(end:-1:1)], colors(r,:),... -% 'Parent', ax); -% set(fillhandle, 'FaceAlpha', 0.15, 'EdgeAlpha', 0); - %,... + end + cla(ax) + psychoMCmap = reshape(permute(psychoM, [2 1 3]), numRespTypes*nCR, nCL)'; + psychoMCmap(isnan(psychoMCmap))=-1; + imagesc(ax, psychoMCmap) + colormap(colormap_pinkgreyscale) + + set(ax, 'XTick', 1:nCR*numRespTypes, 'XTickLabel', cValsRight(repmat(1:nCR, [1 numRespTypes]))); + set(ax, 'YTick', 1:nCL, 'YTickLabel', cValsLeft(1:nCL)); + + for r = 1:numRespTypes-1 + plot(ax, nCR*r+[0.5 0.5], [0.5 nCL+0.5], 'Color', [0.8 0.8 0.8], 'LineWidth', 2.0); + end + + xlim(ax, [0.5 nCR*numRespTypes+0.5]) + ylim(ax, [0.5 nCL+0.5]) + caxis(ax, [-1 1]); + axis(ax, 'image') +else + + cVals = unique(contrast); + colors = iff(numRespTypes>2,[0 1 1; 0 1 0; 1 0 0], [0 1 1; 1 0 0]); + psychoM = zeros(numRespTypes,length(cVals)); + psychoMCI = zeros(numRespTypes,length(cVals)); + numTrials = zeros(1,length(cVals)); + numChooseR = zeros(numRespTypes, length(cVals)); + for r = 1:numRespTypes + for c = 1:length(cVals) + incl = repeatNum==1&contrast==cVals(c); + numTrials(c) = sum(incl); + numChooseR(r,c) = sum(response==respTypes(r)&incl); - -% hold on; - - + psychoM(r, c) = numChooseR(r,c)/numTrials(c); + psychoMCI(r, c) = 1.96*sqrt(psychoM(r, c)*(1-psychoM(r, c))/numTrials(c)); + end + errorbar(ax, 100*cVals, 100*psychoM(r,:), 100*psychoMCI(r,:),... + '-o', 'Color', colors(r,:), 'LineWidth', 1.0); + end + + ylim(ax, [-1 101]); + xdata = cVals(~isnan(cVals))*100; + if numel(xdata) > 1 + xlim(ax, xdata([1 end])*1.1); + end end -ylim(ax, [-1 101]); -xdata = xdata(~isnan(xdata)); -if numel(xdata) > 1 - xlim(ax, xdata([1 end])*1.1); end From 3963d2298e7bcaae70e3fc3887495f7f1b350596 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 1 Mar 2018 17:23:20 +0000 Subject: [PATCH 092/393] Fix'd empty AlyxInstance bug Alyx instance now less likely to cause error when unset --- +eui/AlyxPanel.m | 2 +- +hw/Timeline.m | 2 +- cortexlab/+tl/bindMpepServer.m | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index 5bfdeec6..368aae21 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -32,7 +32,7 @@ % 2017-03 NS created % 2017-10 MW made into class properties (SetAccess = private) - AlyxInstance = Alyx; % An Alyx object to interfacing with the database + AlyxInstance = Alyx('',''); % An Alyx object to interfacing with the database SubjectList % List of active subjects from database Subject = 'default' % The name of the currently selected subject end diff --git a/+hw/Timeline.m b/+hw/Timeline.m index 0613a3b5..4850b519 100644 --- a/+hw/Timeline.m +++ b/+hw/Timeline.m @@ -464,7 +464,7 @@ function stop(obj) % register Timeline.mat file to Alyx database [subject, ~, ~] = dat.parseExpRef(obj.Data.expRef); - if obj.AlyxInstance.IsLoggedIn && ~strcmp(subject,'default') + if ~isempty(obj.AlyxInstance) && obj.AlyxInstance.IsLoggedIn && ~strcmp(subject,'default') try obj.AlyxInstance.registerFile(obj.Data.savePaths{end}, 'mat',... obj.AlyxInstance.SessionURL, 'Timeline', []); diff --git a/cortexlab/+tl/bindMpepServer.m b/cortexlab/+tl/bindMpepServer.m index 9e15219e..9b4a5024 100644 --- a/cortexlab/+tl/bindMpepServer.m +++ b/cortexlab/+tl/bindMpepServer.m @@ -36,7 +36,7 @@ tls.close = @closeConns; tls.process = @process; tls.listen = @listen; -tls.AlyxInstance = []; +tls.AlyxInstance = Alyx('',''); %% Initialize timeline From 1d870f72ed178f464f7a20a0e5cbbe51b43f1ee3 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 15 Mar 2018 17:26:29 +0000 Subject: [PATCH 093/393] Behavioural data saved and registered as npy files If expDef contains 'choiceworld', key data are extracted from the block file and saved as npy files, before registering to Alyx. --- +exp/SignalsExp.m | 133 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 108 insertions(+), 25 deletions(-) diff --git a/+exp/SignalsExp.m b/+exp/SignalsExp.m index 6723095d..c22e1e10 100644 --- a/+exp/SignalsExp.m +++ b/+exp/SignalsExp.m @@ -838,32 +838,115 @@ function activateEventHandler(obj, handler, eventInfo, dueTime) end function saveData(obj) - % save the data to the appropriate locations indicated by expRef - savepaths = dat.expFilePath(obj.Data.expRef, 'block'); - superSave(savepaths, struct('block', obj.Data)); - - if ~obj.AlyxInstance.IsLoggedIn - warning('No Alyx token set'); - else - try - [subject, expDate, seq] = dat.parseExpRef(obj.Data.expRef); - if strcmp(subject, 'default'); return; end - % Register saved files - obj.AlyxInstance.registerFile(savepaths{end}, 'mat',... - obj.AlyxInstance.SessionURL, 'Block', []); -% obj.AlyxInstance.registerFile(savepaths{end}, 'mat',... -% {subject, expDate, seq}, 'Block', []); - % Save the session end time - if ~isempty(obj.AlyxInstance.SessionURL) - obj.AlyxInstance.putData(obj.AlyxInstance.SessionURL,... - struct('end_time', obj.AlyxInstance.datestr(now), 'subject', subject)); - else - % Infer from date session and retrieve using expFilePath - end - catch ex - warning(ex.identifier, 'Failed to register files to Alyx: %s', ex.message); - end + % save the data to the appropriate locations indicated by expRef + savepaths = dat.expFilePath(obj.Data.expRef, 'block'); + superSave(savepaths, struct('block', obj.Data)); + [subject, ~, ~] = dat.parseExpRef(obj.Data.expRef); + + % if this is a 'ChoiceWorld' experiment, let's save out for + % relevant data for basic behavioural analysis and register them to + % Alyx + if contains(lower(obj.Data.expDef), 'choiceworld') ... + && ~strcmp(subject, 'default') && isfield(obj.Data, 'events') ... + && ~strcmp(obj.Data.endStatus,'aborted') + try + expPath = dat.expPath(obj.Data.expRef, 'main', 'master'); + % Write feedback + + feedback = getOr(obj.Data.events, 'feedbackValues', NaN); + feedback = double(feedback); + feedback(feedback == 0) = -1; + if ~isnan(feedback) + writeNPY(feedback(:), fullfile(expPath, 'cwFeedback.type.npy')); + alf.writeEventseries(expPath, 'cwFeedback',... + obj.Data.events.feedbackTimes, [], []); + else + warning('No ''feedback'' events recorded, cannot register to Alyx') + end + + % Write go cue + interactiveOn = getOr(obj.Data.events, 'interactiveOnTimes', NaN); + if ~isnan(interactiveOn) + alf.writeEventseries(expPath, 'cwGoCue', interactiveOn, [], []); + else + warning('No ''interactiveOn'' events recorded, cannot register to Alyx') + end + + % Write response + response = getOr(obj.Data.events, 'responseValues', NaN); + if min(response) == -1 + response(response == 0) = 3; + response(response == 1) = 2; + response(response == -1) = 1; + end + if ~isnan(response) + writeNPY(response(:), fullfile(expPath, 'cwResponse.choice.npy')); + alf.writeEventseries(expPath, 'cwResponse',... + obj.Data.events.responseTimes, [], []); + else + warning('No ''feedback'' events recorded, cannot register to Alyx') + end + + % Write stim on times + stimOnTimes = getOr(obj.Data.events, 'stimulusOnTimes', NaN); + if ~isnan(stimOnTimes) + alf.writeEventseries(expPath, 'cwStimOn', stimOnTimes, [], []); + else + warning('No ''stimulusOn'' events recorded, cannot register to Alyx') + end + contL = getOr(obj.Data.events, 'contrastLeftValues', NaN); + contR = getOr(obj.Data.events, 'contrastRightValues', NaN); + if ~isnan(contL)&&~isnan(contR) + writeNPY(contL(:), fullfile(expPath, 'cwStimOn.contrastLeft.npy')); + writeNPY(contR(:), fullfile(expPath, 'cwStimOn.contrastRight.npy')); + else + warning('No ''contrastLeft'' and/or ''contrastRight'' events recorded, cannot register to Alyx') + end + + % Write trial intervals + alf.writeInterval(expPath, 'cwTrials',... + obj.Data.events.newTrialTimes(:), obj.Data.events.endTrialTimes(:), [], []); + repNum = obj.Data.events.repeatNumValues(:); + writeNPY(repNum == 1, fullfile(expPath, 'cwTrials.inclTrials.npy')); + writeNPY(repNum, fullfile(expPath, 'cwTrials.repNum.npy')); + + % Write wheel times, position and velocity + wheelValues = obj.Data.inputs.wheelValues(:); + wheelValues = wheelValues*(3.1*2*pi/(4*1024)); + wheelTimes = obj.Data.inputs.wheelTimes(:); + alf.writeTimeseries(expPath, 'Wheel', wheelTimes, [], []); + writeNPY(wheelValues, fullfile(expPath, 'Wheel.position.npy')); + writeNPY(wheelValues./wheelTimes, fullfile(expPath, 'Wheel.velocity.npy')); + + % Register them to Alyx + obj.AlyxInstance.registerALFtoAlyx(expPath); + catch ex + warning(ex.identifier, 'Failed to register alf files: %s.', ex.message); + end + end + + if isempty(obj.AlyxInstance) + warning('No Alyx token set'); + else + try + [subject, expDate, seq] = dat.parseExpRef(obj.Data.expRef); + if strcmp(subject, 'default'); return; end + % Register saved files + obj.AlyxInstance.registerFile(savepaths{end}, 'mat',... + obj.AlyxInstance.SessionURL, 'Block', []); +% obj.AlyxInstance.registerFile(savepaths{end}, 'mat',... +% {subject, expDate, seq}, 'Block', []); + % Save the session end time + if ~isempty(obj.AlyxInstance.SessionURL) + obj.AlyxInstance.putData(obj.AlyxInstance.SessionURL,... + struct('end_time', obj.AlyxInstance.datestr(now), 'subject', subject)); + else + % Infer from date session and retrieve using expFilePath + end + catch ex + warning(ex.identifier, 'Failed to register files to Alyx: %s', ex.message); end + end end end From 5520c4cb9b2e00d617321d54599e5fd9ad49562d Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 15 Mar 2018 17:43:03 +0000 Subject: [PATCH 094/393] Reverted to session url registration Registering files with expRef not yet implemented --- +exp/Experiment.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/+exp/Experiment.m b/+exp/Experiment.m index a4c6bf33..acdd7f9f 100644 --- a/+exp/Experiment.m +++ b/+exp/Experiment.m @@ -781,7 +781,9 @@ function saveData(obj) if strcmp(subject, 'default'); return; end % Register saved files obj.AlyxInstance.registerFile(savepaths{end}, 'mat',... - {subject, expDate, seq}, 'Block', []); + obj.AlyxInstance.SessionURL, 'Block', []); +% obj.AlyxInstance.registerFile(savepaths{end}, 'mat',... +% {subject, expDate, seq}, 'Block', []); % Save the session end time if ~isempty(obj.AlyxInstance.SessionURL) obj.AlyxInstance.putData(obj.AlyxInstance.SessionURL,... From 47042707d0f395fd46287fb2530093a6c4f1e645 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 16 Mar 2018 11:30:03 +0000 Subject: [PATCH 095/393] Added lick detector input to signals --- +exp/SignalsExp.m | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/+exp/SignalsExp.m b/+exp/SignalsExp.m index c22e1e10..5d9b00d7 100644 --- a/+exp/SignalsExp.m +++ b/+exp/SignalsExp.m @@ -156,6 +156,7 @@ obj.Events.newTrial = net.origin('newTrial'); obj.Events.expStop = net.origin('expStop'); obj.Inputs.wheel = net.origin('wheel'); + obj.Inputs.lick = net.origin('lick'); obj.Inputs.keyboard = net.origin('keyboard'); % get global parameters & conditional parameters structs [~, globalStruct, allCondStruct] = toConditionServer(... @@ -674,6 +675,14 @@ function mainLoop(obj) % tic wx = readAbsolutePosition(obj.Wheel); post(obj.Inputs.wheel, wx); + if ~isempty(obj.LickDetector) + % read and log the current lick count + [nlicks, ~, licked] = readPosition(obj.LickDetector); + if licked + post(obj.Inputs.lick, nlicks); + fprintf('lick count now %i\n', nlicks); + end + end post(obj.Time, now(obj.Clock)); runSchedule(obj.Net); From 867d98ff5218cb876b6652d9752b4668b91daafb Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 16 Mar 2018 11:55:33 +0000 Subject: [PATCH 096/393] Parameters now charector type friendly Also default numTrials is 1000 total trials --- +exp/inferParameters.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/+exp/inferParameters.m b/+exp/inferParameters.m index bc879029..145f5978 100644 --- a/+exp/inferParameters.m +++ b/+exp/inferParameters.m @@ -42,7 +42,9 @@ end sz = iff(isempty(fieldnames(parsStruct)), 1,... % if there are no paramters sz = 1 structfun(@(a)size(a,2), parsStruct)); % otherwise get number of columns - parsStruct.numRepeats = ones(1,max(sz)); % add 'numRepeats' parameter + isChar = structfun(@ischar, parsStruct); % we disregard charecter arrays + % add 'numRepeats' parameter, where total number of trials = 1000 + parsStruct.numRepeats = ones(1,max(sz(~isChar)))*floor(1000/max(sz(~isChar))); parsStruct.defFunction = expdef; parsStruct.type = 'custom'; % Define the ExpPanel to use (automatically by name convention for now) From 6b4b46bda74122aa12ed9907674e89856f0982e2 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 16 Mar 2018 12:42:00 +0000 Subject: [PATCH 097/393] Fix'd a couple of indexing bugs --- +exp/SignalsExp.m | 4 ++-- +exp/inferParameters.m | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/+exp/SignalsExp.m b/+exp/SignalsExp.m index 5d9b00d7..12dc3732 100644 --- a/+exp/SignalsExp.m +++ b/+exp/SignalsExp.m @@ -905,7 +905,7 @@ function saveData(obj) end contL = getOr(obj.Data.events, 'contrastLeftValues', NaN); contR = getOr(obj.Data.events, 'contrastRightValues', NaN); - if ~isnan(contL)&&~isnan(contR) + if ~any(isnan(contL))&&~any(isnan(contR)) writeNPY(contL(:), fullfile(expPath, 'cwStimOn.contrastLeft.npy')); writeNPY(contR(:), fullfile(expPath, 'cwStimOn.contrastRight.npy')); else @@ -928,7 +928,7 @@ function saveData(obj) writeNPY(wheelValues./wheelTimes, fullfile(expPath, 'Wheel.velocity.npy')); % Register them to Alyx - obj.AlyxInstance.registerALFtoAlyx(expPath); + obj.AlyxInstance.registerALF(expPath); catch ex warning(ex.identifier, 'Failed to register alf files: %s.', ex.message); end diff --git a/+exp/inferParameters.m b/+exp/inferParameters.m index 145f5978..51c57a01 100644 --- a/+exp/inferParameters.m +++ b/+exp/inferParameters.m @@ -43,8 +43,9 @@ sz = iff(isempty(fieldnames(parsStruct)), 1,... % if there are no paramters sz = 1 structfun(@(a)size(a,2), parsStruct)); % otherwise get number of columns isChar = structfun(@ischar, parsStruct); % we disregard charecter arrays + if any(isChar); sz = sz(~isChar); end % add 'numRepeats' parameter, where total number of trials = 1000 - parsStruct.numRepeats = ones(1,max(sz(~isChar)))*floor(1000/max(sz(~isChar))); + parsStruct.numRepeats = ones(1,max(sz))*floor(1000/max(sz)); parsStruct.defFunction = expdef; parsStruct.type = 'custom'; % Define the ExpPanel to use (automatically by name convention for now) From 8a51bd3cdb892d8015a2edab7f5b7b8562fcfa32 Mon Sep 17 00:00:00 2001 From: nsteinme <nick.steinmetz@gmail.com> Date: Mon, 19 Mar 2018 17:02:00 +0000 Subject: [PATCH 098/393] update bindMpepServerWithWS to modern --- cortexlab/+tl/bindMpepServerWithWS.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cortexlab/+tl/bindMpepServerWithWS.m b/cortexlab/+tl/bindMpepServerWithWS.m index 0adbafca..32ef7ade 100644 --- a/cortexlab/+tl/bindMpepServerWithWS.m +++ b/cortexlab/+tl/bindMpepServerWithWS.m @@ -156,7 +156,7 @@ function listen() if isempty(tls.AlyxInstance) % first get an alyx instance - ai = alyx.loginWindow(); + ai = Alyx; else ai = tls.AlyxInstance; end @@ -166,8 +166,8 @@ function listen() if ~isempty(mouseName) clear expParams; expParams.experimentType = 'timelineManualStart'; - [newExpRef, newExpSeq, subsessionURL] = dat.newExp(mouseName, now, expParams, ai); - ai.subsessionURL = subsessionURL; + [newExpRef, ~, subsessionURL] = ai.newExp(mouseName, now, expParams); + ai.SessionURL = subsessionURL; tls.AlyxInstance = ai; %[subjectRef, expDate, expSequence] = dat.parseExpRef(newExpRef); From 2e92e454ab507b7803aeadf60ada83b24b78322b Mon Sep 17 00:00:00 2001 From: nsteinme <nick.steinmetz@gmail.com> Date: Mon, 19 Mar 2018 18:12:07 +0000 Subject: [PATCH 099/393] add offset option to sinepulsegen --- +hw/SinePulseGenerator.m | 2 ++ 1 file changed, 2 insertions(+) diff --git a/+hw/SinePulseGenerator.m b/+hw/SinePulseGenerator.m index 95576183..3dc9f174 100644 --- a/+hw/SinePulseGenerator.m +++ b/+hw/SinePulseGenerator.m @@ -38,6 +38,8 @@ % add a zero so it turns off at the end samples = [samples'; 0]; + + samples = samples+obj.Offset; end end From fbaa46231b696579c21d5efb4956a503fc2c0fba Mon Sep 17 00:00:00 2001 From: nsteinme <nick.steinmetz@gmail.com> Date: Mon, 19 Mar 2018 18:12:32 +0000 Subject: [PATCH 100/393] add set-able samplerate for daq controller --- +hw/DaqController.m | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/+hw/DaqController.m b/+hw/DaqController.m index 4a480fcf..a9835d40 100644 --- a/+hw/DaqController.m +++ b/+hw/DaqController.m @@ -45,6 +45,9 @@ SignalGenerators = hw.PulseSwitcher.empty DaqIds = 'Dev1' % device ID's for each channel, e.g. 'Dev1' DaqChannelIds = {} % DAQ's ID for each channel, e.g. 'ao0' + SampleRate = 1000 % output sample rate ("scans/sec") of the daq device + % 1000 is also the default of the ni daq devices themselves, so if + % you don't change this, it doesn't actually do anything. end properties (Transient) @@ -66,6 +69,7 @@ function createDaqChannels(obj) if isempty(obj.DaqSession) obj.DaqSession = daq.createSession('ni'); + obj.DaqSession.Rate = obj.SampleRate; end if isempty(obj.DigitalDaqSession)&&any(~obj.AnalogueChannelsIdx) obj.DigitalDaqSession = daq.createSession('ni'); From e598fc6084cf47089e6f5a7ec6cac1bc304ac658 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 20 Mar 2018 18:12:43 +0000 Subject: [PATCH 101/393] StimulusControl has default Alyx object --- +srv/StimulusControl.m | 2 +- cortexlab/+psy/plot2AUFC.m | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/+srv/StimulusControl.m b/+srv/StimulusControl.m index 34db88fe..9e6f8114 100644 --- a/+srv/StimulusControl.m +++ b/+srv/StimulusControl.m @@ -43,7 +43,7 @@ end properties (Transient, Hidden) - AlyxInstance = [] % Property to store rig specific Alyx token + AlyxInstance = Alyx('','') % Property to store rig specific Alyx token end properties (Constant) diff --git a/cortexlab/+psy/plot2AUFC.m b/cortexlab/+psy/plot2AUFC.m index 66037cb6..61744ebf 100644 --- a/cortexlab/+psy/plot2AUFC.m +++ b/cortexlab/+psy/plot2AUFC.m @@ -10,11 +10,15 @@ function plot2AUFC(ax, block) % contrast = diff(contrast); response = [block.trial.response]; repeatNum = [block.trial.repeatNum]; -if any(structfun(@isnan, block.trial(end))) % strip incomplete trials - contrast = contrast(:,1:end-1); - response = response(1:end-1); - repeatNum = repeatNum(1:end-1); -end +incl = ~any(isnan([contrast;response;repeatNum])); +contrast = contrast(incl); +response = response(incl); +repeatNum = repeatNum(incl); +% if any(structfun(@isnan, block.trial(end))) % strip incomplete trials +% contrast = contrast(:,1:end-1); +% response = response(1:end-1); +% repeatNum = repeatNum(1:end-1); +% end respTypes = unique(response); numRespTypes = numel(respTypes); From 283ef018a1543dee97ae584787f78d2016632517 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 22 Mar 2018 12:30:37 +0000 Subject: [PATCH 102/393] AlyxPanel now has active flag when inactive, Alyx instance is set to Headless mode and the login button is disabled. --- +eui/AlyxPanel.m | 24 ++++++++++++++++++------ +eui/MControl.m | 4 ---- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index 368aae21..4945e0ca 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -57,12 +57,15 @@ end methods - function obj = AlyxPanel(parent) - % Constructor to build all the UI elements and set callbacks to - % the relevant functions. If a handle to parant UI object is - % not specified, a seperate figure is created. An optional - % handle to a logging display panal may be provided, otherwise - % one is created. + function obj = AlyxPanel(parent, active) + % Constructor to build all the UI elements and set callbacks to the + % relevant functions. If a handle to parant UI object is not + % specified, a seperate figure is created. An optional handle to a + % logging display panal may be provided, otherwise one is created. If + % the active flag is set to false (default is true), the panel is + % inactive and the instance of Alyx will be set to headless. + % + % See also Alyx if ~nargin % No parant object: create new figure f = figure('Name', 'alyx GUI',... @@ -84,6 +87,9 @@ obj.NewExpSubject.addlistener('SelectionChanged', @(src, evt)obj.dispWaterReq(src, evt)); end + % Default to active AlyxPanel + if nargin < 2; active = true; end + obj.RootContainer = uix.Panel('Parent', parent, 'Title', 'Alyx'); alyxbox = uiextras.VBox('Parent', obj.RootContainer); @@ -98,6 +104,12 @@ 'Callback', @(~,~)obj.login); loginbox.Widths = [-1 75]; + % If active flag set as false, make Alyx headless + if ~active + obj.AlyxInstance.Headless = true; + set(obj.LoginButton, 'Enable', 'off') + end + waterReqbox = uix.HBox('Parent', alyxbox); obj.WaterRequiredText = bui.label('Log in to see water requirements', waterReqbox); % water required text % Button to refresh all data retrieved from Alyx diff --git a/+eui/MControl.m b/+eui/MControl.m index f46c4e96..c7ab5901 100644 --- a/+eui/MControl.m +++ b/+eui/MControl.m @@ -6,10 +6,6 @@ % - improve it. % - ensure all Parent objects specified explicitly (See GUI Layout % Toolbox 2.3.1/layoutdoc/Getting_Started2.html) - % - Do PrePostExpDelayEdits still store handles now it's moved to new - % dialog? - % - Tidy Options dialog - % - Comment rigOptions function % See also MC, EUI.ALYXPANEL, EUI.EXPPANEL, EUI.LOG, EUI.PARAMEDITOR % % Part of Rigbox From 2a210616e44b0eda830b8015a5c6bb44268dafe6 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 22 Mar 2018 14:06:13 +0000 Subject: [PATCH 103/393] Restored stop delay to former function * Upon calling stop method, timeline immediatley stops all outputs before pausing for th duration of the StopDelay, then stops the main aquisition session. * A warning is now issued if the user sets the stop delay to a value less than 2. --- +hw/Timeline.m | 100 +++++++++++++++++++++++++++---------------------- 1 file changed, 55 insertions(+), 45 deletions(-) diff --git a/+hw/Timeline.m b/+hw/Timeline.m index 4850b519..30c613c6 100644 --- a/+hw/Timeline.m +++ b/+hw/Timeline.m @@ -14,13 +14,13 @@ % assumed that the time between sending the chrono pulse and recieving it % is negligible. % -% There are other available clocking signals, for instance: 'acqLive' and 'clock'. -% The former outputs a high (+5V) signal the entire time tl is aquiring -% (0V otherwise), and can be used to trigger devices with a TTL input. -% The 'clock' output is a regular pulse at a frequency of +% There are other available clocking signals, for instance: 'acqLive' and +% 'clock'. The former outputs a high (+5V) signal the entire time tl is +% aquiring (0V otherwise), and can be used to trigger devices with a TTL +% input. The 'clock' output is a regular pulse at a frequency of % ClockOutputFrequency and duty cycle of ClockOutputDutyCycle. This can -% be used to trigger a camera at a specific frame rate. See "properties" below for -% further details on output configurations. +% be used to trigger a camera at a specific frame rate. See "properties" +% below for further details on output configurations. % % Besides the chrono signal, tl can aquire any number of inputs and % record their values on the same clock. For example a photodiode to @@ -43,9 +43,9 @@ % timeline.Outputs(1).DaqChannelID and timeline.Inputs(1).daqChannelID % timeline.wiringInfo('chrono'); % %Add the rotary encoder -% timeline.addInput('rotaryEncoder', 'ctr0', 'Position'); +% timeline.addInput('rotaryEncoder', 'ctr0', 'Position'); % %For a lick detector -% timeline.addInput('lickDetector', 'ctr2', 'EdgeCount'); +% timeline.addInput('lickDetector', 'ctr2', 'EdgeCount'); % %We want use camera frame acquisition trigger by default % timeline.UseOutputs{end+1} = 'clock'; % %Save your hardware.mat file @@ -64,7 +64,7 @@ % 2014-01 CB created % 2017-10 MW updated - + properties DaqVendor = 'ni' % 'ni' is using National Instruments USB-6211 DAQ DaqIds = 'Dev1' % Device ID can be found with daq.getDevices() @@ -78,26 +78,26 @@ 'terminalConfig', 'SingleEnded',... 'axesScale', 1) % multiplicative vertical scaling for when live plotting the input UseInputs = {'chrono'} % array of inputs to record while tl is running - StopDelay = 2 % currently pauses for at least 2 secs as 'hack' before stopping main DAQ session - MaxExpectedDuration = 2*60*60 % expected experiment time so data structure is initialised to sensible size (in secs) + StopDelay = 2 % currently pauses for at least 2 secs as 'hack' before stopping main DAQ session to allow + MaxExpectedDuration = 2*60*60 % expected experiment time so data structure is initialised to sensible size (in secs) AquiredDataType = 'double' % default data type for the acquired data array (i.e. Data.rawDAQData) UseTimeline = false % used by expServer. If true, timeline is started by default (otherwise can be toggled with the t key) LivePlot = false % if true the data are plotted as the data are aquired FigureScale = []; % figure position in normalized units, default is [0 0 1 1] (full screen) WriteBufferToDisk = false % if true the data buffer is written to disk as they're aquired NB: in the future this will happen by default end - + properties (Dependent) SamplingInterval % defined as 1/DaqSampleRate IsRunning = false % flag is set to true when the first chrono pulse is aquired and set to false when tl is stopped (and everything saved), see tl.process and tl.stop end - + properties (Transient, Access = protected) Listener % holds the listener for 'DataAvailable', see DataAvailable and Timeline.process() - Sessions = containers.Map % map of daq sessions and their channels, created at tl.start() + Sessions = containers.Map % map of daq sessions and their channels, created at tl.start() LastTimestamp % the last timestamp returned from the daq during the DataAvailable event. Used to check sampling continuity, see tl.process() Ref % the expRef string. See tl.start() - AlyxInstance % a struct contraining the Alyx token, user and url for ile registration. See tl.start() + AlyxInstance % a struct contraining the Alyx token, user and url for ile registration. See tl.start() Data % A structure containing timeline data Axes % A figure handle for plotting the aquired data as it's processed DataFID % The data file ID for writing aquired data directly to disk @@ -111,7 +111,7 @@ % Adds chrono, aquireLive and clock to the outputs list, % along with default ports and delays - obj.DaqSamplesPerNotify = 1/obj.SamplingInterval; % calculate DaqSamplesPerNotify + obj.DaqSamplesPerNotify = 1/obj.SamplingInterval; % calculate DaqSamplesPerNotify if nargin % if old tl hardware struct provided, use these to populate properties % Configure the inputs obj.Inputs = hw.inputs; @@ -139,7 +139,7 @@ function start(obj, expRef, ai) end obj.Ref = expRef; % set the current experiment ref obj.AlyxInstance = ai; % set the current instance of Alyx - init(obj); % start the relevent sessions and add channels + init(obj); % start the relevent sessions and add channels obj.Listener = obj.Sessions('main').addlistener('DataAvailable', @obj.process); % add listener @@ -162,7 +162,7 @@ function start(obj, expRef, ai) fprintf(parfid, 'Fs = %d\n', obj.DaqSampleRate); % record the DAQ sample date fclose(parfid); % close the file end - + obj.Data.rawDAQData = zeros(numSamples, numInputChannels, obj.AquiredDataType); obj.Data.rawDAQSampleCount = 0; obj.Data.startDateTime = now; @@ -233,7 +233,7 @@ function record(obj, name, event, t) % Timeline data acquistion. 'strict' is optional (defaults to true), and % if true, this function will fail if Timeline is not running. If false, % it will just return the time using Psychtoolbox GetSecs if it's not - % running. + % running. % See also TL.PTBSECSTOTIMELINE(). if nargin < 2; strict = true; end if obj.IsRunning @@ -259,7 +259,7 @@ function record(obj, name, event, t) end function addInput(obj, name, channelID, measurement,... - terminalConfig, axesScale, use) + terminalConfig, axesScale, use) % Add a new input to the object's Input property % ADDINPUT(name, channelID, measurement, terminalConfig, use) % adds a new input 'name' to the Inputs list. If use is @@ -268,7 +268,7 @@ function addInput(obj, name, channelID, measurement,... % if no terminal config specified, leave empty which means use the % DAQ default for that port if nargin < 5; terminalConfig = []; end - + % if use is not specified, assume user wants normal scaling if nargin < 6; axesScale = 1; end @@ -322,7 +322,7 @@ function wiringInfo(obj, name) outputClasses = arrayfun(@class, obj.Outputs, 'uni', false); if strcmp(name, 'chrono') % Chrono wiring info idI = cellfun(@(s2)strcmp('chrono',s2), {obj.Inputs.name}); - idO = find(cellfun(@(s2)strcmp('tlOutputChrono',s2), outputClasses),1); + idO = find(cellfun(@(s2)strcmp('tlOutputChrono',s2), outputClasses),1); fprintf('Bridge terminals %s and %s\n',... obj.Outputs(idO).daqChannelID, obj.Inputs(idI).daqChannelID) elseif any(strcmp(name, {obj.Outputs.name})) % Output wiring info @@ -351,33 +351,43 @@ function wiringInfo(obj, name) if isfield(obj.Data, 'rawDAQSampleCount')&&... obj.Data.rawDAQSampleCount > 0 % obj.Data.rawDAQSampleCount is greater than 0 during the first call to tl.process - bool = true; + bool = true; else % obj.Data is cleared in tl.stop, after all data are saved - bool = false; + bool = false; end end + function set.StopDelay(obj, delay) + if delay < 2 + warning('Timeline:StopDelay:DelayTooShort',... + 'A stop delay less than 2s may cause some output samples to be missed upon stopping'); + end + obj.StopDelay = delay; + end + function stop(obj) %TL.STOP Stops Timeline data acquisition % TL.STOP() Deletes the listener, saves the aquired data, - % stops all running DAQ sessions - % + % stops all running DAQ sessions + % % See Also HW.TLOUTPUT/STOP if ~obj.IsRunning warning('Nothing to do, Timeline is not running!') return end - pause(obj.StopDelay) - + % stop acquisition output signals arrayfun(@stop, obj.Outputs) + % wait for the final samples to be aquired + pause(obj.StopDelay) + % stop actual DAQ aquisition stop(obj.Sessions('main')); - % wait before deleting the listener to ensure most recent samples are - % collected + % wait before deleting the listener to ensure most recent + % samples are processed pause(1.5); - delete(obj.Listener) % now delete the data listener + delete(obj.Listener) % now delete the data listener % only keep the used part of the daq input array obj.Data.rawDAQData((obj.Data.rawDAQSampleCount + 1):end,:) = []; @@ -403,7 +413,7 @@ function stop(obj) nextChrono = obj.Outputs(chronoOutputIdx).NextChronoSign; LastClockSentSysTime = obj.Outputs(chronoOutputIdx).LastClockSentSysTime; CurrSysTimeTimelineOffset = obj.Outputs(chronoOutputIdx).CurrSysTimeTimelineOffset; - end + end acqLiveOutputIdx = find(strcmp(outputClasses, 'hw.TLOutputAcqLive'),1); if ~isempty(acqLiveOutputIdx) acqLiveChan = obj.Outputs(acqLiveOutputIdx).DaqChannelID; @@ -411,7 +421,7 @@ function stop(obj) clockOutputIdx = find(strcmp(outputClasses, 'hw.TLOutputClock'),1); if ~isempty(clockOutputIdx) useClock = true; - clockF = obj.Outputs(clockOutputIdx).Frequency; + clockF = obj.Outputs(clockOutputIdx).Frequency; clockD = obj.Outputs(clockOutputIdx).DutyCycle; end @@ -430,7 +440,7 @@ function stop(obj) obj.Data.lastClockSentSysTime = LastClockSentSysTime; obj.Data.currSysTimeTimelineOffset = CurrSysTimeTimelineOffset; - % saving hardware metadata for each output + % saving hardware metadata for each output warning('off', 'MATLAB:structOnObject'); % sorry, don't care for outIdx = 1:numel(obj.Outputs) s = struct(obj.Outputs(outIdx)); @@ -441,11 +451,11 @@ function stop(obj) % save tl to all paths superSave(obj.Data.savePaths, struct('Timeline', obj.Data)); - + % write hardware info to a JSON file for compatibility with database if exist('savejson', 'file') % save local copy - savejson('hw', obj.Data.hw, fullfile(fileparts(obj.Data.savePaths{1}), 'TimelineHW.json')); + savejson('hw', obj.Data.hw, fullfile(fileparts(obj.Data.savePaths{1}), 'TimelineHW.json')); % save server copy savejson('hw', obj.Data.hw, fullfile(fileparts(obj.Data.savePaths{2}), 'TimelineHW.json')); else @@ -461,7 +471,7 @@ function stop(obj) else warning('did not write files into alf format. Check that alyx-matlab and npy-matlab repositories are in path'); end - + % register Timeline.mat file to Alyx database [subject, ~, ~] = dat.parseExpRef(obj.Data.expRef); if ~isempty(obj.AlyxInstance) && obj.AlyxInstance.IsLoggedIn && ~strcmp(subject,'default') @@ -507,10 +517,10 @@ function init(obj) % TL.INIT() creates all the DAQ sessions % and stores them in the Sessions map by their Outputs name. % Also add a 'main' session to which all input channels are - % added. + % added. % - % See Also DAQ.CREATESESSION - + % See Also DAQ.CREATESESSION + %%reate channels for each input [use, idx] = intersect({obj.Inputs.name}, obj.UseInputs);% find which inputs to use assert(numel(idx) == numel(obj.UseInputs), 'Not all inputs were recognised'); @@ -553,8 +563,8 @@ function process(obj, ~, event) % flipped on each call (at LastClockSentSysTime), and the % time of the previous flip is found in the data and its % timestamp noted. This is used by tl.time() to convert - % between system time and acquisition time. - % + % between system time and acquisition time. + % % LastTimestamp is the time of the last scan in the previous % data chunk, and is used to ensure no data samples have been % lost. @@ -565,7 +575,7 @@ function process(obj, ~, event) assert(abs(event.TimeStamps(1) - obj.LastTimestamp - obj.SamplingInterval) < 1e-8,... 'Discontinuity of DAQ acquistion detected: last timestamp was %f and this one is %f',... obj.LastTimestamp, event.TimeStamps(1)); - + %Process methods for outputs arrayfun(@(out)out.process(obj, event), obj.Outputs); @@ -621,7 +631,7 @@ function livePlot(obj, data) % figure and scale by scrolling the one that's hovered over scales = pick([obj.Inputs.axesScale], cellfun(@(x)find(strcmp({obj.Inputs.name}, x),1), obj.UseInputs)); - traces = get(obj.Axes, 'Children'); + traces = get(obj.Axes, 'Children'); if isempty(traces) Fs = obj.DaqSampleRate; plot((1:Fs*10)/Fs, zeros(Fs*10, length(names))+repmat(offsets, Fs*10, 1)); @@ -650,7 +660,7 @@ function livePlot(obj, data) % necessary to prevent the value from wandering way off % the range and making it impossible to see any of the % other traces. Plus it is probably more useful, - % anyway. + % anyway. yy(end-nSamps+1:end) = conv(diff([data(1,t); data(:,t)]),... gausswin(50)./sum(gausswin(50)), 'same') * scales(t) + offsets(t); else From ee3638f875db7b44fd4000641415d7fb43a0beee Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Mon, 26 Mar 2018 18:23:40 +0100 Subject: [PATCH 104/393] Fix'd bugs in plot2AFUC Plotting works correctly now; various bug fixes --- cortexlab/+psy/plot2AUFC.m | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/cortexlab/+psy/plot2AUFC.m b/cortexlab/+psy/plot2AUFC.m index 61744ebf..be2e4724 100644 --- a/cortexlab/+psy/plot2AUFC.m +++ b/cortexlab/+psy/plot2AUFC.m @@ -11,7 +11,7 @@ function plot2AUFC(ax, block) response = [block.trial.response]; repeatNum = [block.trial.repeatNum]; incl = ~any(isnan([contrast;response;repeatNum])); -contrast = contrast(incl); +contrast = contrast(:,incl); response = response(incl); repeatNum = repeatNum(incl); % if any(structfun(@isnan, block.trial(end))) % strip incomplete trials @@ -22,18 +22,11 @@ function plot2AUFC(ax, block) respTypes = unique(response); numRespTypes = numel(respTypes); -if size(contrast, 1) > 1 - allContrast = contrast; - contrast = diff(contrast, [], 1); -else - allContrast = [0;0]; -end - -if any(allContrast(1,:)>0 & allContrast(2,:)>0) +if any(contrast(1,:)>0 & contrast(2,:)>0) % mode for plotting task with two stimuli at once - cValsLeft = unique(allContrast(1,:)); - cValsRight = unique(allContrast(2,:)); + cValsLeft = unique(contrast(1,:)); + cValsRight = unique(contrast(2,:)); nCL = numel(cValsLeft); nCR = numel(cValsRight); % pedVals = cVals(1:end-1); @@ -47,7 +40,7 @@ function plot2AUFC(ax, block) for r = 1:numRespTypes for c1 = 1:nCL for c2 = 1:nCR - incl = repeatNum==1&allContrast(1,:)==cValsLeft(c1)&allContrast(2,:) == cValsRight(c2); + incl = repeatNum==1&contrast(1,:)==cValsLeft(c1)&contrast(2,:) == cValsRight(c2); numTrials(1,c1,c2) = sum(incl); numChooseR(r,c1,c2) = sum(response==respTypes(r)&incl); @@ -74,7 +67,7 @@ function plot2AUFC(ax, block) caxis(ax, [-1 1]); axis(ax, 'image') else - + contrast = diff(contrast, [], 1); cVals = unique(contrast); colors = iff(numRespTypes>2,[0 1 1; 0 1 0; 1 0 0], [0 1 1; 1 0 0]); psychoM = zeros(numRespTypes,length(cVals)); From 8884002ce3296f1de2acdc50b6c48c6c1a00f474 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Mon, 26 Mar 2018 18:59:53 +0100 Subject: [PATCH 105/393] Added colourmap used by psy.plot2AUFC --- cortexlab/+psy/colormap_pinkgreyscale.m | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 cortexlab/+psy/colormap_pinkgreyscale.m diff --git a/cortexlab/+psy/colormap_pinkgreyscale.m b/cortexlab/+psy/colormap_pinkgreyscale.m new file mode 100644 index 00000000..0ae7753c --- /dev/null +++ b/cortexlab/+psy/colormap_pinkgreyscale.m @@ -0,0 +1,9 @@ +function map = colormap_pinkgreyscale() +% pink for one extreme, greyscale for the other half. + +map = zeros(100,3); +map(50:-1:1,:) = repmat([(1:50)/50]',1,3); +map(100,:) = [1 0 1]; + +map = map(end:-1:1,:); + From 433635d69d7034dba826a9482854b0091b6cc113 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Mon, 9 Apr 2018 19:45:11 +0100 Subject: [PATCH 106/393] Added rewardSize ALF file save NPY file called cwFeedback.rewardVolume.npy now saved at the end of the experiment along with the other ALF files. --- +exp/SignalsExp.m | 1 + 1 file changed, 1 insertion(+) diff --git a/+exp/SignalsExp.m b/+exp/SignalsExp.m index 12dc3732..6073d9a0 100644 --- a/+exp/SignalsExp.m +++ b/+exp/SignalsExp.m @@ -869,6 +869,7 @@ function saveData(obj) writeNPY(feedback(:), fullfile(expPath, 'cwFeedback.type.npy')); alf.writeEventseries(expPath, 'cwFeedback',... obj.Data.events.feedbackTimes, [], []); + writeNPY([obj.Data.outputs.rewardValues]', fullfile(expPath, 'cwFeedback.rewardVolume.npy')); else warning('No ''feedback'' events recorded, cannot register to Alyx') end From e3390c6e40f085ef37b7bc4a34eff431cb3de13f Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 10 Apr 2018 11:36:26 +0100 Subject: [PATCH 107/393] Removed old/legacy code from hw.devices Also added FIXME comment to eui.AlyxPanel: low weight thresholds inaccurate, requires change to endpoint --- +eui/AlyxPanel.m | 2 ++ +hw/devices.m | 26 -------------------------- 2 files changed, 2 insertions(+), 26 deletions(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index 4945e0ca..a9689776 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -521,6 +521,8 @@ function viewSubjectHistory(obj, ax) plot(ax, dates, [records.weight_measured], '.-'); hold(ax, 'on'); + % FIXME: This weights are inaccurate - should be + % ([records.weight_expected]-implantWeight)*0.7 + implantWeight plot(ax, dates, [records.weight_expected]*0.7, 'r', 'LineWidth', 2.0); plot(ax, dates, [records.weight_expected]*0.8, 'LineWidth', 2.0, 'Color', [244, 191, 66]/255); box(ax, 'off'); diff --git a/+hw/devices.m b/+hw/devices.m index 82598ab6..4a1a4616 100644 --- a/+hw/devices.m +++ b/+hw/devices.m @@ -19,13 +19,6 @@ paths = dat.paths(name); -% if strcmp(name, 'zen') -% baseDir = 'D:\Users\Chris\Documents\MATLAB\Experiments'; -% configDir = fullfile(fullfile(baseDir, 'config'), name); -% else -% baseDir = '\\zserver\code\Rigging'; -% configDir = fullfile(fullfile(baseDir, 'config'), name); -% end %% Basic initialisation fn = fullfile(paths.rigConfig, 'hardware.mat'); if ~file.exists(fn) @@ -43,26 +36,7 @@ %% Configure common devices, if present configure('mouseInput'); -% configure('rewardController'); configure('lickDetector'); -if isfield(rig, 'laser') - configure('laser', rig.rewardController.DaqSession); -end - -%% Deal with reward controller calibrations -% if init && isfield(rig, 'rewardController') -% if isfield(rig, 'rewardCalibrations') -% % use most recent reward calibration -% [newestDate, idx] = max([rig.rewardCalibrations.dateTime]); -% rig.rewardController.MeasuredDeliveries =... -% rig.rewardCalibrations(idx).measuredDeliveries; -% fprintf('\nApplying reward calibration performed on %s\n', datestr(newestDate)); -% else -% %create an empty structure -% rig.rewardCalibrations = struct('dateTime', {}, 'measuredDeliveries', {}); -% warning('Rigbox:hw:calibration', 'No reward calibrations found'); -% end -% end %% Set up controllers if init From ffce90c29ff25993e608d046ad690b4fabfc856d Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Mon, 16 Apr 2018 17:56:05 +0100 Subject: [PATCH 108/393] Fixes for new file registration end point * Added dependant submodules to the repo * Add rigbox paths now does much of the setting up for the user * AlyxPanel percent weights now take into account the implant weight * Contrast ALF files now saved in % units * Timeline and tlMpepListner now allows one to toggle the live plot --- +dat/expExists.m | 5 +- +dat/listSubjects.m | 4 +- +eui/AlyxPanel.m | 32 ++++--- +exp/SignalsExp.m | 22 +++-- +hw/Timeline.m | 21 ++--- .gitmodules | 9 ++ addRigboxPaths.m | 158 +++++++++++++++++++++++++-------- alyx-matlab | 1 + cortexlab/+tl/bindMpepServer.m | 9 +- npy-matlab | 1 + readme.md | 5 +- signals | 1 + 12 files changed, 190 insertions(+), 78 deletions(-) create mode 100644 .gitmodules create mode 160000 alyx-matlab create mode 160000 npy-matlab create mode 160000 signals diff --git a/+dat/expExists.m b/+dat/expExists.m index ffd6d8f4..88ea312e 100644 --- a/+dat/expExists.m +++ b/+dat/expExists.m @@ -1,6 +1,9 @@ function b = expExists(expRef) %DAT.EXPEXISTS Confirm existence of experiment(s) with reference -% b = DAT.EXPEXISTS(expRef) TODO +% b = DAT.EXPEXISTS(expRef) Returns true is expRef exists, where expRef +% is an experiment reference string or cell array thereof. +% +% See Also DAT.LISTEXPS, DAT.PATHS % % Part of Rigbox diff --git a/+dat/listSubjects.m b/+dat/listSubjects.m index dbac4793..cca333d7 100644 --- a/+dat/listSubjects.m +++ b/+dat/listSubjects.m @@ -9,7 +9,7 @@ % Part of Rigbox % 2013-03 CB created -% 2018-01 NS added alyx compatibility +% 2018-01 NS added Alyx compatibility if nargin>0 && ~isempty(varargin{1}) % user provided an alyx instance ai = varargin{1}; % an alyx instance @@ -38,6 +38,6 @@ expInfoPath = dat.reposPath('expInfo', 'master'); dirs = file.list(expInfoPath, 'dirs'); - subjects = setdiff(dirs, {'misc'}); %exclude the misc directory + subjects = dirs(~cellfun(@(d)startsWith(d, '@'), dirs)); % exclude misc directories end end \ No newline at end of file diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index a9689776..9aa5ee11 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -343,7 +343,7 @@ function dispWaterReq(obj, src, ~) gel = getOr(record, 'hydrogel_given', 0); % Get total gel given weight_expected = getOr(record, 'weight_expected', NaN); % Set colour based on weight percentage - weight_pct = weight/weight_expected; + weight_pct = (weight-wr.implant_weight)/(weight_expected-wr.implant_weight); if weight_pct < 0.8 % Mouse below 80% original weight colour = [0.91, 0.41, 0.17]; % Orange weight_pct = '< 80%'; @@ -408,7 +408,7 @@ function recordWeight(obj, weight, subject) % convert to double if weight is a string weight = iff(ischar(weight{1}), str2double(weight{1}), weight{1}); try - w = postWeight(ai, weight, subject); %FIXME: If multiple things flushed, length(w)>1 + w = postWeight(ai, weight, subject); obj.log('Alyx weight posting succeeded: %.2f for %s', w.weight, w.subject); catch if ~ai.IsLoggedIn % if not logged in, save the weight for later @@ -501,7 +501,10 @@ function viewSubjectHistory(obj, ax) % collect the data for the table endpnt = sprintf('water-requirement/%s?start_date=2016-01-01&end_date=%s', obj.Subject, datestr(now, 'yyyy-mm-dd')); wr = obj.AlyxInstance.getData(endpnt); + iw = wr.implant_weight; records = catStructs(wr.records, nan); + expected = [records.weight_expected]; + expected(expected==0) = nan; % no weighings found if isempty(wr.records) obj.log('No weight data found for subject %s', obj.Subject); @@ -521,10 +524,8 @@ function viewSubjectHistory(obj, ax) plot(ax, dates, [records.weight_measured], '.-'); hold(ax, 'on'); - % FIXME: This weights are inaccurate - should be - % ([records.weight_expected]-implantWeight)*0.7 + implantWeight - plot(ax, dates, [records.weight_expected]*0.7, 'r', 'LineWidth', 2.0); - plot(ax, dates, [records.weight_expected]*0.8, 'LineWidth', 2.0, 'Color', [244, 191, 66]/255); + plot(ax, dates, ((expected-iw)*0.7)+iw, 'r', 'LineWidth', 2.0); + plot(ax, dates, ((expected-iw)*0.8)+iw, 'LineWidth', 2.0, 'Color', [244, 191, 66]/255); box(ax, 'off'); if numel(dates) > 1; xlim(ax, [min(dates) max(dates)]); end if nargin == 1 @@ -536,7 +537,7 @@ function viewSubjectHistory(obj, ax) if nargin==1 ax = axes('Parent', plotBox); - plot(ax, dates, [records.weight_measured]./[records.weight_expected], '.-'); + plot(ax, dates, ([records.weight_measured]-iw)./(expected-iw), '.-'); hold(ax, 'on'); plot(ax, dates, 0.7*ones(size(dates)), 'r', 'LineWidth', 2.0); plot(ax, dates, 0.8*ones(size(dates)), 'LineWidth', 2.0, 'Color', [244, 191, 66]/255); @@ -564,14 +565,14 @@ function viewSubjectHistory(obj, ax) weightsByDate = num2cell([records.weight_measured]); weightsByDate = cellfun(@(x)sprintf('%.1f', x), weightsByDate, 'uni', false); weightsByDate(isnan([records.weight_measured])) = {[]}; - weightPctByDate = num2cell([records.weight_measured]./[records.weight_expected]); + weightPctByDate = num2cell(([records.weight_measured]-iw)./(expected-iw)); weightPctByDate = cellfun(@(x)sprintf('%.1f', x*100), weightPctByDate, 'uni', false); weightPctByDate(isnan([records.weight_measured])) = {[]}; dat = horzcat(... arrayfun(@(x)datestr(x), dates', 'uni', false), ... weightsByDate', ... - arrayfun(@(x)sprintf('%.1f', 0.8*x), [records.weight_expected]', 'uni', false), ... + arrayfun(@(x)sprintf('%.1f', 0.8*(x-iw)), [records.weight_expected]', 'uni', false), ... weightPctByDate'); waterDat = (... num2cell(horzcat([records.water_given]', [records.hydrogel_given]', ... @@ -591,11 +592,7 @@ function viewAllSubjects(obj) ai = obj.AlyxInstance; if ai.IsLoggedIn wr = ai.getData(ai.makeEndpoint('water-restricted-subjects')); - - subjs = cellfun(@(x)x.nickname, wr, 'uni', false); - waterReqTotal = cellfun(@(x)x.water_requirement_total, wr, 'uni', false); - waterReqRemain = cellfun(@(x)x.water_requirement_remaining, wr, 'uni', false); - + % build a figure to show it f = figure; % popup a new figure for this wrBox = uix.VBox('Parent', f); @@ -607,11 +604,12 @@ function viewAllSubjects(obj) % colorgen = @(colorNum,text) ['<html><table border=0 width=400 bgcolor=#',htmlColor(colorNum),'><TR><TD>',text,'</TD></TR> </table></html>']; colorgen = @(colorNum,text) ['<html><body bgcolor=#',htmlColor(colorNum),'>',text,'</body></html>']; - wrdat = cellfun(@(x)colorgen(1-double(x>0)*[0 0.3 0.3], sprintf('%.2f',x)), waterReqRemain, 'uni', false); + wrdat = cellfun(@(x)colorgen(1-double(x>0)*[0 0.3 0.3],... + sprintf('%.2f',x)), {wr.water_requirement_remaining}, 'uni', false); set(wrTable, 'ColumnName', {'Name', 'Water Required', 'Remaining Requirement'}, ... - 'Data', horzcat(subjs', ... - cellfun(@(x)sprintf('%.2f',x),waterReqTotal', 'uni', false), ... + 'Data', horzcat({wr.nickname}', ... + cellfun(@(x)sprintf('%.2f',x),{wr.water_requirement_total}', 'uni', false), ... wrdat'), ... 'ColumnEditable', false(1,3)); end diff --git a/+exp/SignalsExp.m b/+exp/SignalsExp.m index 6073d9a0..d6f86b99 100644 --- a/+exp/SignalsExp.m +++ b/+exp/SignalsExp.m @@ -907,8 +907,8 @@ function saveData(obj) contL = getOr(obj.Data.events, 'contrastLeftValues', NaN); contR = getOr(obj.Data.events, 'contrastRightValues', NaN); if ~any(isnan(contL))&&~any(isnan(contR)) - writeNPY(contL(:), fullfile(expPath, 'cwStimOn.contrastLeft.npy')); - writeNPY(contR(:), fullfile(expPath, 'cwStimOn.contrastRight.npy')); + writeNPY(contL(:)*100, fullfile(expPath, 'cwStimOn.contrastLeft.npy')); + writeNPY(contR(:)*100, fullfile(expPath, 'cwStimOn.contrastRight.npy')); else warning('No ''contrastLeft'' and/or ''contrastRight'' events recorded, cannot register to Alyx') end @@ -929,7 +929,10 @@ function saveData(obj) writeNPY(wheelValues./wheelTimes, fullfile(expPath, 'Wheel.velocity.npy')); % Register them to Alyx - obj.AlyxInstance.registerALF(expPath); + files = dir(expPath); + isNPY = cellfun(@(f)endsWith(f, '.npy'), {files.name}); + files = files(isNPY); + obj.AlyxInstance.registerFile(fullfile({files.folder}, {files.name})); catch ex warning(ex.identifier, 'Failed to register alf files: %s.', ex.message); end @@ -939,19 +942,20 @@ function saveData(obj) warning('No Alyx token set'); else try - [subject, expDate, seq] = dat.parseExpRef(obj.Data.expRef); + [subject, seq] = dat.parseExpRef(obj.Data.expRef); if strcmp(subject, 'default'); return; end % Register saved files - obj.AlyxInstance.registerFile(savepaths{end}, 'mat',... - obj.AlyxInstance.SessionURL, 'Block', []); + obj.AlyxInstance.registerFile(savepaths{end}); % obj.AlyxInstance.registerFile(savepaths{end}, 'mat',... % {subject, expDate, seq}, 'Block', []); % Save the session end time if ~isempty(obj.AlyxInstance.SessionURL) - obj.AlyxInstance.putData(obj.AlyxInstance.SessionURL,... - struct('end_time', obj.AlyxInstance.datestr(now), 'subject', subject)); + obj.AlyxInstance.postData(obj.AlyxInstance.SessionURL,... + struct('end_time', obj.AlyxInstance.datestr(now), 'subject', subject), 'put'); else - % Infer from date session and retrieve using expFilePath + % Retrieve session from endpoint +% subsessions = obj.AlyxInstance.getData(... +% sprintf('sessions?type=Experiment&subject=%s&number=%i', subject, seq)); end catch ex warning(ex.identifier, 'Failed to register files to Alyx: %s', ex.message); diff --git a/+hw/Timeline.m b/+hw/Timeline.m index 30c613c6..f0ae52b1 100644 --- a/+hw/Timeline.m +++ b/+hw/Timeline.m @@ -453,18 +453,12 @@ function stop(obj) superSave(obj.Data.savePaths, struct('Timeline', obj.Data)); % write hardware info to a JSON file for compatibility with database - if exist('savejson', 'file') - % save local copy - savejson('hw', obj.Data.hw, fullfile(fileparts(obj.Data.savePaths{1}), 'TimelineHW.json')); - % save server copy - savejson('hw', obj.Data.hw, fullfile(fileparts(obj.Data.savePaths{2}), 'TimelineHW.json')); - else - warning('JSONlab not found - hardware information not saved to ALF') - end + hw = jsonencode(obj.Data.hw); %#ok<NASGU> + save(fullfile(fileparts(obj.Data.savePaths{2}), 'TimelineHW.json'), 'hw', '-ascii'); % save each recorded vector into the correct format in Timeline % timebase for Alyx and optionally into universal timebase if - % conversion is provided + % conversion is provided. TODO: Make timelineToALF a class method if ~isempty(which('alf.timelineToALF'))&&~isempty(which('writeNPY')) alf.timelineToALF(obj.Data, [],... fileparts(dat.expFilePath(obj.Data.expRef, 'timeline', 'master'))) @@ -602,7 +596,14 @@ function process(obj, ~, event) end %If plotting the channels live, plot the new data - if obj.LivePlot; obj.livePlot(event.Data); end + if obj.LivePlot + obj.livePlot(event.Data) + else % If LivePlot has been toggled to false, delete the figure + if ~isempty(obj.Axes) + close(obj.Axes.Parent) + obj.Axes = []; + end + end end function livePlot(obj, data) diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..4b73c981 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,9 @@ +[submodule "alyx-matlab"] + path = alyx-matlab + url = https://github.com/cortex-lab/alyx-matlab/ +[submodule "signals"] + path = signals + url = https://github.com/dendritic/signals +[submodule "npy-matlab"] + path = npy-matlab + url = https://github.com/kwikteam/npy-matlab diff --git a/addRigboxPaths.m b/addRigboxPaths.m index 2fa54a8b..9c41c4a4 100644 --- a/addRigboxPaths.m +++ b/addRigboxPaths.m @@ -3,63 +3,121 @@ function addRigboxPaths(savePaths) % % Part of the Rigging toolbox % TODO: -% - Paths to 'cortexlab' and 'cb-tools' were incorrect % - Consider renaming above folder to something more informative % % 2014-01 CB % 2017-02 MW Updated to work with 2016b -if nargin < 1 - savePaths = true; -end +% Flag for perminantly saving paths +if nargin < 1; savePaths = true; end + +%%% MATLAB version and toolbox validation %%% +% MATLAB must be running on Windows +assert(ispc, 'Rigbox currently only works on Windows 7 or later') + +% Microsoft Visual C++ Redistributable for Visual Studio 2015 must be +% installed, check for runtime dll file in system32 folder +sys32 = dir('C:\Windows\System32'); +assert(any(strcmpi('VCRuntime140.dll',{sys32.name})), 'Rigbox:setup:libraryRequired',... + ['Requires Microsoft Visual C++ Redistributable for Visual Studio 2015. ',... + 'Click <a href="matlab:web(''%s'',''-browser'')">here</a> to install.'],... + 'https://www.microsoft.com/en-us/download/details.aspx?id=48145') -rigboxPath = fileparts(mfilename('fullpath')); -cbToolsPath = fullfile(rigboxPath, 'cb-tools'); % Assumes 'cb-tools' in same -% directory as Rigbox, was not the case +% Check MATLAB 2016b is running +assert(~verLessThan('matlab', '8.4'), 'Requires MATLAB 2016b or later') -% 2017-02-17 GUI Layout Toolbox should be installed as matlab toolbox +% Check essential toolboxes are installed (common to both master and +% stimulus computers) toolboxes = ver; +requiredMissing = setdiff(... + {'Data Acquisition Toolbox', ... + 'Signal Processing Toolbox', ... + 'Instrument Control Toolbox'}, ... + 'Statistics and Machine Learning Toolbox',... + {toolboxes.Name}); + +assert(isempty(requiredMissing),'Rigbox:setup:toolboxRequired',... + 'Please install the following toolboxes before proceeding: \n%s',... + strjoin(requiredMissing, '\n')) + +% Check that GUI Layout Toolbox is installed (required for the master +% computer only) isInstalled = strcmp('GUI Layout Toolbox', {toolboxes.Name}); -if any(isInstalled) - fprintf('GUI Layout Toolbox version %s is currently installed\n', toolboxes(isInstalled).Version) -else - warning('MC requires GUI Layout Toolbox v2.3 or higher to be installed') +if ~any(isInstalled) ||... + str2double(strrep(toolboxes(isInstalled).Version,'.', '')) < 230 + warning('Rigbox:setup:toolboxRequired',... + ['MC requires GUI Layout Toolbox v2.3 or higher to be installed. '... + 'Click <a href="matlab:web(''%s'',''-browser'')">here</a> to install.'],... + 'https://uk.mathworks.com/matlabcentral/fileexchange/47982-gui-layout-toolbox') end -% Check MATLAB 2016b is running -assert(~verLessThan('matlab', '8.4'), 'Requires MATLAB 2014b or later'); - -cortexLabAddonsPath = fullfile(rigboxPath, 'rigbox-cortexlab'); % doesn't exist 2017-02-13 -if ~isdir(cortexLabAddonsPath) % handle two possible alternative paths - cortexLabAddonsPath = fullfile(rigboxPath, 'cortexlab'); % doesn't exist in Rigbox directory 2017-02-13 +% Check that the Psychophisics Toolbox is installed (required for the +% stimulus computer only) +isInstalled = strcmp('Psychtoolbox', {toolboxes.Name}); +if ~any(isInstalled) || str2double(toolboxes(isInstalled).Version(1)) < 3 + warning('Rigbox:setup:toolboxRequired',... + ['The stimulus computer requires Psychtoolbox v3.0 or higher to be installed. '... + 'Click <a href="matlab:web(''%s'',''-browser'')">here</a> to install.'],... + 'https://github.com/Psychtoolbox-3/Psychtoolbox-3/releases') end -addpath(... - cortexLabAddonsPath,... % add the Rigging cortexlab add-ons - rigboxPath,... % add Rigbox itself - cbToolsPath,... % add cb-tools root dir - fullfile(cbToolsPath, 'burgbox')); % Burgbox -% guiLayoutPath,... % add GUI Layout toolbox -% fullfile(guiLayoutPath, 'layout'),... -% fullfile(guiLayoutPath, 'Patch'),... -% fullfile(guiLayoutPath, 'layoutHelp')... -% ); - -if savePaths - assert(savepath == 0, 'Failed to save changes to MATLAB path'); +% Check that the NI DAQ support package is installed (required for the +% stimulus computer only) +info = matlabshared.supportpkg.getInstalled; +if isempty(info) || ~any(contains({info.Name}, 'NI-DAQmx')) + warning('Rigbox:setup:toolboxRequired',... + ['The stimulus computer requires the National Instruments support package to be installed. '... + 'Click <a href="matlab:web(''%s'',''-browser'')">here</a> to install.'],... + 'https://www.mathworks.com/hardware-support/nidaqmx.html') end -cbtoolsjavapath = fullfile(cbToolsPath, 'java'); +%%% Paths for adding +% Add the main Rigbox directory, containing the main packages for running +% the experiment server and mc programmes +root = fileparts(mfilename('fullpath')); +addpath(root); + +% The cb-tools directory contains numerious convenience functions which are +% utilized by the main code. Those within the 'burgbox' directory were +% written by Chris Burgess. +addpath(fullfile(root, 'cb-tools'), fullfile(root, 'cb-tools', 'burgbox')); + +% Add CortexLab paths. These are mostly extra classes that allow Rigbox to +% work with other software developed by CortexLab, including MPEP +addpath(fullfile(root, 'cortexlab')); + +% Add signals paths, this includes all the core code for running signals +% experiments. This submodule is maintained by Chris Burgess. +addpath(fullfile(root, 'signals', 'util'), fullfile(root, 'signals', 'mexnet')); +% Add the Java paths for signals +jcp = fullfile(fileparts(root, 'signals', 'java')); +if ~any(strcmp(javaclasspath, jcp)); javaaddpath(jcp); end + +% Add the paths for Alyx-matlab. This submodule allows one to interact +% with an instance of an Alyx database. For more information please visit: +% http://alyx.readthedocs.io/en/latest/ +addpath(fullfile(root, 'alyx-matlab'), fullfile(root, 'helpers')); + +% Add paths for the npy-matlab. This submodule is maintained by the +% Kwik Team (https://github.com/kwikteam). It allows for the saving of +% NumPy binary files. Used by Rigbox to save data as .npy files with the +% ALF (ALex File) naming convention. For more information please visit: +% https://docs.scipy.org/doc/numpy-dev/neps/npy-format.html +addpath(fullfile(root, 'npy-matlab')); + +% Add the Java paths for Java WebSockets used for communications between +% the stimulus computer and the master computer +cbtoolsjavapath = fullfile(root, 'cb-tools', 'java'); javaclasspathfile = fullfile(prefdir, 'javaclasspath.txt'); fid = fopen(javaclasspathfile, 'a+'); fseek(fid, 0, 'bof'); closeFile = onCleanup( @() fclose(fid) ); -javaclasspaths = first(textscan(fid,'%s', 'CommentStyle', '#',... - 'Delimiter','')); % this will crash on 2014b, but not in 2016b +javaclasspaths = first(textscan(fid,'%s', 'CommentStyle', '#', 'Delimiter','')); cbtoolsInJavaPath = any(strcmpi(javaclasspaths, cbtoolsjavapath)); +%%% Validate that paths saved correctly %%% if savePaths -% assert(savepath == 0, 'Failed to save changes to MATLAB path'); + assert(savepath == 0, 'Failed to save changes to MATLAB path'); if ~cbtoolsInJavaPath fseek(fid, 0, 'eof'); n = fprintf(fid, '\n#path to CB-tools java classes\n%s', cbtoolsjavapath); @@ -72,4 +130,34 @@ function addRigboxPaths(savePaths) 'Cannot use java classes without saving new classpath'); end +%%% Attempt to move dll file for signals %%% +fileName = fullfile(root, 'signals', 'msvcr120.dll'); +fileExists = any(strcmp('msvcr120.dll',{sys32.name})); +copied = false; +if isWindowsAdmin % If user has admin privileges, attempt to copy dll file + if fileExists % If there's already a dll file there prompt use to make backup + prompt = sprintf(['For signals to work propery, it is nessisary to copy ',... + 'the file \n<strong>', strrep(fileName, '\', '\\'), '</strong> to ',... + '<strong>C:\\Windows\\System32</strong>.\n',... + 'You may want to make a backup of your existing dll file before continuing.\n\n',... + 'Do you want to proceed? Y/N [Y]: ']); + str = input(prompt,'s'); if isempty(str); str = 'y'; end + if strcmpi(str, 'n'); return; end % Return without copying + end + copied = copyfile(fileName, 'C:\Windows\System32'); +end +% Check that the file was copied +if ~copied + warning('Rigbox:setup:libraryRequired', ['Please copy the file ',... + '<strong>%s</strong> to <strong>C:\\Windows\\System32</strong>.'], fileName) +end + +function out = isWindowsAdmin() +%ISWINDOWSADMIN True if this user is in admin role. +% 2011 Andrew Janke (https://github.com/apjanke) +if ~NET.isNETSupported; out = false; return; end +wi = System.Security.Principal.WindowsIdentity.GetCurrent(); +wp = System.Security.Principal.WindowsPrincipal(wi); +out = wp.IsInRole(System.Security.Principal.WindowsBuiltInRole.Administrator); +end end \ No newline at end of file diff --git a/alyx-matlab b/alyx-matlab new file mode 160000 index 00000000..94334e93 --- /dev/null +++ b/alyx-matlab @@ -0,0 +1 @@ +Subproject commit 94334e938c52f225dc0dcd78ea670205c9cf603f diff --git a/cortexlab/+tl/bindMpepServer.m b/cortexlab/+tl/bindMpepServer.m index 9b4a5024..5e371841 100644 --- a/cortexlab/+tl/bindMpepServer.m +++ b/cortexlab/+tl/bindMpepServer.m @@ -21,6 +21,7 @@ % mpepSendPort = 1103; % send responses back to this remote port quitKey = KbName('esc'); manualStartKey = KbName('t'); +livePlotKey = KbName('p'); %% Start UDP communication listeners = struct(... @@ -119,7 +120,10 @@ function listen() KbQueueCreate(); KbQueueStart(); cleanup1 = onCleanup(@KbQueueRelease); - log('Polling for UDP messages. PRESS <%s> TO QUIT', KbName(quitKey)); + log(['Polling for UDP messages. PRESS <%s> TO QUIT, '... + '<%s> to manually start/stop timeline, and ',... + '<%s> to toggle live plotting'],... + KbName(quitKey), KbName(manualStartKey), KbName(livePlotKey)); running = true; tid = tic; while running @@ -128,6 +132,9 @@ function listen() if firstPress(quitKey) running = false; end + if firstPress(livePlotKey) + tlObj.LivePlot = ~tlObj.LivePlot; + end if firstPress(manualStartKey) && ~tlObj.IsRunning if isempty(tls.AlyxInstance) diff --git a/npy-matlab b/npy-matlab new file mode 160000 index 00000000..524bd143 --- /dev/null +++ b/npy-matlab @@ -0,0 +1 @@ +Subproject commit 524bd143c34cbd9d1bbc895ed05e082ce4249b62 diff --git a/readme.md b/readme.md index 0e01d560..d93aa94e 100644 --- a/readme.md +++ b/readme.md @@ -18,13 +18,12 @@ Rigbox has a number of essential and optional software dependencies, listed belo * Signal Processing Toolbox * Instrument Control Toolbox -Additionally, Rigbox works with a number of extra repositories: +Additionally, Rigbox works with a number of extra submodules (included): * [Signals](https://github.com/dendritic/signals) (for running bespoke experiment designs) * Statistics and Machine Learning Toolbox * [Microsoft Visual C++ Redistributable for Visual Studio 2015](https://www.microsoft.com/en-us/download/details.aspx?id=48145) * [Alyx-matlab](https://github.com/cortex-lab/alyx-matlab) (for registering data to, and retrieving from, an Alyx database - * [Missing HTTP v1](https://github.com/psexton/missing-http/releases/tag/missing-http-1.0.0) or later - * [JSONlab](https://uk.mathworks.com/matlabcentral/fileexchange/33381-jsonlab--a-toolbox-to-encode-decode-json-files) +* [NPY-matlab](https://github.com/kwikteam/npy-matlab) (for saving data in binary NPY format) ## Installing 1. To install Rigbox, first ensure that all the above dependencies are installed. diff --git a/signals b/signals new file mode 160000 index 00000000..c2cf2854 --- /dev/null +++ b/signals @@ -0,0 +1 @@ +Subproject commit c2cf2854491525905ed9f21e0d708b451bfce988 From 8284459ddcc01e8acc416b5df8075134297f1044 Mon Sep 17 00:00:00 2001 From: petersaj <peters.andrew.j@gmail.com> Date: Tue, 17 Apr 2018 13:19:50 +0100 Subject: [PATCH 109/393] Small fixes to addRigboxPaths mostly typos --- addRigboxPaths.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/addRigboxPaths.m b/addRigboxPaths.m index 9c41c4a4..9aa6e0ef 100644 --- a/addRigboxPaths.m +++ b/addRigboxPaths.m @@ -32,8 +32,8 @@ function addRigboxPaths(savePaths) requiredMissing = setdiff(... {'Data Acquisition Toolbox', ... 'Signal Processing Toolbox', ... - 'Instrument Control Toolbox'}, ... - 'Statistics and Machine Learning Toolbox',... + 'Instrument Control Toolbox', ... + 'Statistics and Machine Learning Toolbox'},... {toolboxes.Name}); assert(isempty(requiredMissing),'Rigbox:setup:toolboxRequired',... @@ -90,7 +90,7 @@ function addRigboxPaths(savePaths) % experiments. This submodule is maintained by Chris Burgess. addpath(fullfile(root, 'signals', 'util'), fullfile(root, 'signals', 'mexnet')); % Add the Java paths for signals -jcp = fullfile(fileparts(root, 'signals', 'java')); +jcp = fullfile(root, 'signals', 'java'); if ~any(strcmp(javaclasspath, jcp)); javaaddpath(jcp); end % Add the paths for Alyx-matlab. This submodule allows one to interact From 6e5b5cbfcbf22a0bf3f390b49f57d8331c9f55aa Mon Sep 17 00:00:00 2001 From: Andy Peters <peters.andrew.j@gmail.com> Date: Tue, 17 Apr 2018 13:37:13 +0100 Subject: [PATCH 110/393] moved alyx-matlab to alyx-as-class --- alyx-matlab | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alyx-matlab b/alyx-matlab index 94334e93..901a90fa 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit 94334e938c52f225dc0dcd78ea670205c9cf603f +Subproject commit 901a90fad903b15ed77574374e93d3f48b487681 From c6ca41ca7ff4e759a13ed5c5486c0f8ba76779c8 Mon Sep 17 00:00:00 2001 From: Andy Peters <peters.andrew.j@gmail.com> Date: Tue, 17 Apr 2018 13:42:58 +0100 Subject: [PATCH 111/393] fixed alyx-matlab helpers path in addRigboxPaths --- addRigboxPaths.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addRigboxPaths.m b/addRigboxPaths.m index 9aa6e0ef..f740afc5 100644 --- a/addRigboxPaths.m +++ b/addRigboxPaths.m @@ -96,7 +96,7 @@ function addRigboxPaths(savePaths) % Add the paths for Alyx-matlab. This submodule allows one to interact % with an instance of an Alyx database. For more information please visit: % http://alyx.readthedocs.io/en/latest/ -addpath(fullfile(root, 'alyx-matlab'), fullfile(root, 'helpers')); +addpath(fullfile(root, 'alyx-matlab'), fullfile(root, 'alyx-matlab', 'helpers')); % Add paths for the npy-matlab. This submodule is maintained by the % Kwik Team (https://github.com/kwikteam). It allows for the saving of From cb583ba0974340f7a5b74878b89593f049209323 Mon Sep 17 00:00:00 2001 From: petersaj <peters.andrew.j@gmail.com> Date: Tue, 17 Apr 2018 13:53:01 +0100 Subject: [PATCH 112/393] Forgot to add signals root directory in last commit --- addRigboxPaths.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/addRigboxPaths.m b/addRigboxPaths.m index f740afc5..d9f255e8 100644 --- a/addRigboxPaths.m +++ b/addRigboxPaths.m @@ -88,7 +88,9 @@ function addRigboxPaths(savePaths) % Add signals paths, this includes all the core code for running signals % experiments. This submodule is maintained by Chris Burgess. -addpath(fullfile(root, 'signals', 'util'), fullfile(root, 'signals', 'mexnet')); +addpath(fullfile(root, 'signals'),... + fullfile(root, 'signals', 'mexnet'),... + fullfile(root, 'signals', 'util')); % Add the Java paths for signals jcp = fullfile(root, 'signals', 'java'); if ~any(strcmp(javaclasspath, jcp)); javaaddpath(jcp); end From b32b6155ea50e7a9de6e92a10fa4390b64e666e0 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 17 Apr 2018 16:37:58 +0100 Subject: [PATCH 113/393] catStructs no longer necessary All data returned as structures now --- +eui/AlyxPanel.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index 9aa5ee11..b64a34c4 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -323,7 +323,7 @@ function dispWaterReq(obj, src, ~) % Refresh the timer as the user isn't inactive stop(obj.LoginTimer); start(obj.LoginTimer) try - s = catStructs(ai.getData('water-restricted-subjects')); % struct with data about restricted subjects + s = ai.getData('water-restricted-subjects'); % struct with data about restricted subjects idx = strcmp(obj.Subject, {s.nickname}); if ~any(idx) % Subject not on water restriction set(obj.WaterRequiredText, 'ForegroundColor', 'black',... @@ -334,7 +334,7 @@ function dispWaterReq(obj, src, ~) obj.Subject, datestr(now, 'yyyy-mm-dd'),datestr(now, 'yyyy-mm-dd')); wr = ai.getData(endpnt); % Get today's weight and water record if ~isempty(wr.records) - record = wr.records{end}; + record = wr.records(end); else record = struct(); end @@ -362,7 +362,7 @@ function dispWaterReq(obj, src, ~) obj.WaterRemaining = s(idx).water_requirement_remaining; end catch me - d = loadjson(me.message); + d = me.message; %FIXME: JSON no longer returned if isfield(d, 'detail') && strcmp(d.detail, 'Not found.') set(obj.WaterRequiredText, 'ForegroundColor', 'black',... 'String', sprintf('Subject %s not found in alyx', obj.Subject)); From 6a828646b56fd77652c89259c26774c8be7badc0 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 17 Apr 2018 19:05:39 +0100 Subject: [PATCH 114/393] Timeline uses new endpoint hw.Timeline now registers using the new registerFile method --- +hw/Timeline.m | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/+hw/Timeline.m b/+hw/Timeline.m index f0ae52b1..51201172 100644 --- a/+hw/Timeline.m +++ b/+hw/Timeline.m @@ -467,11 +467,10 @@ function stop(obj) end % register Timeline.mat file to Alyx database - [subject, ~, ~] = dat.parseExpRef(obj.Data.expRef); + subject = dat.parseExpRef(obj.Data.expRef); if ~isempty(obj.AlyxInstance) && obj.AlyxInstance.IsLoggedIn && ~strcmp(subject,'default') try - obj.AlyxInstance.registerFile(obj.Data.savePaths{end}, 'mat',... - obj.AlyxInstance.SessionURL, 'Timeline', []); + obj.AlyxInstance.registerFile(obj.Data.savePaths{2}); catch ex warning(ex.identifier, 'couldn''t register files to Alyx: %s', ex.message); end From aed274c885a35753a97deeef617047a84f9f9124 Mon Sep 17 00:00:00 2001 From: Julie <northerlywind@users.noreply.github.com> Date: Tue, 17 Apr 2018 19:17:55 +0100 Subject: [PATCH 115/393] Added specific branch for alyx-matlab submodule --- .gitmodules | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitmodules b/.gitmodules index 4b73c981..a143960b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,7 @@ [submodule "alyx-matlab"] path = alyx-matlab url = https://github.com/cortex-lab/alyx-matlab/ + branch = alyx-as-class [submodule "signals"] path = signals url = https://github.com/dendritic/signals From 1cacfd15462e00586a5794b0828b576c725859e5 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 17 Apr 2018 20:29:02 +0100 Subject: [PATCH 116/393] Added registration of all timeline related files using new register-file endpoint --- +hw/Timeline.m | 9 ++++++--- alyx-matlab | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/+hw/Timeline.m b/+hw/Timeline.m index 51201172..2467e99a 100644 --- a/+hw/Timeline.m +++ b/+hw/Timeline.m @@ -466,16 +466,19 @@ function stop(obj) warning('did not write files into alf format. Check that alyx-matlab and npy-matlab repositories are in path'); end - % register Timeline.mat file to Alyx database + %Register ALF components and hardware structures to Alyx + %database. TODO: Make this process more robust. subject = dat.parseExpRef(obj.Data.expRef); if ~isempty(obj.AlyxInstance) && obj.AlyxInstance.IsLoggedIn && ~strcmp(subject,'default') try - obj.AlyxInstance.registerFile(obj.Data.savePaths{2}); + files = dir(fileparts(obj.Data.savePaths{2})); + files = fullfile(files(1).folder, {files(endsWith({files.name},... + {'HW.json', '.raw.npy', '_Timeline.npy'})).name}); + obj.AlyxInstance.registerFile([obj.Data.savePaths{2} files]); catch ex warning(ex.identifier, 'couldn''t register files to Alyx: %s', ex.message); end end - %TODO: Register ALF components to alyx, incl TimelineHW.json % delete data from memory, tl is now officially no longer running obj.Data = []; diff --git a/alyx-matlab b/alyx-matlab index 901a90fa..3e96903d 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit 901a90fad903b15ed77574374e93d3f48b487681 +Subproject commit 3e96903d3a9bdddc1688c45e08a96e91adb26e06 From 3d66542ebaf5132501b475511866303f0f3bb8eb Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 20 Apr 2018 13:18:22 +0100 Subject: [PATCH 117/393] Added better handling of headless mode when Alyx is down --- +eui/AlyxPanel.m | 12 +++++++++++- +eui/MControl.m | 6 ++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index b64a34c4..0a203c2c 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -214,6 +214,8 @@ function login(obj) % Logging out does not cause the token to expire, instead the % token is simply deleted from this object. + % Reset headless flag in case user wishes to retry connection + obj.AlyxInstance.Headless = false; % Are we logging in or out? if ~obj.AlyxInstance.IsLoggedIn % logging in % attempt login @@ -227,7 +229,8 @@ function login(obj) start(obj.LoginTimer) % Enable all buttons set(findall(obj.RootContainer, '-property', 'Enable'), 'Enable', 'on'); - set(obj.LoginText, 'String', ['You are logged in as ', obj.AlyxInstance.User]); % display which user is logged in + set(obj.LoginText, 'ForegroundColor', 'black',... + 'String', ['You are logged in as ', obj.AlyxInstance.User]); % display which user is logged in set(obj.LoginButton, 'String', 'Logout'); % try updating the subject selectors in other panels @@ -248,6 +251,13 @@ function login(obj) mkdir(thisDir); end end + elseif obj.AlyxInstance.Headless + % Panel inactive or login failed due to Alyx being down + set(findall(obj.RootContainer, '-property', 'Enable'), 'Enable', 'on'); + set(obj.LoginText, 'ForegroundColor', [0.91, 0.41, 0.17],... + 'String', 'Unable to reach Alyx, posts to be queued'); + set(obj.LoginButton, 'String', 'Retry'); % Retry button + obj.log('Failed to reach Alyx server, please retry later'); else obj.log('Did not log into Alyx'); end diff --git a/+eui/MControl.m b/+eui/MControl.m index c7ab5901..8635f7b4 100644 --- a/+eui/MControl.m +++ b/+eui/MControl.m @@ -543,10 +543,12 @@ function beginExp(obj) set([obj.BeginExpButton obj.RigOptionsButton], 'Enable', 'off'); % Grey out buttons rig = obj.RemoteRigs.Selected; % Find which rig is selected % Save the current instance of Alyx so that eui.ExpPanel can register water to the correct account - if ~obj.AlyxPanel.AlyxInstance.IsLoggedIn && ~strcmp(obj.NewExpSubject.Selected,'default') + if ~obj.AlyxPanel.AlyxInstance.IsLoggedIn &&... + ~strcmp(obj.NewExpSubject.Selected,'default') &&... + ~obj.AlyxPanel.AlyxInstance.Headless try obj.AlyxPanel.login(); - assert(obj.AlyxPanel.AlyxInstance.IsLoggedIn); + assert(obj.AlyxPanel.AlyxInstance.IsLoggedIn||obj.AlyxPanel.AlyxInstance.Headless); catch obj.log('Warning: Must be logged in to Alyx before running an experiment') return From fbc92037c5b3a880e4adb8f34e086621256fd5fb Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Sat, 21 Apr 2018 20:54:30 +0100 Subject: [PATCH 118/393] Added helper function to remove empty elements rmEmpty simply removes all empty elements of the input array and returns it --- cb-tools/burgbox/rmEmpty.m | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 cb-tools/burgbox/rmEmpty.m diff --git a/cb-tools/burgbox/rmEmpty.m b/cb-tools/burgbox/rmEmpty.m new file mode 100644 index 00000000..82220d5e --- /dev/null +++ b/cb-tools/burgbox/rmEmpty.m @@ -0,0 +1,15 @@ +function passed = rmEmpty(A) +%RMEMPTY Returns input array with empty elements removed +% Simply removes all empty elements of the input array and returns it +% +% See also FUN.EMPTYSEQ, EMPTYELEMS, FUN.FILTER +% 2018 MW created + + +if iscell(A) + empty = cellfun('isempty', A); +else + empty = arrayfun(@isempty, A); +end + +passed = A(~empty); \ No newline at end of file From 974125ffa948eb0fd19bcf26f42c865a8ad799fe Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Mon, 23 Apr 2018 13:56:00 +0100 Subject: [PATCH 119/393] Updated readme --- .gitmodules | 1 - readme.md | 19 ++++++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index a143960b..4b73c981 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,7 +1,6 @@ [submodule "alyx-matlab"] path = alyx-matlab url = https://github.com/cortex-lab/alyx-matlab/ - branch = alyx-as-class [submodule "signals"] path = signals url = https://github.com/dendritic/signals diff --git a/readme.md b/readme.md index d93aa94e..b6632e3c 100644 --- a/readme.md +++ b/readme.md @@ -26,12 +26,25 @@ Additionally, Rigbox works with a number of extra submodules (included): * [NPY-matlab](https://github.com/kwikteam/npy-matlab) (for saving data in binary NPY format) ## Installing -1. To install Rigbox, first ensure that all the above dependencies are installed. +1. To install Rigbox, clone the repository in git. It is *not* recommended to clone directly into the MATLAB folder +```git clone https://github.com/cortex-lab/Rigbox.git``` 2. Pull the latest Rigbox-lite branch. This branch is currently the 'cleanest' one, however in the future it will likely be merged with the master branch. +``` +cd Rigbox/ +git checkout rigbox-lite +``` +3. Run the following to clone the submodules: +```git submodules update --init``` 3. In MATLAB run 'addRigboxPaths.m' and restart the program. 4. Set the correct paths by following the instructions in Rigbox\+dat\paths.m on both computers. 5. On the stimulus server, load the hardware.mat file in Rigbox\Repositories\code\config\exampleRig and edit according to your specific hardware setup (link to detailed instructions above, under 'Getting started'). +To keep up to date, run the following: +``` +git pull +git submodules update --remote +``` + ## Running an experiment On the stimulus server, run: @@ -76,6 +89,7 @@ NB: Lower-level communication protocol code is found in the +io package ## cb-tools\burgbox Burgbox contains many simply helper functions that are used by the main packages. Within this directory are further packages: + * +bui --- Classes for managing graphics objects such as axes * +aud --- Functions for interacting with PsychoPortAudio * +file --- Functions for simplifying directory and file management, for instance returning the modified dates for specified folders or filtering an array of directories by those that exist @@ -89,5 +103,8 @@ Burgbox contains many simply helper functions that are used by the main packages ## cortexlab The cortexlab directory is intended for functions and classes that are rig or lab specific, for instance code that allows compatibility with other stimulus presentation packages used by cortexlab (i.e. MPEP) +## alyx-matlab/@Alyx +This class allows interation with an instance of the Alyx database. More information about Alyx can be found [here](http://alyx.readthedocs.io/en/latest/). Information about using the alyx-matlab class can be found in [alyx-matlab/Examples.m](https://github.com/cortex-lab/alyx-matlab/blob/alyx-as-class/Examples.m). + ## Authors The majority of the Rigbox code was written by [Chris Burgess](https://github.com/dendritic/) in 2013. It is now maintained and developed by a number of people at [CortexLab](https://www.ucl.ac.uk/cortexlab). From 69de4c04ea15cbf1cf96890b1935485a14c9c9d6 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 24 Apr 2018 13:14:02 +0100 Subject: [PATCH 120/393] Reverted changes to dat.newExp. Was in a half-way house due to merge. Now contains no alyx info --- +dat/newExp.m | 57 +-------------------------------------------------- 1 file changed, 1 insertion(+), 56 deletions(-) diff --git a/+dat/newExp.m b/+dat/newExp.m index f0e8ee39..007aa566 100644 --- a/+dat/newExp.m +++ b/+dat/newExp.m @@ -58,53 +58,6 @@ % now make the folder(s) to hold the new experiment assert(all(cellfun(@(p) mkdir(p), expPath)), 'Creating experiment directories failed'); -if ~strcmp(subject, 'default') % Ignore fake subject - % if the Alyx Instance is set, find or create BASE session - expDate = alyx.datestr(expDate); % date in Alyx format - % Get list of base sessions - sessions = alyx.getData(AlyxInstance,... - ['sessions?type=Base&subject=' subject]); - - %If the date of this latest base session is not the same date as - %today, then create a new base session for today - if isempty(sessions) || ~strcmp(sessions{end}.start_time(1:10), expDate(1:10)) - d = struct; - d.subject = subject; - d.procedures = {'Behavior training/tasks'}; - d.narrative = 'auto-generated session'; - d.start_time = expDate; - d.type = 'Base'; - % d.users = {AlyxInstance.username}; - - base_submit = alyx.postData(AlyxInstance, 'sessions', d); - assert(isfield(base_submit,'subject'),... - 'Submitted base session did not return appropriate values'); - - %Now retrieve the sessions again - sessions = alyx.getData(AlyxInstance,... - ['sessions?type=Base&subject=' subject]); - end - latest_base = sessions{end}; - - %Now create a new SUBSESSION, using the same experiment number - d = struct; - d.subject = subject; - d.procedures = {'Behavior training/tasks'}; - d.narrative = 'auto-generated session'; - d.start_time = expDate; - d.type = 'Experiment'; - d.parent_session = latest_base.url; - d.number = expSeq; - % d.users = {AlyxInstance.username}; - - subsession = alyx.postData(AlyxInstance, 'sessions', d); - assert(isfield(subsession,'subject'),... - 'Failed to create new sub-session in Alyx for %s', subject); - url = subsession.url; -else - url = []; -end - % if the parameters had an experiment definition function, save a copy in % the experiment's folder if isfield(expParams, 'defFunction') @@ -131,16 +84,8 @@ [expRef, '_parameters.json']); savejson('parameters', expParams, jsonPath); % Register our JSON parameter set to Alyx - if ~strcmp(subject, 'default') - alyx.registerFile(jsonPath, 'json', url, 'Parameters', [], AlyxInstance); - end catch ex - warning(ex.identifier, 'Failed to save paramters as JSON: %s.\n Registering mat file instead', ex.message) - % Register our parameter set to Alyx - if ~strcmp(subject, 'default') - alyx.registerFile(dat.expFilePath(expRef, 'parameters', 'master'), 'mat',... - url, 'Parameters', [], AlyxInstance); - end + warning(ex.identifier, 'Failed to save paramters as JSON: %s.', ex.message) end end end \ No newline at end of file From af0b25cd4017cac6d348fae168dc9ce5f5b8b70a Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 25 Apr 2018 11:47:28 +0100 Subject: [PATCH 121/393] Added Apache 2.0 licence --- LICENCE | 202 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100644 LICENCE diff --git a/LICENCE b/LICENCE new file mode 100644 index 00000000..e06d2081 --- /dev/null +++ b/LICENCE @@ -0,0 +1,202 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + From 978d9a21d7e8caa40279ba30265b603134a025b8 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 25 Apr 2018 12:15:25 +0100 Subject: [PATCH 122/393] Changed to fork of signals for testing new changes --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 4b73c981..c7ef4a47 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,7 +3,7 @@ url = https://github.com/cortex-lab/alyx-matlab/ [submodule "signals"] path = signals - url = https://github.com/dendritic/signals + url = https://github.com/cortex-lab/signals [submodule "npy-matlab"] path = npy-matlab url = https://github.com/kwikteam/npy-matlab From 3768be96b7276e86fddb62ff8ae242145ff071ac Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 25 Apr 2018 18:29:50 +0100 Subject: [PATCH 123/393] Fix for issues with audio device conflict. Audio device now opened in configuresChoiceWorld instead of hw.devices. Signals now able to output samples to named audio devices. --- +exp/Experiment.m | 14 +++++++------- +exp/SignalsExp.m | 7 ++----- +hw/devices.m | 14 +++++++------- cortexlab/+exp/configureChoiceExperiment.m | 7 +++++++ 4 files changed, 23 insertions(+), 19 deletions(-) diff --git a/+exp/Experiment.m b/+exp/Experiment.m index acdd7f9f..d78cdef5 100644 --- a/+exp/Experiment.m +++ b/+exp/Experiment.m @@ -556,6 +556,9 @@ function cleanup(obj) % destroy video texures created during intialisation deleteTextures(obj.StimWindow); + + % close audio + aud.close(obj.Audio); end function mainLoop(obj) @@ -777,17 +780,14 @@ function saveData(obj) warning('No Alyx token set'); else try - [subject, expDate, seq] = dat.parseExpRef(obj.Data.expRef); + subject = dat.parseExpRef(obj.Data.expRef); if strcmp(subject, 'default'); return; end % Register saved files - obj.AlyxInstance.registerFile(savepaths{end}, 'mat',... - obj.AlyxInstance.SessionURL, 'Block', []); -% obj.AlyxInstance.registerFile(savepaths{end}, 'mat',... -% {subject, expDate, seq}, 'Block', []); + obj.AlyxInstance.registerFile(savepaths{end}); % Save the session end time if ~isempty(obj.AlyxInstance.SessionURL) - obj.AlyxInstance.putData(obj.AlyxInstance.SessionURL,... - struct('end_time', obj.AlyxInstance.datestr(now), 'subject', subject)); + obj.AlyxInstance.postData(obj.AlyxInstance.SessionURL,... + struct('end_time', obj.AlyxInstance.datestr(now), 'subject', subject), 'put'); else % Infer from date session and retrieve using expFilePath end diff --git a/+exp/SignalsExp.m b/+exp/SignalsExp.m index d6f86b99..4a019e22 100644 --- a/+exp/SignalsExp.m +++ b/+exp/SignalsExp.m @@ -143,10 +143,7 @@ obj.Inputs = sig.Registry(clockFun); obj.Outputs = sig.Registry(clockFun); obj.Visual = StructRef; - nAudChannels = getOr(paramStruct, 'numAudChannels', rig.audioDevice.NrOutputChannels); - audSampleRate = getOr(paramStruct, 'audSampleRate', rig.audioDevice.DefaultSampleRate); % Hz - audDevIdx = getOr(paramStruct, 'audDevIdx', rig.audioDevice.DeviceIndex); % -1 means use system default - obj.Audio = audstream.Registry(audSampleRate, nAudChannels, audDevIdx); + obj.Audio = audstream.Registry(rig.audioDevices); obj.Events = sig.Registry(clockFun); %% configure signals net = sig.Net; @@ -942,7 +939,7 @@ function saveData(obj) warning('No Alyx token set'); else try - [subject, seq] = dat.parseExpRef(obj.Data.expRef); + subject = dat.parseExpRef(obj.Data.expRef); if strcmp(subject, 'default'); return; end % Register saved files obj.AlyxInstance.registerFile(savepaths{end}); diff --git a/+hw/devices.m b/+hw/devices.m index 4a1a4616..2721566a 100644 --- a/+hw/devices.m +++ b/+hw/devices.m @@ -59,13 +59,13 @@ InitializePsychSound; IsPsychSoundInitialize = true; end - idx = pick(rig, 'audioDevice', 'def', 0); - rig.audioDevice = PsychPortAudio('GetDevices', [], idx); - % setup playback audio device - no configurable settings for now - % 96kHz sampling rate, 2 channels, try to very low audio latency - rig.audio = aud.open(rig.audioDevice.DeviceIndex,... - rig.audioDevice.NrOutputChannels,... - rig.audioDevice.DefaultSampleRate, 1); + % Get list of audio devices + devs = getOr(rig, 'audioDevices', PsychPortAudio('GetDevices')); + % Sanitize the names + names = matlab.lang.makeValidName([{'default'} {devs(2:end).DeviceName}],... + 'ReplacementStyle', 'delete'); + for i = 1:length(names); devs(i).DeviceName = names{i}; end + rig.audioDevices = devs; end rig.paths = paths; diff --git a/cortexlab/+exp/configureChoiceExperiment.m b/cortexlab/+exp/configureChoiceExperiment.m index 66ea611c..446573b2 100644 --- a/cortexlab/+exp/configureChoiceExperiment.m +++ b/cortexlab/+exp/configureChoiceExperiment.m @@ -17,6 +17,13 @@ params.Struct = paramStruct; %% Generate audio samples at device sample rate +% setup playback audio device - no configurable settings for now +% 96kHz sampling rate, 2 channels, try to very low audio latency +dev = rig.audioDevices(strcmp('default', {rig.audioDevices.DeviceName})); +rig.audio = aud.open(dev.DeviceIndex,... +dev.NrOutputChannels,... +dev.DefaultSampleRate, 1); + %Sound samples are wrapped in a cell for storing to a parameter %(to ensure they're used as one global parameter) audSampleRate = aud.rate(rig.audio); From 3ca7719ad35f5d9ed93baaacc49d004fcec1f41d Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 25 Apr 2018 18:33:53 +0100 Subject: [PATCH 124/393] Updated signals submodule to allow putting of audio samples to a named device --- signals | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/signals b/signals index c2cf2854..c3a09df7 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit c2cf2854491525905ed9f21e0d708b451bfce988 +Subproject commit c3a09df7875ed4eff9d130cb69db6f446e0eb877 From 0afd6b912858257ecb52194473550e5cd47721a1 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 26 Apr 2018 10:42:36 +0100 Subject: [PATCH 125/393] Updated alyx-matlab to use regex to find relative path in registerFile --- alyx-matlab | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alyx-matlab b/alyx-matlab index 3e96903d..577a6d02 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit 3e96903d3a9bdddc1688c45e08a96e91adb26e06 +Subproject commit 577a6d02fb30e8e063a7609950e2b224e5ea0b39 From 442842eb1c03431f1e22a31999aaefed5beafcda Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 26 Apr 2018 12:56:43 +0100 Subject: [PATCH 126/393] Fix for errors when inferring signals params when audio device info is used in the expDef --- +exp/inferParameters.m | 16 +++++++++++----- +hw/devices.m | 3 +-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/+exp/inferParameters.m b/+exp/inferParameters.m index 51c57a01..6175fd3f 100644 --- a/+exp/inferParameters.m +++ b/+exp/inferParameters.m @@ -19,14 +19,12 @@ e.pars = net.subscriptableOrigin('pars'); e.pars.CacheSubscripts = true; e.visual = net.subscriptableOrigin('visual'); -e.audio = net.subscriptableOrigin('audio'); -e.audio.SampleRate = 44100; -e.audio.NChannels = 2; +e.audio.Devices = @dummyDev; e.inputs = net.subscriptableOrigin('inputs'); e.outputs = net.subscriptableOrigin('outputs'); try - expdeffun(e.t, e.events, e.pars, e.visual, e.inputs , e.outputs); + expdeffun(e.t, e.events, e.pars, e.visual, e.inputs , e.outputs, e.audio); % paramNames will be the strings corresponding to the fields of e.pars % that the user tried to reference in her expdeffun. paramNames = e.pars.Subscripts.keys'; @@ -60,5 +58,13 @@ net.delete(); - + function dev = dummyDev(~) + % Returns a dummy audio device structure, regardless of input + % Returns a standard structure with values for generating tone + % samples. This function gets around the problem of querying the + % rig's audio devices when inferring parameters. + dev = struct('DeviceIndex', -1,... + 'DefaultSampleRate', 44100,... + 'NrOutputChannels', 2); + end end \ No newline at end of file diff --git a/+hw/devices.m b/+hw/devices.m index 2721566a..24e532cf 100644 --- a/+hw/devices.m +++ b/+hw/devices.m @@ -86,5 +86,4 @@ function configure(deviceName, usedaq) end end -end - +end \ No newline at end of file From 0face35e6d413475a2aeb3bcedac58c28757fb6d Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 26 Apr 2018 14:13:00 +0100 Subject: [PATCH 127/393] updated signals repo --- signals | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/signals b/signals index c3a09df7..c879c89d 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit c3a09df7875ed4eff9d130cb69db6f446e0eb877 +Subproject commit c879c89d9b267370b2c6cb061e8c8afaf48f4474 From 590c17bb707a06b008468a1881e2ae90b61018ec Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 4 May 2018 13:44:19 +0100 Subject: [PATCH 128/393] Added rig hardware save in JSON for each experiment. Also the sessions property in Timeline is now availiable to output objects without seperate method. Also removed expInfo remository and replaced it with 'main' --- +dat/delParamProfile.m | 4 ++-- +dat/expExists.m | 2 +- +dat/expFilePath.m | 25 ++++--------------------- +dat/expPath.m | 4 ++-- +dat/listExps.m | 6 +++--- +dat/listSubjects.m | 8 ++++---- +dat/loadParamProfiles.m | 2 +- +dat/logPath.m | 4 ++-- +dat/newExp.m | 4 ++-- +dat/paths.m | 29 ++++++++--------------------- +dat/reposPath.m | 8 ++++---- +dat/saveParamProfile.m | 4 ++-- +dat/subjectExists.m | 2 +- +eui/AlyxPanel.m | 6 +++--- +hw/PositionSensor.m | 5 ++++- +hw/TLOutputChrono.m | 2 +- +hw/Timeline.m | 17 +++++------------ +hw/devices.m | 2 +- +srv/expServer.m | 5 +++++ +srv/prepareExp.m | 13 ------------- alyx-matlab | 2 +- cb-tools/burgbox/mergeStructs.m | 6 ++++++ npy-matlab | 2 +- 23 files changed, 63 insertions(+), 99 deletions(-) diff --git a/+dat/delParamProfile.m b/+dat/delParamProfile.m index d67b4d24..ee46eeb6 100644 --- a/+dat/delParamProfile.m +++ b/+dat/delParamProfile.m @@ -8,14 +8,14 @@ function delParamProfile(expType, profileName) %path to repositories fn = 'parameterProfiles.mat'; -repos = fullfile(dat.reposPath('expInfo'), fn); +repos = fullfile(dat.reposPath('main'), fn); %load existing profiles for specified expType profiles = dat.loadParamProfiles(expType); %remove the params with the field named by profile profiles = rmfield(profiles, profileName); %wrap in a struct for saving -set.(expType) = profiles; +set.(expType) = profiles; %#ok<STRNU> %save the updated set of profiles to each repos %where files exist already, append diff --git a/+dat/expExists.m b/+dat/expExists.m index 88ea312e..82924523 100644 --- a/+dat/expExists.m +++ b/+dat/expExists.m @@ -17,7 +17,7 @@ function b = check(expRef) % ensure the standard folder given the reference exists - b = file.exists(dat.expPath(expRef, 'expInfo', 'master')); + b = file.exists(dat.expPath(expRef, 'main', 'master')); end end \ No newline at end of file diff --git a/+dat/expFilePath.m b/+dat/expFilePath.m index b2695823..b9053047 100644 --- a/+dat/expFilePath.m +++ b/+dat/expFilePath.m @@ -44,65 +44,48 @@ function [repos, suff, dateLevel] = typeInfo(type) % whether this repository is at the date level or otherwise deeper at the sequence - % level (default) + % level (default). FIXME: Date level doesn't work, perhaps this should + % be modified to work with deeper sequences also? E.g. + % default\2018-05-04\1\2 dateLevel = false; + repos = 'main'; switch lower(type) case 'block' % MAT-file with info about each set of trials - repos = 'expInfo'; suff = '_Block.mat'; case 'hw-info' % MAT-file with info about the hardware used for an experiment - repos = 'expInfo'; suff = '_hardwareInfo.mat'; case '2p-raw' % TIFF with 2-photon raw fluorescence movies - repos = 'twoPhoton'; suff = '_2P.tif'; case 'calcium-preview' - repos = 'twoPhoton'; suff = '_2P_CalciumPreview.tif'; case 'calcium-reg' - repos = 'twoPhoton'; suff = '_2P_CalciumReg'; case 'calcium-regframe' - repos = 'twoPhoton'; suff = '_2P_CalciumRegFrame.tif'; case 'timeline' % MAT-file with acquired timing information - repos = 'expInfo'; suff = '_Timeline.mat'; case 'calcium-roi' - repos = 'twoPhoton'; suff = '_ROI.mat'; case 'calcium-fc' % minimally filtered fractional change frames - repos = 'twoPhoton'; suff = '_2P_CalciumFC'; case 'calcium-ffc' % ROI filtered fractional change frames - repos = 'twoPhoton'; suff = '_2P_CalciumFFC'; case 'calcium-widefield-svd' - repos = 'widefield'; suff = '_SVD'; case 'eyetracking' - repos = 'eyeTracking'; suff = '_eye'; case 'parameters' % MAT-file with parameters used for experiment - repos = 'expInfo'; suff = '_parameters.mat'; case 'lasermanip' - repos = 'expInfo'; suff = '_laserManip.mat'; case 'img-info' - repos = 'twoPhoton'; suff = '_imgInfo.mat'; case 'tmaze' - repos = 'expInfo'; suff = '_TMaze.mat'; case 'expdeffun' - repos = 'expInfo'; suff = '_expDef.m'; case 'svdspatialcomps' dateLevel = true; - % expPath = mapToCell(@fileparts, expPath); - % repos = 'expInfo'; - % suff = '_expDef.m'; otherwise error('"%s" is not a valid file type', type); end diff --git a/+dat/expPath.m b/+dat/expPath.m index b9b1abf6..abcc8294 100644 --- a/+dat/expPath.m +++ b/+dat/expPath.m @@ -9,10 +9,10 @@ % sames as the above, but returns paths for an experiment with a % specified 'subject', on a particular 'date', and numbered 'seq'. % -% e.g. to get the paths for the 'expInfo' repository, for the first +% e.g. to get the paths for the 'main' repository, for the first % experiment of the day for 'SUBJECTA': % -% paths = DAT.EXPPATH('SUBJECTA', now, 1, 'expInfo'); +% paths = DAT.EXPPATH('SUBJECTA', now, 1, 'main'); % % Part of Rigbox diff --git a/+dat/listExps.m b/+dat/listExps.m index c29506a3..b44aa8dc 100644 --- a/+dat/listExps.m +++ b/+dat/listExps.m @@ -7,15 +7,15 @@ % 2013-03 CB created -% The master 'expInfo' repository is the reference for the existence of +% The master 'main' repository is the reference for the existence of % experiments, as given by the folder structure -expInfoPath = dat.reposPath('expInfo', 'master'); +mainPath = dat.reposPath('main', 'master'); function [expRef, expDate, expSeq] = subjectExps(subject) % finds experiments for individual subjects % experiment dates correpsond to date formated folders in subject's % folder - subjectPath = fullfile(expInfoPath, subject); + subjectPath = fullfile(mainPath, subject); subjectDirs = file.list(subjectPath, 'dirs'); dateRegExp = '^(?<year>\d\d\d\d)\-?(?<month>\d\d)\-?(?<day>\d\d)$'; dateMatch = regexp(subjectDirs, dateRegExp, 'names'); diff --git a/+dat/listSubjects.m b/+dat/listSubjects.m index cca333d7..4147b013 100644 --- a/+dat/listSubjects.m +++ b/+dat/listSubjects.m @@ -1,7 +1,7 @@ function subjects = listSubjects(varargin) %DAT.LISTSUBJECTS Lists recorded subjects % subjects = DAT.LISTSUBJECTS([alyxInstance]) Lists the experimental subjects present -% in experiment info repository ('expInfo'). +% in experiment info repository ('main'). % % Optional input argument of an alyx instance will enable generating this % list from alyx rather than from the directory structure on zserver @@ -33,11 +33,11 @@ subjects = [{'default'}, thisUserSubs, otherUserSubs]'; else - % The master 'expInfo' repository is the reference for the existence of + % The master 'main' repository is the reference for the existence of % experiments, as given by the folder structure - expInfoPath = dat.reposPath('expInfo', 'master'); + mainPath = dat.reposPath('main', 'master'); - dirs = file.list(expInfoPath, 'dirs'); + dirs = file.list(mainPath, 'dirs'); subjects = dirs(~cellfun(@(d)startsWith(d, '@'), dirs)); % exclude misc directories end end \ No newline at end of file diff --git a/+dat/loadParamProfiles.m b/+dat/loadParamProfiles.m index ee0b4c61..2c82c14a 100644 --- a/+dat/loadParamProfiles.m +++ b/+dat/loadParamProfiles.m @@ -8,7 +8,7 @@ % 2017-02 MW Param struct now sorted in ASCII dictionary order fn = 'parameterProfiles.mat'; -masterPath = fullfile(dat.reposPath('expInfo', 'master'), fn); +masterPath = fullfile(dat.reposPath('main', 'master'), fn); p = struct; %default is to return an empty struct diff --git a/+dat/logPath.m b/+dat/logPath.m index 7141129b..8dc80527 100644 --- a/+dat/logPath.m +++ b/+dat/logPath.m @@ -10,8 +10,8 @@ %ensure the subject exists assert(dat.subjectExists(subject), 'Subject "%s" does not exist', subject); -% get path(s) to expInfo repository -reposPath = dat.reposPath('expInfo', varargin{:}); +% get path(s) to main repository +reposPath = dat.reposPath('main', varargin{:}); filename = sprintf('%s_log.mat', subject); subjectPath = file.mkPath(reposPath, subject); diff --git a/+dat/newExp.m b/+dat/newExp.m index 007aa566..0ff092b8 100644 --- a/+dat/newExp.m +++ b/+dat/newExp.m @@ -49,8 +49,8 @@ expSeq = 1; end -% expInfo repository is the reference location for which experiments exist -[expPath, expRef] = dat.expPath(subject, floor(expDate), expSeq, 'expInfo'); +% main repository is the reference location for which experiments exist +[expPath, expRef] = dat.expPath(subject, floor(expDate), expSeq, 'main'); % ensure nothing went wrong in making a "unique" ref and path to hold assert(~any(file.exists(expPath)), ... sprintf('Something went wrong as experiment folders already exist for "%s".', expRef)); diff --git a/+dat/paths.m b/+dat/paths.m index e95284dc..1dee5d35 100644 --- a/+dat/paths.m +++ b/+dat/paths.m @@ -14,10 +14,6 @@ end server1Name = '\\zubjects.cortexlab.net'; -% server2Name = '\\zserver2.cortexlab.net'; -% server3Name = '\\zserver3.cortexlab.net'; % 2017-02-18 MW - Currently -% unused by Rigbox -server4Name = '\\zserver4.cortexlab.net'; basketName = '\\basket.cortexlab.net'; % for working analyses lugaroName = '\\lugaro.cortexlab.net'; % for tape backup @@ -27,23 +23,11 @@ p.rigbox = fileparts(which('addRigboxPaths')); % Repository for local copy of everything generated on this rig p.localRepository = 'C:\LocalExpData'; -% for all data types, under the new system of having data grouped by mouse -% rather than data type -p.mainRepository = fullfile(server1Name, 'Subjects'); -% Repository for info about experiments, i.e. stimulus, behavioural, -% Timeline etc -p.expInfoRepository = p.mainRepository; -% Repository for storing two photon movies -p.twoPhotonRepository = p.mainRepository; -% for calcium widefield imaging -p.widefieldRepository = fullfile(server1Name, 'data', 'GCAMP'); -% Repository for storing eye tracking movies -p.eyeTrackingRepository = p.mainRepository; +% Under the new system of having data grouped by mouse +% rather than data type, all experimental data are saved here. +p.mainRepository = fullfile(server1Name, 'Subjects'); -% electrophys repositories -p.lfpRepository = fullfile(server1Name, 'Data', 'Cerebus'); -p.spikesRepository = fullfile(server1Name, 'Data', 'multichanspikes'); % directory for organisation-wide configuration files, for now these should % all remain on zserver % p.globalConfig = fullfile(p.rigbox, 'config'); @@ -64,7 +48,6 @@ p.tapeArchiveRepository = fullfile(lugaroName, 'bigdrive', 'toarchive'); - %% load rig-specific overrides from config file, if any customPathsFile = fullfile(p.rigConfig, 'paths.mat'); if file.exists(customPathsFile) @@ -73,9 +56,13 @@ % 'centralRepository' is deprecated, remove field, if any customPaths = rmfield(customPaths, 'centralRepository'); end + if isfield(customPaths, 'expInfoRepository') + % 'expInfo' is deprecated, change to 'main' + p.mainRepository = customPaths.expInfoRepository; + customPaths = rmfield(customPaths, 'expInfoRepository'); + end % merge paths structures, with precedence on the loaded custom paths p = mergeStructs(customPaths, p); end - end \ No newline at end of file diff --git a/+dat/reposPath.m b/+dat/reposPath.m index 28ed0532..e2991ebc 100644 --- a/+dat/reposPath.m +++ b/+dat/reposPath.m @@ -13,11 +13,11 @@ % that repository, and "master" will return the path to the master % location. % -% e.g. to get all paths you should save to for the "expInfo" repository: -% savePaths = DAT.REPOSPATH('expInfo') % savePaths is a string cell array +% e.g. to get all paths you should save to for the "main" repository: +% savePaths = DAT.REPOSPATH('main') % savePaths is a string cell array % -% To get the master location for the "expInfo" repository: -% loadPath = DAT.REPOSPATH('expInfo', 'master') % loadPath is a string +% To get the master location for the "main" repository: +% loadPath = DAT.REPOSPATH('main', 'master') % loadPath is a string % % Part of Rigbox diff --git a/+dat/saveParamProfile.m b/+dat/saveParamProfile.m index 9ca7b011..e76ffdf9 100644 --- a/+dat/saveParamProfile.m +++ b/+dat/saveParamProfile.m @@ -9,7 +9,7 @@ function saveParamProfile(expType, profileName, params) %path to repositories fn = 'parameterProfiles.mat'; -repos = fullfile(dat.reposPath('expInfo'), fn); +repos = fullfile(dat.reposPath('main'), fn); %load existing profiles for specified expType profiles = dat.loadParamProfiles(expType); @@ -17,7 +17,7 @@ function saveParamProfile(expType, profileName, params) profiles.(profileName) = params; %wrap in a struct for saving set = struct; -set.(expType) = profiles; +set.(expType) = profiles; %#ok<STRNU> %save the updated set of profiles to each repos %where files exist already, append diff --git a/+dat/subjectExists.m b/+dat/subjectExists.m index 3ad31b0e..37799dfb 100644 --- a/+dat/subjectExists.m +++ b/+dat/subjectExists.m @@ -8,6 +8,6 @@ % 2013-03 CB created -b = file.exists(fullfile(dat.reposPath('expInfo', 'master'), ref)); +b = file.exists(fullfile(dat.reposPath('main', 'master'), ref)); end \ No newline at end of file diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index 0a203c2c..e8f5fd28 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -242,12 +242,12 @@ function login(obj) obj.log('Logged into Alyx successfully as %s', obj.AlyxInstance.User); % any database subjects that weren't in the old list of - % subjects will need a folder in expInfo. + % subjects will need a folder in the main repository. firstTimeSubs = newSubs(~ismember(newSubs, dat.listSubjects)); for fts = 1:length(firstTimeSubs) - thisDir = fullfile(dat.reposPath('expInfo', 'master'), firstTimeSubs{fts}); + thisDir = fullfile(dat.reposPath('main', 'master'), firstTimeSubs{fts}); if ~exist(thisDir, 'dir') - fprintf(1, 'making expInfo directory for %s\n', firstTimeSubs{fts}); + fprintf(1, 'making directory for %s\n', firstTimeSubs{fts}); mkdir(thisDir); end end diff --git a/+hw/PositionSensor.m b/+hw/PositionSensor.m index a7bd3c4f..350bbfe2 100644 --- a/+hw/PositionSensor.m +++ b/+hw/PositionSensor.m @@ -35,7 +35,10 @@ end function value = get.LastPosition(obj) - value = obj.DataBuffer(obj.SampleCount); + value = []; + if obj.SampleCount + value = obj.DataBuffer(obj.SampleCount); + end end function zero(obj, log) diff --git a/+hw/TLOutputChrono.m b/+hw/TLOutputChrono.m index 373cad10..1bf9c52d 100644 --- a/+hw/TLOutputChrono.m +++ b/+hw/TLOutputChrono.m @@ -68,7 +68,7 @@ function init(obj, timeline) % Add on-demand digital channel obj.Session.addDigitalChannel(obj.DaqDeviceID, obj.DaqChannelID, 'OutputOnly'); warning('on', 'daq:Session:onDemandOnlyChannelsAdded'); - tls = timeline.getSessions('main'); + tls = timeline.Sessions('main'); %%Send a test pulse low, then high to clocking channel & check we read it back idx = cellfun(@(s2)strcmp('chrono',s2), {timeline.Inputs.name}); diff --git a/+hw/Timeline.m b/+hw/Timeline.m index 2467e99a..dca17a60 100644 --- a/+hw/Timeline.m +++ b/+hw/Timeline.m @@ -94,7 +94,6 @@ properties (Transient, Access = protected) Listener % holds the listener for 'DataAvailable', see DataAvailable and Timeline.process() - Sessions = containers.Map % map of daq sessions and their channels, created at tl.start() LastTimestamp % the last timestamp returned from the daq during the DataAvailable event. Used to check sampling continuity, see tl.process() Ref % the expRef string. See tl.start() AlyxInstance % a struct contraining the Alyx token, user and url for ile registration. See tl.start() @@ -103,6 +102,10 @@ DataFID % The data file ID for writing aquired data directly to disk end + properties (Transient, SetAccess = protected, GetAccess = {?hw.Timeline, ?hw.TLOutput}) + Sessions = containers.Map % map of daq sessions and their channels, created at tl.start() + end + methods function obj = Timeline(hw) % TIMELINE Constructor method @@ -495,18 +498,8 @@ function stop(obj) % Report successful stop fprintf('Timeline for ''%s'' stopped and saved successfully.\n', obj.Ref); end - - function s = getSessions(obj, name) - % GETSESSIONS() Returns the Sessions property - % returns the Sessions property. Some things (e.g. output - % classes) need this. - % - % See Also HW.TLOUTPUT - s = obj.Sessions(name); - end - end - + methods (Access = private) function init(obj) % Create DAQ session and add channels diff --git a/+hw/devices.m b/+hw/devices.m index 24e532cf..2b3b914e 100644 --- a/+hw/devices.m +++ b/+hw/devices.m @@ -56,7 +56,7 @@ if init % intialise psychportaudio if isempty(IsPsychSoundInitialize) || ~IsPsychSoundInitialize - InitializePsychSound; + InitializePsychSound IsPsychSoundInitialize = true; end % Get list of audio devices diff --git a/+srv/expServer.m b/+srv/expServer.m index f43a1749..36aa8529 100644 --- a/+srv/expServer.m +++ b/+srv/expServer.m @@ -253,6 +253,11 @@ function runExp(expRef, preDelay, postDelay, Alyx) rig.stimWindow.BackgroundColour = bgColour; rig.stimWindow.flip(); % clear the screen after + % save a copy of the hardware in JSON + jsonData = obj2json(rig); %#ok<NASGU> + name = dat.expFilePath(expRef, 'hw-info', 'master'); + save([name(1:end-3) 'json'], 'jsonData', '-ascii'); + if rig.timeline.UseTimeline %stop the timeline system rig.timeline.stop(); diff --git a/+srv/prepareExp.m b/+srv/prepareExp.m index 6bb2a2f3..cff683e0 100644 --- a/+srv/prepareExp.m +++ b/+srv/prepareExp.m @@ -1,5 +1,4 @@ function experiment = prepareExp(params, rig, preDelay, postDelay, comm) - % parameters should have a create experiment function that takes three % arguments: % 1st, the parameters structure for configuring the experiment @@ -30,16 +29,4 @@ exp.EventHandler('experimentInit', startServices),... exp.EventHandler('experimentCleanup', stopServices)); end - -% % add a log entry for the experiment -% %TODO: in future logging will be handled by the client so that e.g. -% %comments can be entered by the supervisor and added -% % expInfo.ref = block.expRef; -% % expInfo.proportionCorrect = psycho.proportionCorrect(block); -% % expInfo.rewardType = 'water'; -% % expInfo.rewardTotal = sum([block.rewardDeliveredSizes]); % in microlitres -% % expInfo.rewardUnits = '�l'; % in microlitres -% % data.addLogEntry(subjectRef, block.startDateTime, 'experiment-info', expInfo, ''); -% end - end \ No newline at end of file diff --git a/alyx-matlab b/alyx-matlab index 577a6d02..378f95af 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit 577a6d02fb30e8e063a7609950e2b224e5ea0b39 +Subproject commit 378f95afeba573da867d8ff55851152b82468640 diff --git a/cb-tools/burgbox/mergeStructs.m b/cb-tools/burgbox/mergeStructs.m index ae9b4767..a7ee11d9 100644 --- a/cb-tools/burgbox/mergeStructs.m +++ b/cb-tools/burgbox/mergeStructs.m @@ -2,6 +2,12 @@ %MERGESTRUCTS Concatenates different structures into one structure array % s = MERGESTRUCTS(struct1, struct2,...) % +% If there are any repeated fields, the first instance of that field +% takes precedence. Therefore the order of the input structs affects the +% resulting merged struct. +% +% See also CATSTRUCTS +% % Part of Burgbox % 2013-11 CB created diff --git a/npy-matlab b/npy-matlab index 524bd143..a99e00f7 160000 --- a/npy-matlab +++ b/npy-matlab @@ -1 +1 @@ -Subproject commit 524bd143c34cbd9d1bbc895ed05e082ce4249b62 +Subproject commit a99e00f78c72a7ec5f9c3074242ffaf242de9448 From bc38aa131febae49bcbdbdd13d813c7696beb751 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 4 May 2018 13:45:14 +0100 Subject: [PATCH 129/393] Added function to turn MATLAB objects to JSON ones --- cb-tools/obj2json.m | 84 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 cb-tools/obj2json.m diff --git a/cb-tools/obj2json.m b/cb-tools/obj2json.m new file mode 100644 index 00000000..34b0bae0 --- /dev/null +++ b/cb-tools/obj2json.m @@ -0,0 +1,84 @@ +function s = obj2json(rig) +% OBJ2JSON Converts input into JSON +s = obj2struct(rig); +if verLessThan('matlab','9.1') + s = savejson('', s); +elseif verLessThan('matlab','9.3') + s = jsonencode(s); +else + s = jsonencode(s, 'ConvertInfAndNaN', true); +end +end + +function s = obj2struct(obj) +% OBJ2STRUCT Converts input object into a struct +% Returns the input but with any non-fundamental object converted to a +% structure. If the input does not contain an object, the resulting +% output will remain unchanged. +% +% NB: Does not convert National Instruments object or objects within +% non-scalar structures. Cannot currently deal with Java, COM or certain +% graphics objects. Function handles are converted to strings. +% +% 2018-05-03 MW created + +if isobject(obj) + if length(obj) > 1 + % If dealing with heterogeneous array of objects, recurse through array + s = arrayfun(@obj2struct, obj, 'uni', 0); + elseif isa(obj, 'containers.Map') + % Convert to scalar struct + keySet = keys(obj); + valueSet = values(obj); + for j = 1:length(keySet) + m.(keySet{j}) = valueSet{j}; + end + s = obj2struct(m); + else % Normal object + names = fieldnames(obj); % Get list of public properties + for i = 1:length(names) + if isobject(obj.(names{i})) % Property contains an object + if startsWith(class(obj.(names{i})),'daq.ni.') + % Do not attempt to save ni daq sessions of channels + s.(names{i}) = []; + else % Recurse + s.(names{i}) = obj2struct(obj.(names{i})); + end + elseif iscell(obj.(names{i})) + % If property contains cell array, run through each element in case + % any contain an object + s.(names{i}) = cellfun(@obj2struct, obj.(names{i}), 'uni', 0); + elseif isstruct(obj.(names{i})) && isscalar(obj.(names{i})) + % If property contains struct, run through each field in case any + % contain an object + s.(names{i}) = structfun(@obj2struct, obj.(names{i}), 'uni', 0); + elseif isa(obj.(names{i}), 'function_handle') + % Convert function to string + s.(names{i}) = func2str(obj.(names{i})); + elseif isa(obj.(names{i}), 'containers.Map') + % Convert to scalar struct + keySet = keys(obj.(names{i})); + valueSet = values(obj.(names{i})); + for j = 1:length(keySet) + m.(keySet{j}) = valueSet{j}; + end + s.(names{i}) = obj2struct(m); + else % Property is fundamental object + s.(names{i}) = obj.(names{i}); + end + end + s.ClassContructor = class(obj); % Supply class name for loading object + end +elseif iscell(obj) + % If dealing with cell array, recurse through elements + s = cellfun(@obj2struct, obj, 'uni', 0); +elseif isstruct(obj) && isscalar(obj) + % If dealing with structure, recurse through fields + s = structfun(@obj2struct, obj, 'uni', 0); +elseif isa(obj, 'function_handle') + % Convert function to string + s = func2str(obj); +else % Fundamental object, return unchanged + s = obj; +end +end \ No newline at end of file From 747629c870e9b2b0315b280e22a7bc5c816f9cfc Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 4 May 2018 13:46:57 +0100 Subject: [PATCH 130/393] Updated alyx-matlab to not use expInfo repo --- alyx-matlab | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alyx-matlab b/alyx-matlab index 378f95af..c87c470d 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit 378f95afeba573da867d8ff55851152b82468640 +Subproject commit c87c470de5b417885188e16855cbdfdcf105f13d From a27c33aeb488f58dabd8a4a7897e61802a51ac28 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 4 May 2018 20:07:21 +0100 Subject: [PATCH 131/393] Added git hash to the rig structure in hw.devices --- +hw/devices.m | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/+hw/devices.m b/+hw/devices.m index 2b3b914e..f0a8274e 100644 --- a/+hw/devices.m +++ b/+hw/devices.m @@ -34,6 +34,13 @@ end rig.useDaq = pick(rig, 'useDaq', 'def', true); +%% If Git is installed, determine hash of latest commit to code +[status, hash] = system(sprintf('git -C "%s" rev-parse HEAD',... + fileparts(which('addRigboxPaths')))); +if status == 0 + rig.GitHash = hash; +end + %% Configure common devices, if present configure('mouseInput'); configure('lickDetector'); From 1a92b22df00cc161cfa564cae07d9de1e7757d69 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 8 May 2018 12:18:13 +0100 Subject: [PATCH 132/393] Water no longer posted to Alyx if experiment is aborted --- +eui/ExpPanel.m | 5 +++-- +srv/StimulusControl.m | 4 ++-- +srv/expServer.m | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/+eui/ExpPanel.m b/+eui/ExpPanel.m index 7878f6da..9ff3310f 100644 --- a/+eui/ExpPanel.m +++ b/+eui/ExpPanel.m @@ -206,7 +206,7 @@ function expStarted(obj, rig, evt) end end - function expStopped(obj, rig, ~) + function expStopped(obj, rig, evt) % EXPSTOPPED Callback for the ExpStopped event. % expStopped(obj, rig, event) Updates the ExpRunning flag, the % panel title and status label to show that the experiment has @@ -222,8 +222,9 @@ function expStopped(obj, rig, ~) obj.Root.TitleColor = [1 0.3 0.22]; % red title area %post water to Alyx ai = rig.AlyxInstance; + aborted = evt.Data; % aborted experiment flag subject = obj.SubjectRef; - if ~isempty(ai)&&~strcmp(subject,'default') + if ~isempty(ai)&&~strcmp(subject,'default')&&~aborted switch class(obj) case 'eui.ChoiceExpPanel' if ~isfield(obj.Block.trial,'feedbackType'); return; end % No completed trials diff --git a/+srv/StimulusControl.m b/+srv/StimulusControl.m index 9e6f8114..7e1fde06 100644 --- a/+srv/StimulusControl.m +++ b/+srv/StimulusControl.m @@ -198,8 +198,8 @@ function onWSReceived(obj, ~, eventArgs) notify(obj, 'ExpStarting', srv.ExpEvent('starting', ref)); case 'completed' %experiment stopped without any exceptions - ref = data{2}; - notify(obj, 'ExpStopped', srv.ExpEvent('completed', ref)); + ref = data{2}; aborted = data{3}; + notify(obj, 'ExpStopped', srv.ExpEvent('completed', ref, aborted)); case 'expException' %experiment stopped with an exception ref = data{2}; err = data{3}; diff --git a/+srv/expServer.m b/+srv/expServer.m index 36aa8529..8fee4a84 100644 --- a/+srv/expServer.m +++ b/+srv/expServer.m @@ -203,7 +203,7 @@ function handleMessage(id, data, host) experiment.AlyxInstance = AlyxInstance; end experiment.quit(immediately); - send(communicator, id, []); + send(communicator, id, immediately); else log('Quit message received but no experiment is running\n'); end From aa55f766108131116a4b331c8267b6a2c15207c6 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 8 May 2018 12:46:45 +0100 Subject: [PATCH 133/393] Updated Alyx package to deal with server timeouts gracefully --- alyx-matlab | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alyx-matlab b/alyx-matlab index c87c470d..ba6be6a2 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit c87c470de5b417885188e16855cbdfdcf105f13d +Subproject commit ba6be6a22d91207e8cb776b82426be3a81df476b From ec3a705fb4ff68b1be9d60d7fe90479890ed06db Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 8 May 2018 17:44:40 +0100 Subject: [PATCH 134/393] Fixed bug introduced by abort feature: now expServer correctly reports abort status to mc --- +eui/ExpPanel.m | 2 +- +srv/expServer.m | 11 ++++++----- alyx-matlab | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/+eui/ExpPanel.m b/+eui/ExpPanel.m index 9ff3310f..de272d64 100644 --- a/+eui/ExpPanel.m +++ b/+eui/ExpPanel.m @@ -393,7 +393,7 @@ function build(obj, parent) 'String', 'End'),... uicontrol('Parent', buttonpanel,... 'Style', 'pushbutton',... - 'String', 'Abort')]; + 'String', 'Abort (Doesn''t post water to Alyx)')]; set(obj.StopButtons, 'Enable', 'off', 'Visible', 'off'); uicontrol('Parent', buttonpanel,... 'Style', 'pushbutton',... diff --git a/+srv/expServer.m b/+srv/expServer.m index 8fee4a84..5ba62f5c 100644 --- a/+srv/expServer.m +++ b/+srv/expServer.m @@ -55,7 +55,7 @@ function expServer(useTimelineOverride, bgColour) @() PsychPortAudio('Verbosity', oldPpaVerbosity)... })); -HideCursor(); +% HideCursor(); if nargin < 2 bgColour = 127*[1 1 1]; % mid gray by default @@ -177,9 +177,9 @@ function handleMessage(id, data, host) communicator.send(id, []); try communicator.send('status', {'starting', expRef}); - runExp(expRef, preDelay, postDelay, Alyx); + aborted = runExp(expRef, preDelay, postDelay, Alyx); log('Experiment ''%s'' completed', expRef); - communicator.send('status', {'completed', expRef}); + communicator.send('status', {'completed', expRef, aborted}); catch runEx communicator.send('status', {'expException', expRef, runEx.message}); log('Exception during experiment ''%s'' because ''%s''', expRef, runEx.message); @@ -203,7 +203,7 @@ function handleMessage(id, data, host) experiment.AlyxInstance = AlyxInstance; end experiment.quit(immediately); - send(communicator, id, immediately); + send(communicator, id, []); else log('Quit message received but no experiment is running\n'); end @@ -217,7 +217,7 @@ function handleMessage(id, data, host) end end - function runExp(expRef, preDelay, postDelay, Alyx) + function aborted = runExp(expRef, preDelay, postDelay, Alyx) % disable ptb keyboard listening KbQueueRelease(); @@ -248,6 +248,7 @@ function runExp(expRef, preDelay, postDelay, Alyx) experiment.AlyxInstance = Alyx; % add Alyx Instance experiment.run(expRef); % run the experiment communicator.EventMode = false; % back to pull message mode + aborted = strcmp(experiment.Data.endStatus, 'aborted'); % clear the active experiment var experiment = []; rig.stimWindow.BackgroundColour = bgColour; diff --git a/alyx-matlab b/alyx-matlab index ba6be6a2..296cc0a8 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit ba6be6a22d91207e8cb776b82426be3a81df476b +Subproject commit 296cc0a8240d42e183bf62c10c42e729541ae653 From d07017a27712c1b5c095f87d43eab4b08dffdfc9 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 9 May 2018 13:09:16 +0100 Subject: [PATCH 135/393] Removed return charecter from Git hash --- +hw/devices.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/+hw/devices.m b/+hw/devices.m index f0a8274e..2734fe92 100644 --- a/+hw/devices.m +++ b/+hw/devices.m @@ -38,7 +38,7 @@ [status, hash] = system(sprintf('git -C "%s" rev-parse HEAD',... fileparts(which('addRigboxPaths')))); if status == 0 - rig.GitHash = hash; + rig.GitHash = strtrim(hash); end %% Configure common devices, if present From cc2ddaaffe0a00c75c101e7d573e313c0cb68854 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 9 May 2018 15:38:28 +0100 Subject: [PATCH 136/393] Fixed the saving of JSON files in newExp expServer and Timeline: now json is written to file using fprintf --- +hw/Timeline.m | 5 +++-- +srv/expServer.m | 5 +++-- alyx-matlab | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/+hw/Timeline.m b/+hw/Timeline.m index dca17a60..71dddeb5 100644 --- a/+hw/Timeline.m +++ b/+hw/Timeline.m @@ -456,8 +456,9 @@ function stop(obj) superSave(obj.Data.savePaths, struct('Timeline', obj.Data)); % write hardware info to a JSON file for compatibility with database - hw = jsonencode(obj.Data.hw); %#ok<NASGU> - save(fullfile(fileparts(obj.Data.savePaths{2}), 'TimelineHW.json'), 'hw', '-ascii'); + fid = fopen(fullfile(fileparts(obj.Data.savePaths{2}), 'TimelineHW.json')); + fprintf(fid, '%s', jsonencode(obj.Data.hw)); + fclose(fid); % save each recorded vector into the correct format in Timeline % timebase for Alyx and optionally into universal timebase if diff --git a/+srv/expServer.m b/+srv/expServer.m index 5ba62f5c..7697c919 100644 --- a/+srv/expServer.m +++ b/+srv/expServer.m @@ -255,9 +255,10 @@ function handleMessage(id, data, host) rig.stimWindow.flip(); % clear the screen after % save a copy of the hardware in JSON - jsonData = obj2json(rig); %#ok<NASGU> name = dat.expFilePath(expRef, 'hw-info', 'master'); - save([name(1:end-3) 'json'], 'jsonData', '-ascii'); + fid = fopen([name(1:end-3) 'json']); + fprintf(fid, '%s', obj2json(rig)); + fclose(fid); if rig.timeline.UseTimeline %stop the timeline system diff --git a/alyx-matlab b/alyx-matlab index 296cc0a8..d612fe08 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit 296cc0a8240d42e183bf62c10c42e729541ae653 +Subproject commit d612fe083670f4680283bae2827dbe24c65df573 From 9bd2494f264d20c28b33ac26f2dff9979a2b03fb Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 9 May 2018 16:27:02 +0100 Subject: [PATCH 137/393] Added write flag for saving JSON to file. Also took obj2struct out of obj2json function so it can be used seperately by Timeline for saving outputs to a structure --- +hw/Timeline.m | 7 ++--- +srv/expServer.m | 2 +- alyx-matlab | 2 +- cb-tools/obj2json.m | 73 ------------------------------------------- cb-tools/obj2struct.m | 72 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 76 insertions(+), 80 deletions(-) create mode 100644 cb-tools/obj2struct.m diff --git a/+hw/Timeline.m b/+hw/Timeline.m index 71dddeb5..2e8a3b16 100644 --- a/+hw/Timeline.m +++ b/+hw/Timeline.m @@ -444,19 +444,16 @@ function stop(obj) obj.Data.currSysTimeTimelineOffset = CurrSysTimeTimelineOffset; % saving hardware metadata for each output - warning('off', 'MATLAB:structOnObject'); % sorry, don't care for outIdx = 1:numel(obj.Outputs) - s = struct(obj.Outputs(outIdx)); - s.Class = class(obj.Outputs(outIdx)); + s = obj2struct(obj.Outputs(outIdx)); obj.Data.hw.Outputs{outIdx} = s; end - warning('on', 'MATLAB:structOnObject'); % save tl to all paths superSave(obj.Data.savePaths, struct('Timeline', obj.Data)); % write hardware info to a JSON file for compatibility with database - fid = fopen(fullfile(fileparts(obj.Data.savePaths{2}), 'TimelineHW.json')); + fid = fopen(fullfile(fileparts(obj.Data.savePaths{2}), 'TimelineHW.json'), 'w'); fprintf(fid, '%s', jsonencode(obj.Data.hw)); fclose(fid); diff --git a/+srv/expServer.m b/+srv/expServer.m index 7697c919..e56b4dd9 100644 --- a/+srv/expServer.m +++ b/+srv/expServer.m @@ -256,7 +256,7 @@ function handleMessage(id, data, host) % save a copy of the hardware in JSON name = dat.expFilePath(expRef, 'hw-info', 'master'); - fid = fopen([name(1:end-3) 'json']); + fid = fopen([name(1:end-3) 'json'], 'w'); fprintf(fid, '%s', obj2json(rig)); fclose(fid); diff --git a/alyx-matlab b/alyx-matlab index d612fe08..81470615 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit d612fe083670f4680283bae2827dbe24c65df573 +Subproject commit 8147061553071352e992a09e68ec10f5d85cbd69 diff --git a/cb-tools/obj2json.m b/cb-tools/obj2json.m index 34b0bae0..100a2ab9 100644 --- a/cb-tools/obj2json.m +++ b/cb-tools/obj2json.m @@ -8,77 +8,4 @@ else s = jsonencode(s, 'ConvertInfAndNaN', true); end -end - -function s = obj2struct(obj) -% OBJ2STRUCT Converts input object into a struct -% Returns the input but with any non-fundamental object converted to a -% structure. If the input does not contain an object, the resulting -% output will remain unchanged. -% -% NB: Does not convert National Instruments object or objects within -% non-scalar structures. Cannot currently deal with Java, COM or certain -% graphics objects. Function handles are converted to strings. -% -% 2018-05-03 MW created - -if isobject(obj) - if length(obj) > 1 - % If dealing with heterogeneous array of objects, recurse through array - s = arrayfun(@obj2struct, obj, 'uni', 0); - elseif isa(obj, 'containers.Map') - % Convert to scalar struct - keySet = keys(obj); - valueSet = values(obj); - for j = 1:length(keySet) - m.(keySet{j}) = valueSet{j}; - end - s = obj2struct(m); - else % Normal object - names = fieldnames(obj); % Get list of public properties - for i = 1:length(names) - if isobject(obj.(names{i})) % Property contains an object - if startsWith(class(obj.(names{i})),'daq.ni.') - % Do not attempt to save ni daq sessions of channels - s.(names{i}) = []; - else % Recurse - s.(names{i}) = obj2struct(obj.(names{i})); - end - elseif iscell(obj.(names{i})) - % If property contains cell array, run through each element in case - % any contain an object - s.(names{i}) = cellfun(@obj2struct, obj.(names{i}), 'uni', 0); - elseif isstruct(obj.(names{i})) && isscalar(obj.(names{i})) - % If property contains struct, run through each field in case any - % contain an object - s.(names{i}) = structfun(@obj2struct, obj.(names{i}), 'uni', 0); - elseif isa(obj.(names{i}), 'function_handle') - % Convert function to string - s.(names{i}) = func2str(obj.(names{i})); - elseif isa(obj.(names{i}), 'containers.Map') - % Convert to scalar struct - keySet = keys(obj.(names{i})); - valueSet = values(obj.(names{i})); - for j = 1:length(keySet) - m.(keySet{j}) = valueSet{j}; - end - s.(names{i}) = obj2struct(m); - else % Property is fundamental object - s.(names{i}) = obj.(names{i}); - end - end - s.ClassContructor = class(obj); % Supply class name for loading object - end -elseif iscell(obj) - % If dealing with cell array, recurse through elements - s = cellfun(@obj2struct, obj, 'uni', 0); -elseif isstruct(obj) && isscalar(obj) - % If dealing with structure, recurse through fields - s = structfun(@obj2struct, obj, 'uni', 0); -elseif isa(obj, 'function_handle') - % Convert function to string - s = func2str(obj); -else % Fundamental object, return unchanged - s = obj; -end end \ No newline at end of file diff --git a/cb-tools/obj2struct.m b/cb-tools/obj2struct.m new file mode 100644 index 00000000..60e91eb6 --- /dev/null +++ b/cb-tools/obj2struct.m @@ -0,0 +1,72 @@ +function s = obj2struct(obj) +% OBJ2STRUCT Converts input object into a struct +% Returns the input but with any non-fundamental object converted to a +% structure. If the input does not contain an object, the resulting +% output will remain unchanged. +% +% NB: Does not convert National Instruments object or objects within +% non-scalar structures. Cannot currently deal with Java, COM or certain +% graphics objects. Function handles are converted to strings. +% +% 2018-05-03 MW created + +if isobject(obj) + if length(obj) > 1 + % If dealing with heterogeneous array of objects, recurse through array + s = arrayfun(@obj2struct, obj, 'uni', 0); + elseif isa(obj, 'containers.Map') + % Convert to scalar struct + keySet = keys(obj); + valueSet = values(obj); + for j = 1:length(keySet) + m.(keySet{j}) = valueSet{j}; + end + s = obj2struct(m); + else % Normal object + names = fieldnames(obj); % Get list of public properties + for i = 1:length(names) + if isobject(obj.(names{i})) % Property contains an object + if startsWith(class(obj.(names{i})),'daq.ni.') + % Do not attempt to save ni daq sessions of channels + s.(names{i}) = []; + else % Recurse + s.(names{i}) = obj2struct(obj.(names{i})); + end + elseif iscell(obj.(names{i})) + % If property contains cell array, run through each element in case + % any contain an object + s.(names{i}) = cellfun(@obj2struct, obj.(names{i}), 'uni', 0); + elseif isstruct(obj.(names{i})) && isscalar(obj.(names{i})) + % If property contains struct, run through each field in case any + % contain an object + s.(names{i}) = structfun(@obj2struct, obj.(names{i}), 'uni', 0); + elseif isa(obj.(names{i}), 'function_handle') + % Convert function to string + s.(names{i}) = func2str(obj.(names{i})); + elseif isa(obj.(names{i}), 'containers.Map') + % Convert to scalar struct + keySet = keys(obj.(names{i})); + valueSet = values(obj.(names{i})); + for j = 1:length(keySet) + m.(keySet{j}) = valueSet{j}; + end + s.(names{i}) = obj2struct(m); + else % Property is fundamental object + s.(names{i}) = obj.(names{i}); + end + end + s.ClassContructor = class(obj); % Supply class name for loading object + end +elseif iscell(obj) + % If dealing with cell array, recurse through elements + s = cellfun(@obj2struct, obj, 'uni', 0); +elseif isstruct(obj) && isscalar(obj) + % If dealing with structure, recurse through fields + s = structfun(@obj2struct, obj, 'uni', 0); +elseif isa(obj, 'function_handle') + % Convert function to string + s = func2str(obj); +else % Fundamental object, return unchanged + s = obj; +end +end \ No newline at end of file From e99159be13377d9a889409efa5202252c70bc954 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 9 May 2018 19:22:17 +0100 Subject: [PATCH 138/393] Added tooltip strings to end and abort buttons for clarity and removed some redundant code --- +eui/ExpPanel.m | 6 ++++-- +eui/ParamEditor.m | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/+eui/ExpPanel.m b/+eui/ExpPanel.m index de272d64..e07ceb6b 100644 --- a/+eui/ExpPanel.m +++ b/+eui/ExpPanel.m @@ -390,10 +390,12 @@ function build(obj, parent) obj.StopButtons = [... uicontrol('Parent', buttonpanel,... 'Style', 'pushbutton',... - 'String', 'End'),... + 'String', 'End',... + 'TooltipString', 'End experiment'),... uicontrol('Parent', buttonpanel,... 'Style', 'pushbutton',... - 'String', 'Abort (Doesn''t post water to Alyx)')]; + 'String', 'Abort',... + 'TooltipString', 'Abort experiment without posting water to Alyx')]; set(obj.StopButtons, 'Enable', 'off', 'Visible', 'off'); uicontrol('Parent', buttonpanel,... 'Style', 'pushbutton',... diff --git a/+eui/ParamEditor.m b/+eui/ParamEditor.m index a513ad6a..e29749f9 100644 --- a/+eui/ParamEditor.m +++ b/+eui/ParamEditor.m @@ -107,7 +107,7 @@ function build(obj, parent) % Build parameters panel obj.SetValuesButton = uicontrol('Parent', conditionButtonBox,... 'Style', 'pushbutton',... 'String', 'Set values',... - 'TooltipString', sprintf('Set selected values to specified value, range or function'),... + 'TooltipString', 'Set selected values to specified value, range or function',... 'Enable', 'off',... 'Callback', @(~, ~) obj.setSelectedValues()); From 8975458df92f1b1252ef8c4125aae3bd4d425e99 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 10 May 2018 11:54:03 +0100 Subject: [PATCH 139/393] HideCurser now back --- +srv/expServer.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/+srv/expServer.m b/+srv/expServer.m index e56b4dd9..7eea2ccc 100644 --- a/+srv/expServer.m +++ b/+srv/expServer.m @@ -55,7 +55,7 @@ function expServer(useTimelineOverride, bgColour) @() PsychPortAudio('Verbosity', oldPpaVerbosity)... })); -% HideCursor(); +HideCursor(); if nargin < 2 bgColour = 127*[1 1 1]; % mid gray by default From 55e925d6fcd389e946a25a0541242144aa441946 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 10 May 2018 23:03:24 +0100 Subject: [PATCH 140/393] Alyx object paths now set by Rigbox dat.paths function. Fix for missing star in mc weight plot --- +dat/paths.m | 2 ++ +eui/AlyxPanel.m | 3 ++- alyx-matlab | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/+dat/paths.m b/+dat/paths.m index 1dee5d35..72a1f2ea 100644 --- a/+dat/paths.m +++ b/+dat/paths.m @@ -23,6 +23,8 @@ p.rigbox = fileparts(which('addRigboxPaths')); % Repository for local copy of everything generated on this rig p.localRepository = 'C:\LocalExpData'; +p.localAlyxQueue = 'C:\localAlyxQueue'; +p.databaseURL = 'https://alyx.cortexlab.net'; % Under the new system of having data grouped by mouse % rather than data type, all experimental data are saved here. diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index e8f5fd28..719c58d2 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -537,7 +537,8 @@ function viewSubjectHistory(obj, ax) plot(ax, dates, ((expected-iw)*0.7)+iw, 'r', 'LineWidth', 2.0); plot(ax, dates, ((expected-iw)*0.8)+iw, 'LineWidth', 2.0, 'Color', [244, 191, 66]/255); box(ax, 'off'); - if numel(dates) > 1; xlim(ax, [min(dates) max(dates)]); end + % Change the plot x axis limits + if numel(dates) > 1; xlim(ax, [min(dates) max(now)]); end if nargin == 1 set(ax, 'XTickLabel', arrayfun(@(x)datestr(x, 'dd-mmm'), get(ax, 'XTick'), 'uni', false)) else diff --git a/alyx-matlab b/alyx-matlab index 81470615..f418ea8e 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit 8147061553071352e992a09e68ec10f5d85cbd69 +Subproject commit f418ea8ed43ca7da4e26704c05e1f3d6ef83692e From 31d9ea7bfcfe672743b6297740921a5c1449231a Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 11 May 2018 22:09:29 +0100 Subject: [PATCH 141/393] Ensure Alyx always headless when passed to expServer --- +srv/expServer.m | 2 ++ 1 file changed, 2 insertions(+) diff --git a/+srv/expServer.m b/+srv/expServer.m index 7eea2ccc..bfa4b45e 100644 --- a/+srv/expServer.m +++ b/+srv/expServer.m @@ -194,6 +194,7 @@ function handleMessage(id, data, host) if ~isempty(experiment) immediately = args{1}; AlyxInstance = args{2}; + AlyxInstance.Headless = true; if immediately log('Aborting experiment'); else @@ -209,6 +210,7 @@ function handleMessage(id, data, host) end case 'updateAlyxInstance' %recieved new Alyx Instance from Stimulus Control AlyxInstance = args{1}; %get struct + AlyxInstance.Headless = true; if ~isempty(AlyxInstance) experiment.AlyxInstance = AlyxInstance; %set property for current experiment end From 10a49eaf551400e6a5b15720546550103ac4a881 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 11 May 2018 22:24:26 +0100 Subject: [PATCH 142/393] updated submodule to deal with paths not being specified --- alyx-matlab | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alyx-matlab b/alyx-matlab index f418ea8e..1c21cd7a 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit f418ea8ed43ca7da4e26704c05e1f3d6ef83692e +Subproject commit 1c21cd7a91f47ca880e8c813a4efd9c26759f771 From 55b74d265219f05fed87e3d364ed4247e9f52899 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Mon, 14 May 2018 15:14:38 +0100 Subject: [PATCH 143/393] Fixed XTick labels in the log and now xlim depends on whether there is a new reading or not --- +eui/AlyxPanel.m | 9 +++++---- +eui/MControl.m | 1 + 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index 719c58d2..00bd41d5 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -511,15 +511,15 @@ function viewSubjectHistory(obj, ax) % collect the data for the table endpnt = sprintf('water-requirement/%s?start_date=2016-01-01&end_date=%s', obj.Subject, datestr(now, 'yyyy-mm-dd')); wr = obj.AlyxInstance.getData(endpnt); - iw = wr.implant_weight; + iw = iff(isempty(wr.implant_weight), 0, wr.implant_weight); records = catStructs(wr.records, nan); - expected = [records.weight_expected]; - expected(expected==0) = nan; % no weighings found if isempty(wr.records) obj.log('No weight data found for subject %s', obj.Subject); return end + expected = [records.weight_expected]; + expected(expected==0) = nan; dates = cellfun(@(x)datenum(x), {records.date}); % build the figure to show it @@ -538,10 +538,11 @@ function viewSubjectHistory(obj, ax) plot(ax, dates, ((expected-iw)*0.8)+iw, 'LineWidth', 2.0, 'Color', [244, 191, 66]/255); box(ax, 'off'); % Change the plot x axis limits - if numel(dates) > 1; xlim(ax, [min(dates) max(now)]); end + if numel(dates) > 1; xlim(ax, [min(dates) max(dates)]); end if nargin == 1 set(ax, 'XTickLabel', arrayfun(@(x)datestr(x, 'dd-mmm'), get(ax, 'XTick'), 'uni', false)) else + xticks(ax, 'auto') ax.XTickLabel = arrayfun(@(x)datestr(x, 'dd-mmm'), get(ax, 'XTick'), 'uni', false); end ylabel(ax, 'weight (g)'); diff --git a/+eui/MControl.m b/+eui/MControl.m index 8635f7b4..c3f3baa1 100644 --- a/+eui/MControl.m +++ b/+eui/MControl.m @@ -617,6 +617,7 @@ function plotWeightReading(obj) MinSignificantWeight = 5; %grams if g >= MinSignificantWeight obj.WeightReadingPlot = obj.WeightAxes.scatter(floor(now), g, 20^2, 'p', 'filled'); + obj.WeightAxes.XLim = [min(get(obj.WeightAxes.Handle, 'XTick')) max(now)]; set(obj.RecordWeightButton, 'Enable', 'on', 'String', sprintf('Record %.1fg', g)); end end From f842c94190b2e9376f59c68c3036ed01a98ee21f Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 15 May 2018 11:51:59 +0100 Subject: [PATCH 144/393] Fix in old choiceworld for rigs with more than 2 audio output channels --- cortexlab/+exp/configureChoiceExperiment.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cortexlab/+exp/configureChoiceExperiment.m b/cortexlab/+exp/configureChoiceExperiment.m index 446573b2..0f4dc57a 100644 --- a/cortexlab/+exp/configureChoiceExperiment.m +++ b/cortexlab/+exp/configureChoiceExperiment.m @@ -34,7 +34,7 @@ toneMaxAmp = params.Struct.onsetToneMaxAmp; rampLen = 0.01; %secs - length of amplitude ramp up/down toneSamples = toneMaxAmp*aud.pureTone(toneFreq, toneLen, audSampleRate, rampLen); -toneSamples = repmat(toneSamples, 2, 1); % replicate across two channels/stereo +toneSamples = repmat(toneSamples, dev.NrOutputChannels, 1); % replicate across channels/stereo params.set('onsetToneSamples', {toneSamples},... sprintf('The data samples for the onset tone, sampled at %iHz', audSampleRate), 'normalised'); From 621d6e1a20d6b506fbc2d1e34dbf906a30bd0190 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 16 May 2018 16:29:41 +0100 Subject: [PATCH 145/393] Updates paths file: now all config and expDefs found on zserver --- +dat/paths.m | 5 +++-- alyx-matlab | 2 +- signals | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/+dat/paths.m b/+dat/paths.m index 72a1f2ea..63c5769f 100644 --- a/+dat/paths.m +++ b/+dat/paths.m @@ -14,6 +14,7 @@ end server1Name = '\\zubjects.cortexlab.net'; +server2Name = '\\zserver.cortexlab.net'; basketName = '\\basket.cortexlab.net'; % for working analyses lugaroName = '\\lugaro.cortexlab.net'; % for tape backup @@ -33,11 +34,11 @@ % directory for organisation-wide configuration files, for now these should % all remain on zserver % p.globalConfig = fullfile(p.rigbox, 'config'); -p.globalConfig = fullfile(server1Name, 'Code', 'Rigging', 'config'); +p.globalConfig = fullfile(server2Name, 'Code', 'Rigging', 'config'); % directory for rig-specific configuration files p.rigConfig = fullfile(p.globalConfig, rig); % repository for all experiment definitions -p.expDefinitions = fullfile(server1Name, 'Code', 'Rigging', 'ExpDefinitions'); +p.expDefinitions = fullfile(server2Name, 'Code', 'Rigging', 'ExpDefinitions'); % repository for working analyses that are not meant to be stored % permanently diff --git a/alyx-matlab b/alyx-matlab index 1c21cd7a..f418ea8e 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit 1c21cd7a91f47ca880e8c813a4efd9c26759f771 +Subproject commit f418ea8ed43ca7da4e26704c05e1f3d6ef83692e diff --git a/signals b/signals index c879c89d..e200ede5 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit c879c89d9b267370b2c6cb061e8c8afaf48f4474 +Subproject commit e200ede5817d0c72949de71e49329d6ff2ac4bd1 From 841a72223e035319e32154739f10cb8bb34f26d3 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 17 May 2018 11:43:25 +0100 Subject: [PATCH 146/393] Update to Alyx: doesn't crash is dat.paths doesn't provice the relevant paths --- alyx-matlab | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alyx-matlab b/alyx-matlab index 1c21cd7a..b66e4c6f 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit 1c21cd7a91f47ca880e8c813a4efd9c26759f771 +Subproject commit b66e4c6f9918a0332489010f049c9e0aa8a4e48a From 1f21c81f954e87b38ff1d82eb9999354b59b20d1 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 17 May 2018 18:10:31 +0100 Subject: [PATCH 147/393] Contrast values no longer saved in % when exported to ALF --- +exp/SignalsExp.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/+exp/SignalsExp.m b/+exp/SignalsExp.m index 4a019e22..8b998414 100644 --- a/+exp/SignalsExp.m +++ b/+exp/SignalsExp.m @@ -904,8 +904,8 @@ function saveData(obj) contL = getOr(obj.Data.events, 'contrastLeftValues', NaN); contR = getOr(obj.Data.events, 'contrastRightValues', NaN); if ~any(isnan(contL))&&~any(isnan(contR)) - writeNPY(contL(:)*100, fullfile(expPath, 'cwStimOn.contrastLeft.npy')); - writeNPY(contR(:)*100, fullfile(expPath, 'cwStimOn.contrastRight.npy')); + writeNPY(contL(:), fullfile(expPath, 'cwStimOn.contrastLeft.npy')); + writeNPY(contR(:), fullfile(expPath, 'cwStimOn.contrastRight.npy')); else warning('No ''contrastLeft'' and/or ''contrastRight'' events recorded, cannot register to Alyx') end From dbdd7a5be34ae2fc761d134ba00bb1c6a3388a38 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 18 May 2018 11:15:01 +0100 Subject: [PATCH 148/393] Fixed 80% expected weight values in viewSubjectHistory table --- +eui/AlyxPanel.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index 00bd41d5..a06c9353 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -584,7 +584,7 @@ function viewSubjectHistory(obj, ax) dat = horzcat(... arrayfun(@(x)datestr(x), dates', 'uni', false), ... weightsByDate', ... - arrayfun(@(x)sprintf('%.1f', 0.8*(x-iw)), [records.weight_expected]', 'uni', false), ... + arrayfun(@(x)sprintf('%.1f', 0.8*(x-iw)+iw), [records.weight_expected]', 'uni', false), ... weightPctByDate'); waterDat = (... num2cell(horzcat([records.water_given]', [records.hydrogel_given]', ... From eabb88437d5008318a8649e402a4e02c2f5efc03 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 18 May 2018 18:01:48 +0100 Subject: [PATCH 149/393] Rig hardware infor now registered to Alyx --- +srv/expServer.m | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/+srv/expServer.m b/+srv/expServer.m index bfa4b45e..c0706087 100644 --- a/+srv/expServer.m +++ b/+srv/expServer.m @@ -261,6 +261,13 @@ function handleMessage(id, data, host) fid = fopen([name(1:end-3) 'json'], 'w'); fprintf(fid, '%s', obj2json(rig)); fclose(fid); + if ~strcmp(dat.parseExpRef(expRef), 'default') + try + Alyx.registerFile([name(1:end-3) 'json']); + catch ex + warning(ex.identifier, 'Failed to register hardware info: %s', ex.message); + end + end if rig.timeline.UseTimeline %stop the timeline system From 6ebf03f4b1fd6a081a06066e7a0b6626b526ca13 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 25 May 2018 17:33:12 +0100 Subject: [PATCH 150/393] AlyxPanel weight button can now records current scales reading --- +eui/AlyxPanel.m | 33 ++++++++++++++++++++++++++++++--- +eui/MControl.m | 5 ++++- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index 00bd41d5..06ef5885 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -43,11 +43,13 @@ NewExpSubject % Drop-down menu subject list LoginText % Text displaying whether/which user is logged in LoginButton % Button to log in to Alyx + WeightButton % Button to submit weight to Alyx WaterEntry % Text box for entering the amout of water to give IsHydrogel % UI checkbox indicating whether to water to be given is in gel form WaterRequiredText % Handle to text UI element displaying the water required WaterRemainingText % Handle to text UI element displaying the water remaining LoginTimer % Timer to keep track of how long the user has been logged in, when this expires the user is automatically logged out + WeightTimer % Timer to reset weight button text when scale no longer gives new readings WaterRemaining % Holds the current water required for the selected subject end @@ -134,7 +136,7 @@ 'Enable', 'off',... 'Callback', @(~,~)obj.viewAllSubjects); % Button to open a dialog for manually submitting a mouse weight - uicontrol('Parent', waterbox,... + obj.WeightButton = uicontrol('Parent', waterbox,... 'Style', 'pushbutton', ... 'String', 'Manual weighing', ... 'Enable', 'off',... @@ -206,6 +208,11 @@ function delete(obj) delete(obj.LoginTimer) % ... delete it... obj.LoginTimer = []; % ... and remove it end + if ~isempty(obj.WeightTimer) % If there is a timer object + stop(obj.WeightTimer) % Stop the timer... + delete(obj.WeightTimer) % ... delete it... + obj.WeightTimer = []; % ... and remove it + end end function login(obj) @@ -225,7 +232,7 @@ function login(obj) % minutes of 'inactivity' (defined as not calling % dispWaterReq) obj.LoginTimer = timer('StartDelay', 30*60, 'TimerFcn',... - @(~,~)obj.login, 'BusyMode', 'queue'); + @(~,~)obj.login, 'BusyMode', 'queue', 'Name', 'Login Timer'); start(obj.LoginTimer) % Enable all buttons set(findall(obj.RootContainer, '-property', 'Enable'), 'Enable', 'on'); @@ -584,7 +591,7 @@ function viewSubjectHistory(obj, ax) dat = horzcat(... arrayfun(@(x)datestr(x), dates', 'uni', false), ... weightsByDate', ... - arrayfun(@(x)sprintf('%.1f', 0.8*(x-iw)), [records.weight_expected]', 'uni', false), ... + arrayfun(@(x)sprintf('%.1f', 0.8*(x-iw)+iw), [records.weight_expected]', 'uni', false), ... weightPctByDate'); waterDat = (... num2cell(horzcat([records.water_given]', [records.hydrogel_given]', ... @@ -627,6 +634,26 @@ function viewAllSubjects(obj) end end + function updateWeightButton(obj, src, ~) + % Function for changing the text on the weight button to reflect the + % current weight value obtained by the scale. This function must be + % a callback for the hw.WeighingScale NewReading event. If a new + % reading isn't read for 10 sec the manual weighing option is made + % available instead. + % + % Example: + % aiPanel = eui.AlyxPanel; + % lh = event.listener(obj.WeighingScale, 'NewReading',... + % @(src,evt)aiPanel.updateWeightButton(src,evt)); + % + % See also hw.WeighingScale, eui.MControl + set(obj.WeightButton, 'String', sprintf('Record %.1fg', src.readGrams), 'Callback', @(~,~)obj.recordWeight(src.readGrams)) + obj.WeightTimer = timer('Name', 'Last Weight',... + 'TimerFcn', @(~,~)set(obj.WeightButton, 'String', 'Manual weighing', 'Callback', @(~,~)obj.recordWeight),... + 'StopFcn', @(src,~)delete(src), 'StartDelay', 10); + start(obj.WeightTimer) + end + function log(obj, varargin) % Function for displaying timestamped information about % occurrences. If the LoggingDisplay property is unset, the diff --git a/+eui/MControl.m b/+eui/MControl.m index c3f3baa1..9e5a30ca 100644 --- a/+eui/MControl.m +++ b/+eui/MControl.m @@ -90,8 +90,11 @@ if isfield(rig, 'scale') && ~isempty(rig.scale) obj.WeighingScale = fieldOrDefault(rig, 'scale'); init(obj.WeighingScale); + % Add listners for new reading, both for the log tab and also for + % the weigh button in the Alyx Panel. obj.Listeners = [obj.Listeners,... - {event.listener(obj.WeighingScale, 'NewReading', @obj.newScalesReading)}]; + {event.listener(obj.WeighingScale, 'NewReading', @obj.newScalesReading)}... + {event.listener(obj.WeighingScale, 'NewReading', @(src,evt)obj.AlyxPanel.updateWeightButton(src,evt))}]; end catch obj.log('Warning: could not connect to weighing scales'); From 9020166a5e2f025751d016315c3553ca6c55dbd5 Mon Sep 17 00:00:00 2001 From: nsteinme <nick.steinmetz@gmail.com> Date: Sun, 27 May 2018 13:46:35 +0100 Subject: [PATCH 151/393] change by miles? --- +eui/ExpPanel.m | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/+eui/ExpPanel.m b/+eui/ExpPanel.m index 9ff3310f..7878f6da 100644 --- a/+eui/ExpPanel.m +++ b/+eui/ExpPanel.m @@ -206,7 +206,7 @@ function expStarted(obj, rig, evt) end end - function expStopped(obj, rig, evt) + function expStopped(obj, rig, ~) % EXPSTOPPED Callback for the ExpStopped event. % expStopped(obj, rig, event) Updates the ExpRunning flag, the % panel title and status label to show that the experiment has @@ -222,9 +222,8 @@ function expStopped(obj, rig, evt) obj.Root.TitleColor = [1 0.3 0.22]; % red title area %post water to Alyx ai = rig.AlyxInstance; - aborted = evt.Data; % aborted experiment flag subject = obj.SubjectRef; - if ~isempty(ai)&&~strcmp(subject,'default')&&~aborted + if ~isempty(ai)&&~strcmp(subject,'default') switch class(obj) case 'eui.ChoiceExpPanel' if ~isfield(obj.Block.trial,'feedbackType'); return; end % No completed trials From a8ee0e49408578af6b38b36e5e283d7ca8304f12 Mon Sep 17 00:00:00 2001 From: nsteinme <nick.steinmetz@gmail.com> Date: Sun, 27 May 2018 13:47:10 +0100 Subject: [PATCH 152/393] update vis.checkers, exp.inferParameters --- +exp/inferParameters.m | 19 ++++- +srv/StimulusControl.m | 4 +- alyx-matlab | 2 +- cortexlab/+vis/checker6.m | 2 +- cortexlab/+vis/checkerLeft.m | 126 ++++++++++++++++++++++++++++++++++ cortexlab/+vis/checkerRight.m | 126 ++++++++++++++++++++++++++++++++++ 6 files changed, 274 insertions(+), 5 deletions(-) create mode 100644 cortexlab/+vis/checkerLeft.m create mode 100644 cortexlab/+vis/checkerRight.m diff --git a/+exp/inferParameters.m b/+exp/inferParameters.m index 6175fd3f..9bb61a08 100644 --- a/+exp/inferParameters.m +++ b/+exp/inferParameters.m @@ -5,11 +5,20 @@ % create some signals just to pass to the definition function and track % which parameter names are used +% if ischar(expdef) && file.exists(expdef) +% expdeffun = fileFunction(expdef); +% else +% expdeffun = expdef; +% expdef = which(func2str(expdef)); +% end if ischar(expdef) && file.exists(expdef) expdeffun = fileFunction(expdef); + [funcDir, mfile] = fileparts(expdef); addpath(funcDir); + funArgs = nargin(str2func(mfile)); else expdeffun = expdef; expdef = which(func2str(expdef)); + funArgs = nargin(expdeffun); end net = sig.Net; @@ -24,7 +33,15 @@ e.outputs = net.subscriptableOrigin('outputs'); try - expdeffun(e.t, e.events, e.pars, e.visual, e.inputs , e.outputs, e.audio); + + rig = 0; + if funArgs == 7 + expdeffun(e.t, e.events, e.pars, e.visual, e.inputs, e.outputs, e.audio); + else + expdeffun(e.t, e.events, e.pars, e.visual, e.inputs, e.outputs, e.audio, rig); + end + +% expdeffun(e.t, e.events, e.pars, e.visual, e.inputs , e.outputs, e.audio); % paramNames will be the strings corresponding to the fields of e.pars % that the user tried to reference in her expdeffun. paramNames = e.pars.Subscripts.keys'; diff --git a/+srv/StimulusControl.m b/+srv/StimulusControl.m index 7e1fde06..9e6f8114 100644 --- a/+srv/StimulusControl.m +++ b/+srv/StimulusControl.m @@ -198,8 +198,8 @@ function onWSReceived(obj, ~, eventArgs) notify(obj, 'ExpStarting', srv.ExpEvent('starting', ref)); case 'completed' %experiment stopped without any exceptions - ref = data{2}; aborted = data{3}; - notify(obj, 'ExpStopped', srv.ExpEvent('completed', ref, aborted)); + ref = data{2}; + notify(obj, 'ExpStopped', srv.ExpEvent('completed', ref)); case 'expException' %experiment stopped with an exception ref = data{2}; err = data{3}; diff --git a/alyx-matlab b/alyx-matlab index b66e4c6f..46a26d60 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit b66e4c6f9918a0332489010f049c9e0aa8a4e48a +Subproject commit 46a26d603df85dcde04404ede31ea9f51110cc33 diff --git a/cortexlab/+vis/checker6.m b/cortexlab/+vis/checker6.m index 4901b88c..1733d8f7 100644 --- a/cortexlab/+vis/checker6.m +++ b/cortexlab/+vis/checker6.m @@ -1,4 +1,4 @@ -function elem = checker3(t) +function elem = checker6(t) %vis.checker A grid of rectangles % Detailed explanation goes here diff --git a/cortexlab/+vis/checkerLeft.m b/cortexlab/+vis/checkerLeft.m new file mode 100644 index 00000000..cf1122d7 --- /dev/null +++ b/cortexlab/+vis/checkerLeft.m @@ -0,0 +1,126 @@ +function elem = checkerLeft(t) +%vis.checker A grid of rectangles +% Detailed explanation goes here + +elem = t.Node.Net.subscriptableOrigin('checker'); + +%% make initial layers to be used as templates +maskTemplate = vis.emptyLayer(); +maskTemplate.isPeriodic = false; +maskTemplate.interpolation = 'nearest'; +maskTemplate.show = true; +maskTemplate.colourMask = [false false false true]; + +maskTemplate.textureId = 'checkerMaskPixel'; +[maskTemplate.rgba, maskTemplate.rgbaSize] = vis.rgba(0, 0); +maskTemplate.blending = '1-source'; % allows us to lay down our zero alpha value + +stencilTemplate = maskTemplate; +stencilTemplate.textureId = 'checkerStencilPixel'; +[stencilTemplate.rgba, stencilTemplate.rgbaSize] = vis.rgba(1, 1); +stencilTemplate.blending = 'none'; + +% pattern layer uses the alpha values laid down by mask layers +patternLayer = vis.emptyLayer(); +patternLayer.textureId = sprintf('~checker%i', randi(2^32)); +patternLayer.isPeriodic = false; +patternLayer.interpolation = 'nearest'; +patternLayer.blending = 'destination'; % use the alpha mask gets laid down before this + +%% construct signals used to assemble layers +% N rows by cols signal is derived from the size of the pattern array but +% we skip repeats so that pattern changes don't update the mask layers +% unless the size has acutally changed +nRowsByCols = elem.pattern.flatten().map(@size).skipRepeats(); +aziRange = elem.azimuthRange.flatten(); +altRange = elem.altitudeRange.flatten(); +sizeFrac = elem.rectSizeFrac.flatten(); +% signal containing the masking layers +gridMaskLayers = mapn(nRowsByCols, aziRange, altRange, sizeFrac, ... + maskTemplate, stencilTemplate, @gridMask); +% signal contain the checker layer +checkerLayer = scan(elem.pattern.flatten(), @updatePattern,... + elem.colour.flatten(), @updateColour,... + elem.azimuthRange.flatten(), @updateAzi,... + elem.altitudeRange.flatten(), @updateAlt,... + elem.show.flatten(), @updateShow,... + patternLayer); % initial value +%% set default attribute values +elem.layers = [gridMaskLayers checkerLayer]; +elem.azimuthRange = [-135 0]; +elem.altitudeRange = [-37.5 37.5]; +elem.rectSizeFrac = [1 1]; % horizontal and vertical size of each rectangle +elem.pattern = [ + 1 -1 1 -1 + -1 0 0 0 + 1 0 0 0 + -1 1 -1 1]; + elem.show = true; +end + +%% helper functions +function layer = updatePattern(layer, pattern) +% map pattern from -1 -> 1 range to 0->255, cast to 8 bit integers, then +% convert to RGBA texture format. +[layer.rgba, layer.rgbaSize] = vis.rgbaFromUint8(uint8(127.5*(1 + pattern)), 1); +end + +function layer = updateColour(layer, colour) +layer.maxColour = [colour 1]; +end + +function layer = updateAzi(layer, aziRange) +layer.size(1) = abs(diff(aziRange)); +layer.texOffset(1) = mean(aziRange); +end + +function layer = updateAlt(layer, altRange) +layer.size(2) = abs(diff(altRange)); +layer.texOffset(2) = mean(altRange); +end + +function layer = updateShow(layer, show) +layer.show = show; +end + +function layers = gridMask(nRowsByCols, aziRange, altRange, sizeFrac, mask, stencil) +gridDims = [abs(diff(aziRange)) abs(diff(altRange))]; +cellSize = gridDims./flip(nRowsByCols); +nCols = nRowsByCols(2) + 1; +nRows = nRowsByCols(1) + 1; +midAzi = mean(aziRange); +midAlt = mean(altRange); +%% base layer to imprint area the checker can draw on (by applying an alpha mask) +stencil.texOffset = [midAzi midAlt]; +stencil.size = gridDims; +if any(sizeFrac < 1) + %% layers for lines making up mask grid - masks out margins around each square + % make layers for vertical lines + if nCols > 1 + azi = linspace(aziRange(1), aziRange(2), nCols); + else + azi = midAzi; + end + collayers = repmat(mask, 1, nCols); + for vi = 1:nCols + collayers(vi).texOffset = [azi(vi) midAlt]; + end + [collayers.size] = deal([(1 - sizeFrac(1))*cellSize(1) gridDims(2)]); + % make layers for horizontal lines + if nRows > 1 + alt = linspace(altRange(1), altRange(2), nRows); + else + alt = midAlt; + end + rowlayers = repmat(mask, 1, nRows); + for hi = 1:nRows + rowlayers(hi).texOffset = [midAzi alt(hi)]; + end + [rowlayers.size] = deal([gridDims(1) (1 - sizeFrac(2))*cellSize(2)]); + %% combine the layers and return + layers = [stencil collayers rowlayers]; +else % no mask grid needed as each cell is full size + layers = stencil; +end + +end \ No newline at end of file diff --git a/cortexlab/+vis/checkerRight.m b/cortexlab/+vis/checkerRight.m new file mode 100644 index 00000000..78b03545 --- /dev/null +++ b/cortexlab/+vis/checkerRight.m @@ -0,0 +1,126 @@ +function elem = checkerRight(t) +%vis.checker A grid of rectangles +% Detailed explanation goes here + +elem = t.Node.Net.subscriptableOrigin('checker'); + +%% make initial layers to be used as templates +maskTemplate = vis.emptyLayer(); +maskTemplate.isPeriodic = false; +maskTemplate.interpolation = 'nearest'; +maskTemplate.show = true; +maskTemplate.colourMask = [false false false true]; + +maskTemplate.textureId = 'checkerMaskPixel'; +[maskTemplate.rgba, maskTemplate.rgbaSize] = vis.rgba(0, 0); +maskTemplate.blending = '1-source'; % allows us to lay down our zero alpha value + +stencilTemplate = maskTemplate; +stencilTemplate.textureId = 'checkerStencilPixel'; +[stencilTemplate.rgba, stencilTemplate.rgbaSize] = vis.rgba(1, 1); +stencilTemplate.blending = 'none'; + +% pattern layer uses the alpha values laid down by mask layers +patternLayer = vis.emptyLayer(); +patternLayer.textureId = sprintf('~checker%i', randi(2^32)); +patternLayer.isPeriodic = false; +patternLayer.interpolation = 'nearest'; +patternLayer.blending = 'destination'; % use the alpha mask gets laid down before this + +%% construct signals used to assemble layers +% N rows by cols signal is derived from the size of the pattern array but +% we skip repeats so that pattern changes don't update the mask layers +% unless the size has acutally changed +nRowsByCols = elem.pattern.flatten().map(@size).skipRepeats(); +aziRange = elem.azimuthRange.flatten(); +altRange = elem.altitudeRange.flatten(); +sizeFrac = elem.rectSizeFrac.flatten(); +% signal containing the masking layers +gridMaskLayers = mapn(nRowsByCols, aziRange, altRange, sizeFrac, ... + maskTemplate, stencilTemplate, @gridMask); +% signal contain the checker layer +checkerLayer = scan(elem.pattern.flatten(), @updatePattern,... + elem.colour.flatten(), @updateColour,... + elem.azimuthRange.flatten(), @updateAzi,... + elem.altitudeRange.flatten(), @updateAlt,... + elem.show.flatten(), @updateShow,... + patternLayer); % initial value +%% set default attribute values +elem.layers = [gridMaskLayers checkerLayer]; +elem.azimuthRange = [0 135]; +elem.altitudeRange = [-37.5 37.5]; +elem.rectSizeFrac = [1 1]; % horizontal and vertical size of each rectangle +elem.pattern = [ + 1 -1 1 -1 + -1 0 0 0 + 1 0 0 0 + -1 1 -1 1]; + elem.show = true; +end + +%% helper functions +function layer = updatePattern(layer, pattern) +% map pattern from -1 -> 1 range to 0->255, cast to 8 bit integers, then +% convert to RGBA texture format. +[layer.rgba, layer.rgbaSize] = vis.rgbaFromUint8(uint8(127.5*(1 + pattern)), 1); +end + +function layer = updateColour(layer, colour) +layer.maxColour = [colour 1]; +end + +function layer = updateAzi(layer, aziRange) +layer.size(1) = abs(diff(aziRange)); +layer.texOffset(1) = mean(aziRange); +end + +function layer = updateAlt(layer, altRange) +layer.size(2) = abs(diff(altRange)); +layer.texOffset(2) = mean(altRange); +end + +function layer = updateShow(layer, show) +layer.show = show; +end + +function layers = gridMask(nRowsByCols, aziRange, altRange, sizeFrac, mask, stencil) +gridDims = [abs(diff(aziRange)) abs(diff(altRange))]; +cellSize = gridDims./flip(nRowsByCols); +nCols = nRowsByCols(2) + 1; +nRows = nRowsByCols(1) + 1; +midAzi = mean(aziRange); +midAlt = mean(altRange); +%% base layer to imprint area the checker can draw on (by applying an alpha mask) +stencil.texOffset = [midAzi midAlt]; +stencil.size = gridDims; +if any(sizeFrac < 1) + %% layers for lines making up mask grid - masks out margins around each square + % make layers for vertical lines + if nCols > 1 + azi = linspace(aziRange(1), aziRange(2), nCols); + else + azi = midAzi; + end + collayers = repmat(mask, 1, nCols); + for vi = 1:nCols + collayers(vi).texOffset = [azi(vi) midAlt]; + end + [collayers.size] = deal([(1 - sizeFrac(1))*cellSize(1) gridDims(2)]); + % make layers for horizontal lines + if nRows > 1 + alt = linspace(altRange(1), altRange(2), nRows); + else + alt = midAlt; + end + rowlayers = repmat(mask, 1, nRows); + for hi = 1:nRows + rowlayers(hi).texOffset = [midAzi alt(hi)]; + end + [rowlayers.size] = deal([gridDims(1) (1 - sizeFrac(2))*cellSize(2)]); + %% combine the layers and return + layers = [stencil collayers rowlayers]; +else % no mask grid needed as each cell is full size + layers = stencil; +end + +end \ No newline at end of file From 16d6293b4b4d2958f1f30e6cf71eeff409b666c2 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 30 May 2018 16:22:37 +0100 Subject: [PATCH 153/393] Added string compatibility in param editor --- +eui/ParamEditor.m | 15 +++++++++------ +exp/Parameters.m | 6 +++--- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/+eui/ParamEditor.m b/+eui/ParamEditor.m index e29749f9..f1826858 100644 --- a/+eui/ParamEditor.m +++ b/+eui/ParamEditor.m @@ -394,10 +394,12 @@ function updateGlobal(obj, param, src) end function data = controlValue2Param(obj, currParam, data, allowTypeChange) + % Convert the values displayed in the UI ('control values') to + % parameter values. String representations of numrical arrays and + % functions are converted back to their 'native' classes. if nargin < 4 allowTypeChange = false; end - % convert from control value to parameter value switch class(currParam) case 'function_handle' data = str2func(data); @@ -432,13 +434,17 @@ function updateGlobal(obj, param, src) end function data = paramValue2Control(obj, data) - % convert from parameter value to control value + % convert from parameter value to control value, i.e. a value class + % that can be easily displayed and edited by the user. Everything + % except logicals are converted to charecter arrays. switch class(data) case 'function_handle' % convert a function handle to it's string name data = func2str(data); case 'logical' data = data ~= 0; % If logical do nothing, basically. + case 'string' + data = char(data); % Strings not allowed in condition table data otherwise if isnumeric(data) % format numeric types as string number list @@ -448,7 +454,7 @@ function updateGlobal(obj, param, src) data = strJoin(data, ', '); end end - % all other data types stay as they are, including e.g. strings + % all other data types stay as they are end function fillConditionTable(obj) @@ -482,9 +488,6 @@ function makeTrialSpecific(obj, paramName, ctrls) title = obj.Parameters.title(name); description = obj.Parameters.description(name); -% if isnumeric(value) % Why? All this would do is convert logical values to char; everything else dealt with by paramValue2Control. MW 2017-02-15 -% value = num2str(value); -% end if islogical(value) % If parameter is logical, make checkbox ctrl = uicontrol('Parent', parent,... 'Style', 'checkbox',... diff --git a/+exp/Parameters.m b/+exp/Parameters.m index 03c544f6..4fa2d9ce 100644 --- a/+exp/Parameters.m +++ b/+exp/Parameters.m @@ -71,9 +71,9 @@ function set(obj, name, value, description, units) n = numel(obj.pNames); obj.IsTrialSpecific = struct; isTrialSpecificDefault = @(n) ... - ~any(strcmp(n, {'defFunction', 'expPanelFun'})) &&... - (strcmp(n, {'numRepeats'})... - || size(obj.pStruct.(n), 2) > 1); + strcmp(n, 'numRepeats') ||... % numRepeats always trail specific + (ischar(obj.pStruct.(n)) && size(obj.pStruct.(n), 1) > 1) ||... % Number of rows > 1 for chars + (~ischar(obj.pStruct.(n)) && size(obj.pStruct.(n), 2) > 1); % Number of columns > 1 for all others for i = 1:n name = obj.pNames{i}; obj.IsTrialSpecific.(name) = isTrialSpecificDefault(name); From 5d926cdafdfcb54cd74c77e152d158d3d837a90c Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 28 Jun 2018 11:43:40 +0100 Subject: [PATCH 154/393] Updated DiscWorld - fix'd error where flips occured when window wasn't ready --- alyx-matlab | 2 +- cortexlab/+exp/DiscWorld.m | 2 ++ signals | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/alyx-matlab b/alyx-matlab index 46a26d60..b66e4c6f 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit 46a26d603df85dcde04404ede31ea9f51110cc33 +Subproject commit b66e4c6f9918a0332489010f049c9e0aa8a4e48a diff --git a/cortexlab/+exp/DiscWorld.m b/cortexlab/+exp/DiscWorld.m index 0279df71..ac164a86 100644 --- a/cortexlab/+exp/DiscWorld.m +++ b/cortexlab/+exp/DiscWorld.m @@ -71,6 +71,8 @@ function prepareStim(obj) % are wavelengths (per grating cycle) in pixels. pxWavelength = wavelen*pxPerRad; + ensureWindowReady(obj); %ensure graphics window is ready for ops + % delete any previous textures deleteTextures(obj.StimWindow); diff --git a/signals b/signals index e200ede5..6fbd1855 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit e200ede5817d0c72949de71e49329d6ff2ac4bd1 +Subproject commit 6fbd18551b63da12cf9b4fbeab5bccfaad70eba6 From 16298b5447deb23f71072c8af5d6607ae61dc67d Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 28 Jun 2018 16:17:39 +0100 Subject: [PATCH 155/393] Updated Alyx and removed some unessesary code from AlyxPanel delete method --- +eui/AlyxPanel.m | 5 ----- alyx-matlab | 2 +- signals | 2 +- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index 06ef5885..a881ff2a 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -208,11 +208,6 @@ function delete(obj) delete(obj.LoginTimer) % ... delete it... obj.LoginTimer = []; % ... and remove it end - if ~isempty(obj.WeightTimer) % If there is a timer object - stop(obj.WeightTimer) % Stop the timer... - delete(obj.WeightTimer) % ... delete it... - obj.WeightTimer = []; % ... and remove it - end end function login(obj) diff --git a/alyx-matlab b/alyx-matlab index 46a26d60..a154f729 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit 46a26d603df85dcde04404ede31ea9f51110cc33 +Subproject commit a154f729dd15ab50a5450202bbeafb77c478f356 diff --git a/signals b/signals index e200ede5..6fbd1855 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit e200ede5817d0c72949de71e49329d6ff2ac4bd1 +Subproject commit 6fbd18551b63da12cf9b4fbeab5bccfaad70eba6 From 6a9764dfd77a7685a5640d56570259f2b76f8bf6 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 3 Aug 2018 16:08:20 +0100 Subject: [PATCH 156/393] experiment may now be halted by experiment definition --- +exp/SignalsExp.m | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/+exp/SignalsExp.m b/+exp/SignalsExp.m index 8b998414..5f9a4538 100644 --- a/+exp/SignalsExp.m +++ b/+exp/SignalsExp.m @@ -187,7 +187,8 @@ obj.Events.expStart.map(true).into(advanceTrial) %expStart signals advance obj.Events.endTrial.into(advanceTrial) %endTrial signals advance advanceTrial.map(true).keepWhen(hasNext).into(obj.Events.newTrial) %newTrial if more - lastTrialOver.onValue(@(~)quit(obj));]; + lastTrialOver.into(obj.Events.expStop) %newTrial if more + onValue(obj.Events.expStop, @(~)quit(obj));]; % obj.Events.trialNum.onValue(fun.partial(@fprintf, 'trial %i started\n'))]; % initialise the parameter signals globalPars.post(rmfield(globalStruct, 'defFunction')); @@ -402,7 +403,9 @@ function log(obj, field, value) end function quit(obj, immediately) - obj.Events.expStop.post(true); + if isempty(obj.Events.expStop.Node.CurrValue) + obj.Events.expStop.post(true); + end %stop delay timers. todo: need to use a less global tag tmrs = timerfind('Tag', 'sig.delay'); if ~isempty(tmrs) From bc25fa2f8985abd037dd280aba6fd80e25524d41 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 7 Aug 2018 17:19:19 +0100 Subject: [PATCH 157/393] Inputs and parameters now included in update list sent to mc --- +exp/SignalsExp.m | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/+exp/SignalsExp.m b/+exp/SignalsExp.m index 5f9a4538..a173717f 100644 --- a/+exp/SignalsExp.m +++ b/+exp/SignalsExp.m @@ -577,7 +577,11 @@ function init(obj) fieldnames(obj.Events), struct2cell(obj.Events)); outlist = mapToCell(@(n,v)queuefun(['outputs.' n],v),... fieldnames(obj.Outputs), struct2cell(obj.Outputs)); - obj.Listeners = vertcat(obj.Listeners, evtlist(:), outlist(:)); + inlist = mapToCell(@(n,v)queuefun(['inputs.' n],v),... + fieldnames(obj.Inputs), struct2cell(obj.Inputs)); + parslist = queuefun('pars', obj.ParamsLog); + obj.Listeners = vertcat(obj.Listeners, ... + evtlist(:), outlist(:), inlist(:), parslist(:)); end function cleanup(obj) @@ -712,7 +716,12 @@ function mainLoop(obj) obj.Data.stimWindowRenderTimes(obj.StimWindowUpdateCount) = renderTime; obj.StimWindowInvalid = false; end + tic sendSignalUpdates(obj); + q = toc; + if q>0.005 + fprintf(1, 'send updates took %.1fms\n', 1000*toc); + end drawnow; % allow other callbacks to execute end ensureWindowReady(obj); % complete any outstanding refresh From 5b8f8995ab29413cd128a0139b4871075e0f0cfd Mon Sep 17 00:00:00 2001 From: unknown <Experiment@ZURPRISE.ucl.ac.uk> Date: Wed, 8 Aug 2018 17:33:54 +0100 Subject: [PATCH 158/393] Noise burst samples now replicated across all channels, based on the audioDevices struct in hardware.mat file --- cortexlab/+exp/configureChoiceExperiment.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cortexlab/+exp/configureChoiceExperiment.m b/cortexlab/+exp/configureChoiceExperiment.m index 0f4dc57a..c37225f9 100644 --- a/cortexlab/+exp/configureChoiceExperiment.m +++ b/cortexlab/+exp/configureChoiceExperiment.m @@ -41,7 +41,7 @@ %% Generate noise burst for negative feedback % white noise, duplicated across two channels noiseSamples = repmat(... - randn(1, params.Struct.negFeedbackSoundDuration*audSampleRate), 2, 1); + randn(1, params.Struct.negFeedbackSoundDuration*audSampleRate), dev.NrOutputChannels, 1); params.set('negFeedbackSoundSamples', {noiseSamples},... sprintf('The samples for the negative feedback sound, sampled at %iHz', audSampleRate),... 'normalised'); From 6f9d2c8f12899df05ade478db1cc1e2554dc7864 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 9 Aug 2018 19:04:48 +0100 Subject: [PATCH 159/393] Only a single set of parameters are sent to mc per trial, rather than a cumulative structure. This should reduce the size of data to be serialized --- +exp/SignalsExp.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/+exp/SignalsExp.m b/+exp/SignalsExp.m index a173717f..6ba8e377 100644 --- a/+exp/SignalsExp.m +++ b/+exp/SignalsExp.m @@ -14,7 +14,7 @@ %activated and an optional delay between the event and activation. %They should be objects of class EventHandler. EventHandlers = exp.EventHandler.empty - + %Timekeeper used by the experiment. Clocks return the current time. See %the Clock class definition for more information. Clock = hw.ptb.Clock @@ -579,7 +579,7 @@ function init(obj) fieldnames(obj.Outputs), struct2cell(obj.Outputs)); inlist = mapToCell(@(n,v)queuefun(['inputs.' n],v),... fieldnames(obj.Inputs), struct2cell(obj.Inputs)); - parslist = queuefun('pars', obj.ParamsLog); + parslist = queuefun('pars', obj.Params); obj.Listeners = vertcat(obj.Listeners, ... evtlist(:), outlist(:), inlist(:), parslist(:)); end From 36cae70b17bb452510125b0eb61b367dac268593 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 29 Aug 2018 14:08:28 +0100 Subject: [PATCH 160/393] Added wheelAnalysis submodule and moved behavioural ALF extraction to +alf/block2ALF.m in alyx-matlab. ALF names are now up to date --- +exp/SignalsExp.m | 76 ++---------------------------------------- +exp/inferParameters.m | 10 +++--- .gitmodules | 5 +++ addRigboxPaths.m | 5 +++ alyx-matlab | 2 +- wheelAnalysis | 1 + 6 files changed, 19 insertions(+), 80 deletions(-) create mode 160000 wheelAnalysis diff --git a/+exp/SignalsExp.m b/+exp/SignalsExp.m index 6ba8e377..aa7fe87e 100644 --- a/+exp/SignalsExp.m +++ b/+exp/SignalsExp.m @@ -868,80 +868,8 @@ function saveData(obj) && ~strcmp(subject, 'default') && isfield(obj.Data, 'events') ... && ~strcmp(obj.Data.endStatus,'aborted') try - expPath = dat.expPath(obj.Data.expRef, 'main', 'master'); - % Write feedback - - feedback = getOr(obj.Data.events, 'feedbackValues', NaN); - feedback = double(feedback); - feedback(feedback == 0) = -1; - if ~isnan(feedback) - writeNPY(feedback(:), fullfile(expPath, 'cwFeedback.type.npy')); - alf.writeEventseries(expPath, 'cwFeedback',... - obj.Data.events.feedbackTimes, [], []); - writeNPY([obj.Data.outputs.rewardValues]', fullfile(expPath, 'cwFeedback.rewardVolume.npy')); - else - warning('No ''feedback'' events recorded, cannot register to Alyx') - end - - % Write go cue - interactiveOn = getOr(obj.Data.events, 'interactiveOnTimes', NaN); - if ~isnan(interactiveOn) - alf.writeEventseries(expPath, 'cwGoCue', interactiveOn, [], []); - else - warning('No ''interactiveOn'' events recorded, cannot register to Alyx') - end - - % Write response - response = getOr(obj.Data.events, 'responseValues', NaN); - if min(response) == -1 - response(response == 0) = 3; - response(response == 1) = 2; - response(response == -1) = 1; - end - if ~isnan(response) - writeNPY(response(:), fullfile(expPath, 'cwResponse.choice.npy')); - alf.writeEventseries(expPath, 'cwResponse',... - obj.Data.events.responseTimes, [], []); - else - warning('No ''feedback'' events recorded, cannot register to Alyx') - end - - % Write stim on times - stimOnTimes = getOr(obj.Data.events, 'stimulusOnTimes', NaN); - if ~isnan(stimOnTimes) - alf.writeEventseries(expPath, 'cwStimOn', stimOnTimes, [], []); - else - warning('No ''stimulusOn'' events recorded, cannot register to Alyx') - end - contL = getOr(obj.Data.events, 'contrastLeftValues', NaN); - contR = getOr(obj.Data.events, 'contrastRightValues', NaN); - if ~any(isnan(contL))&&~any(isnan(contR)) - writeNPY(contL(:), fullfile(expPath, 'cwStimOn.contrastLeft.npy')); - writeNPY(contR(:), fullfile(expPath, 'cwStimOn.contrastRight.npy')); - else - warning('No ''contrastLeft'' and/or ''contrastRight'' events recorded, cannot register to Alyx') - end - - % Write trial intervals - alf.writeInterval(expPath, 'cwTrials',... - obj.Data.events.newTrialTimes(:), obj.Data.events.endTrialTimes(:), [], []); - repNum = obj.Data.events.repeatNumValues(:); - writeNPY(repNum == 1, fullfile(expPath, 'cwTrials.inclTrials.npy')); - writeNPY(repNum, fullfile(expPath, 'cwTrials.repNum.npy')); - - % Write wheel times, position and velocity - wheelValues = obj.Data.inputs.wheelValues(:); - wheelValues = wheelValues*(3.1*2*pi/(4*1024)); - wheelTimes = obj.Data.inputs.wheelTimes(:); - alf.writeTimeseries(expPath, 'Wheel', wheelTimes, [], []); - writeNPY(wheelValues, fullfile(expPath, 'Wheel.position.npy')); - writeNPY(wheelValues./wheelTimes, fullfile(expPath, 'Wheel.velocity.npy')); - - % Register them to Alyx - files = dir(expPath); - isNPY = cellfun(@(f)endsWith(f, '.npy'), {files.name}); - files = files(isNPY); - obj.AlyxInstance.registerFile(fullfile({files.folder}, {files.name})); + fullpath = alf.block2ALF(obj.Data); + obj.AlyxInstance.registerFile(fullpath); catch ex warning(ex.identifier, 'Failed to register alf files: %s.', ex.message); end diff --git a/+exp/inferParameters.m b/+exp/inferParameters.m index 9bb61a08..9e4c7beb 100644 --- a/+exp/inferParameters.m +++ b/+exp/inferParameters.m @@ -34,12 +34,12 @@ try - rig = 0; - if funArgs == 7 +% rig = 0; +% if funArgs == 7 expdeffun(e.t, e.events, e.pars, e.visual, e.inputs, e.outputs, e.audio); - else - expdeffun(e.t, e.events, e.pars, e.visual, e.inputs, e.outputs, e.audio, rig); - end +% else +% expdeffun(e.t, e.events, e.pars, e.visual, e.inputs, e.outputs, e.audio, rig); +% end % expdeffun(e.t, e.events, e.pars, e.visual, e.inputs , e.outputs, e.audio); % paramNames will be the strings corresponding to the fields of e.pars diff --git a/.gitmodules b/.gitmodules index c7ef4a47..b6d3b966 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,8 @@ [submodule "npy-matlab"] path = npy-matlab url = https://github.com/kwikteam/npy-matlab +[submodule "wheelAnalysis"] + + path = wheelAnalysis + + url = https://github.com/cortex-lab/wheelAnalysis.git diff --git a/addRigboxPaths.m b/addRigboxPaths.m index d9f255e8..cd5d85ee 100644 --- a/addRigboxPaths.m +++ b/addRigboxPaths.m @@ -86,6 +86,11 @@ function addRigboxPaths(savePaths) % work with other software developed by CortexLab, including MPEP addpath(fullfile(root, 'cortexlab')); +% Add wheelAnalysis paths. This is a package for computing wheel velocity, +% classifying movements, etc. +addpath(fullfile(root, 'wheelAnalysis'), ... + fullfile(root, 'wheelAnalysis', 'helpers')); + % Add signals paths, this includes all the core code for running signals % experiments. This submodule is maintained by Chris Burgess. addpath(fullfile(root, 'signals'),... diff --git a/alyx-matlab b/alyx-matlab index a154f729..26a75528 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit a154f729dd15ab50a5450202bbeafb77c478f356 +Subproject commit 26a75528187c82de2faf4ee1a78596fd6bc2c231 diff --git a/wheelAnalysis b/wheelAnalysis new file mode 160000 index 00000000..16979786 --- /dev/null +++ b/wheelAnalysis @@ -0,0 +1 @@ +Subproject commit 169797868da3fe93e1581cb4d581cdc4f4d9cd34 From a54b7a2c4fe3afd9c4b8c02f350132180b467b17 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 30 Aug 2018 17:12:32 +0100 Subject: [PATCH 161/393] Bug fix for Alyx Panel logging with multiple instances of mc. Also SqueakExpPanel no longer displays pars or inputs --- +dat/paths.m | 3 ++- +eui/AlyxPanel.m | 2 +- +eui/ParamEditor.m | 12 +++++++----- +eui/SqueakExpPanel.m | 1 + 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/+dat/paths.m b/+dat/paths.m index 63c5769f..289d84d5 100644 --- a/+dat/paths.m +++ b/+dat/paths.m @@ -25,7 +25,8 @@ % Repository for local copy of everything generated on this rig p.localRepository = 'C:\LocalExpData'; p.localAlyxQueue = 'C:\localAlyxQueue'; -p.databaseURL = 'https://alyx.cortexlab.net'; +%p.databaseURL = 'https://alyx.cortexlab.net'; +p.databaseURL = 'https://dev.alyx.internationalbrainlab.org/'; % Under the new system of having data grouped by mouse % rather than data type, all experimental data are saved here. diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index a881ff2a..80b72ce3 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -660,7 +660,7 @@ function log(obj, varargin) if ~isempty(obj.LoggingDisplay) timestamp = datestr(now, 'dd-mm-yyyy HH:MM:SS'); str = sprintf('[%s] %s', timestamp, message); - current = get(obj.LoggingDisplay, 'String'); + current = cellflat(get(obj.LoggingDisplay, 'String')); %NB: If more that one instance of MATLAB is open, we use %the last opened LoggingDisplay set(obj.LoggingDisplay(end), 'String', [current; str], 'Value', numel(current) + 1); diff --git a/+eui/ParamEditor.m b/+eui/ParamEditor.m index f1826858..75aca882 100644 --- a/+eui/ParamEditor.m +++ b/+eui/ParamEditor.m @@ -489,11 +489,13 @@ function makeTrialSpecific(obj, paramName, ctrls) description = obj.Parameters.description(name); if islogical(value) % If parameter is logical, make checkbox - ctrl = uicontrol('Parent', parent,... - 'Style', 'checkbox',... - 'TooltipString', description,... - 'Value', value,... % Added 2017-02-15 MW set checkbox to what ever the parameter value is - 'Callback', @(src, e) obj.updateGlobal(name, src)); + for i = 1:length(value) + ctrl(end+1) = uicontrol('Parent', parent,... + 'Style', 'checkbox',... + 'TooltipString', description,... + 'Value', value(i),... % Added 2017-02-15 MW set checkbox to what ever the parameter value is + 'Callback', @(src, e) obj.updateGlobal(name, src)); + end elseif ischar(value) ctrl = uicontrol('Parent', parent,... 'BackgroundColor', [1 1 1],... diff --git a/+eui/SqueakExpPanel.m b/+eui/SqueakExpPanel.m index 39320746..29487985 100644 --- a/+eui/SqueakExpPanel.m +++ b/+eui/SqueakExpPanel.m @@ -83,6 +83,7 @@ function processUpdates(obj) for ui = 1:length(updates) signame = updates(ui).name; switch signame + case {'inputs.wheel', 'pars'} otherwise if ~isKey(obj.LabelsMap, signame) obj.LabelsMap(signame) = obj.addInfoField(signame, ''); From 7c49c0373e4b1e4ec057999bf9097ec6588cfc6e Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 30 Aug 2018 18:01:34 +0100 Subject: [PATCH 162/393] Updated alyx-matlab: set method for base url --- +dat/paths.m | 4 ++-- alyx-matlab | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/+dat/paths.m b/+dat/paths.m index 289d84d5..c126e5a8 100644 --- a/+dat/paths.m +++ b/+dat/paths.m @@ -25,8 +25,8 @@ % Repository for local copy of everything generated on this rig p.localRepository = 'C:\LocalExpData'; p.localAlyxQueue = 'C:\localAlyxQueue'; -%p.databaseURL = 'https://alyx.cortexlab.net'; -p.databaseURL = 'https://dev.alyx.internationalbrainlab.org/'; +p.databaseURL = 'https://alyx.cortexlab.net'; +% p.databaseURL = 'https://dev.alyx.internationalbrainlab.org/'; % Under the new system of having data grouped by mouse % rather than data type, all experimental data are saved here. diff --git a/alyx-matlab b/alyx-matlab index 26a75528..521a8b57 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit 26a75528187c82de2faf4ee1a78596fd6bc2c231 +Subproject commit 521a8b57a9a872eb9c27e91a636bf94fb142b230 From b75322cfca06069a9ccdf8240049a53a2964b649 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 20 Sep 2018 10:38:11 +0100 Subject: [PATCH 163/393] Update to block2ALF: fix'd stimOn and feedback times --- alyx-matlab | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alyx-matlab b/alyx-matlab index 521a8b57..fa739add 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit 521a8b57a9a872eb9c27e91a636bf94fb142b230 +Subproject commit fa739add45c654df4f9cc28d55c7825a97c8297d From 524a997f584ee24af5523488047624bde7008012 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 26 Sep 2018 10:57:06 +0100 Subject: [PATCH 164/393] Moved Alyx instantiation to constructor to avoid unexpected behaviour --- +eui/AlyxPanel.m | 3 ++- alyx-matlab | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index 80b72ce3..c107b6f2 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -32,7 +32,7 @@ % 2017-03 NS created % 2017-10 MW made into class properties (SetAccess = private) - AlyxInstance = Alyx('',''); % An Alyx object to interfacing with the database + AlyxInstance % An Alyx object to interfacing with the database SubjectList % List of active subjects from database Subject = 'default' % The name of the currently selected subject end @@ -69,6 +69,7 @@ % % See also Alyx + obj.AlyxInstance = Alyx('',''); if ~nargin % No parant object: create new figure f = figure('Name', 'alyx GUI',... 'MenuBar', 'none',... diff --git a/alyx-matlab b/alyx-matlab index fa739add..480155b7 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit fa739add45c654df4f9cc28d55c7825a97c8297d +Subproject commit 480155b70bb8a4762400887f8ef6f05f84986099 From a44ecd9ba710df1f3d0a30d21a7094b61652e73c Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 27 Sep 2018 12:26:05 +0100 Subject: [PATCH 165/393] Added psychofit toolbox to psy package --- +psy/erf_psycho.m | 15 +++++++ +psy/erf_psycho_2gammas.m | 29 ++++++++++++++ +psy/mle_fit_psycho.m | 84 +++++++++++++++++++++++++++++++++++++++ +psy/neg_likelihood.m | 52 ++++++++++++++++++++++++ +psy/weibull.m | 34 ++++++++++++++++ +psy/weibull50.m | 40 +++++++++++++++++++ 6 files changed, 254 insertions(+) create mode 100644 +psy/erf_psycho.m create mode 100644 +psy/erf_psycho_2gammas.m create mode 100644 +psy/mle_fit_psycho.m create mode 100644 +psy/neg_likelihood.m create mode 100644 +psy/weibull.m create mode 100644 +psy/weibull50.m diff --git a/+psy/erf_psycho.m b/+psy/erf_psycho.m new file mode 100644 index 00000000..315fcc37 --- /dev/null +++ b/+psy/erf_psycho.m @@ -0,0 +1,15 @@ +function f = erf_psycho(pars,xx) +%ERF_PSYCHO erf function from 0 to 1, with lapse rate +% +% f = erf_psycho([threshold slope gamma],xx) +% +% computes: +% f = gamma + (1-2*gamma)* (erf( (xx-threshold)/slope ) + 1)/2 +% +% MC 2000 + +threshold = pars(1); +slope = pars(2); +gamma = pars(3); + +f = gamma + (1-2*gamma)* (erf( (xx-threshold)/slope ) + 1)/2; diff --git a/+psy/erf_psycho_2gammas.m b/+psy/erf_psycho_2gammas.m new file mode 100644 index 00000000..53ab7796 --- /dev/null +++ b/+psy/erf_psycho_2gammas.m @@ -0,0 +1,29 @@ +function f = erf_psycho_2gammas(pars,xx) +%ERF_PSYCHO_2GAMMAS erf function from 0 to 1, wiht two lapse rates +% +% f = erf_psycho_2gammas([threshold slope gamma1 gamma2],xx) +% +% Example: +% xx = -50:50; +% ff = erf_psycho_2gammas([-10 10 0.2 0.0],xx); +% figure; plot(xx, ff); set(gca,'ylim',[0 1]); +% +% MC 2000 +% 2013-06 MC and MD + +threshold = pars(1); +slope = pars(2); +gamma1 = pars(3); +gamma2 = pars(4); + + +f = nan(size(xx)); + +% ii = (xx<threshold); +% f( ii) = gamma1 + (1-2*gamma1)* (erf( (xx( ii)-threshold)/slope ) + 1)/2; +% f(~ii) = gamma2 + (1-2*gamma2)* (erf( (xx(~ii)-threshold)/slope ) + 1)/2; + +f = gamma1 + (1-gamma1-gamma2)* (erf( (xx-threshold)/slope ) + 1)/2; + + + diff --git a/+psy/mle_fit_psycho.m b/+psy/mle_fit_psycho.m new file mode 100644 index 00000000..0e007143 --- /dev/null +++ b/+psy/mle_fit_psycho.m @@ -0,0 +1,84 @@ +function [ pars, L ] = mle_fit_psycho(data,P_model,parstart,parmin,parmax,nfits) +% MLE_FIT_PSYCHO Maximumum likelihood fit of psychometric function +% +% pars = mle_fit_psycho([xx,nn,pp]) fits a Weibull function to the data, where +% xx is a vector of stim levels +% nn is a vector of number of trials for each stim level +% pp is a vector of percentage correct +% +% pars = mle_fit_psycho([xx;nn;pp], P_model) lets you specify the psychometric +% function (default = 'weibull'). Possibilities include 'weibull' +% (DEFAULT), 'weibull50' and 'erf_psycho' +% +% pars = mle_fit_psycho([xx;nn;pp], P_model,parstart,parmin,parmax) ... +% +% pars = mle_fit_psycho([xx;nn;pp], P_model,parstart,parmin,parmax,nfits) +% - default 5 +% - choose this number of random starting values to try to avoid local +% minima. Recommended to use a value >1. +% +% If the data go from 50% to 100%, you better use an appropriate P_model... +% For example: weibull50 +% +% [pars L ] = ... returns the likelihood +% +% EXAMPLE (with data from 0 to 1) +% cc = [-8 -6 -4 -2 0 2 4 6 8 ]; % contrasts +% nn = [10 10 10 10 10 10 10 10 10 ]; % number of trials at each contrast +% pp = [ 5 8 20 41 54 59 79 92 96 ]/100; % proportion "rightward" +% +% pars = mle_fit_psycho([cc;nn;pp], 'erf_psycho'); +% +% figure; clf +% plot(cc, pp, 'bo', 'markerfacec','b'); hold on +% plot(-8:0.1:8, erf_psycho(pars,-8:0.1:8), 'b'); + +% 1999-11 FH wrote it +% 2000-01 MC cleaned it up +% 2000-04 MC took care of the 50% case +% 2009-12 MC replaced fmins with fminsearch +% 2010-02 MC, AZ added nfits +% 2013-02 MC+MD fixed bug with dealing with NaNs + +if nargin < 6 + nfits = 5; +end + +if size(data,1)~=3 + error('Error in mle_fit_psycho: Size of ''data'' must be [3,x]!'); +end + +% find the good values in pp (conditions that were effectively run) +ii = isfinite(data(3,:)); + +if nargin < 2 + P_model = 'weibull'; +end + +if nargin < 3 + xx = data(1,ii); + parstart = [ mean(xx), 3, 0.05 ]; + parmin = [min(xx) 0 0]; + parmax = [max(xx) 10 0.40]; +end + +likelihoods = zeros(nfits,1); +pars = cell(nfits,1); + +for ifit = 1:nfits + + pars{ifit} = fminsearch(... + @(pars) psy.neg_likelihood(pars, data(:,ii), P_model, parmin, parmax),... + parstart , optimset('Display','off') ); %AZ2010-03-31: suppress msgs + + parstart = parmin + rand(size(parmin)).* (parmax-parmin); + + likelihoods(ifit) = - psy.neg_likelihood(pars{ifit}, data(:,ii), P_model, parmin, parmax); + +end + +% the values to be output +[L,iBestFit] = max(likelihoods); +pars = pars{iBestFit}; + + diff --git a/+psy/neg_likelihood.m b/+psy/neg_likelihood.m new file mode 100644 index 00000000..d59f588a --- /dev/null +++ b/+psy/neg_likelihood.m @@ -0,0 +1,52 @@ +function l = neg_likelihood(pars, data, P_model, parmin, parmax) +% NEG_LIKELIHOOD Negative likelihood of a psychometric function +% +% L = neg_likelihood(pars, [xx,nn,pp], P_model) is +% - sum(nn.*(pp.*log10(P_model)+(1-pp).*log10(1-P_model))) +% +% P_model defaults to 'weibull' +% +% L = neg_likelihood(pars, [xx,nn,pp], P_model, parmin, parmax) lets you +% choose the boundaries for the parameters. +% +% parameters are pars = [threshold, slope, gamma] +% +% 1999-11 FH wrote it +% 2000-01 MC cleaned it up +% 2000-07 MC made it indep of Weibull and added parmin and parmax + +if nargin<3 + P_model= 'weibull'; +end + +if nargin<4 + parmin = [0.005 0 0]; +end + +if nargin<5 + parmax = [0.5 10 0.25]; +end + +xx = data(1,:); +nn = data(2,:); +pp = data(3,:); + +% here is where you effectively put the constraints. +if any(pars<parmin) || any(pars>parmax) + l = 10000000; + return +end + +probs = eval(['psy.', P_model,'(pars,xx)']); + +if max(probs)>1 || min(probs)<0 + error('At least one of the probabilities is not between 0 and 1'); +end + +probs(probs==0)=eps; +probs(probs==1)=1-eps; + +l = - sum(nn.*(pp.*log(probs)+(1-pp).*log(1-probs))); +% this equation comes from the appendix of Watson, A.B. (1979). Probability +% summation over time. Vision Res 19, 515-522. + diff --git a/+psy/weibull.m b/+psy/weibull.m new file mode 100644 index 00000000..1200c047 --- /dev/null +++ b/+psy/weibull.m @@ -0,0 +1,34 @@ +function f = weibull(pars,xx) +%WEIBULL Weibull function from 0 to 1, with lapse rate +% +% f = weibull([alpha,beta,gamma],xx) is +% (1 - gamma) - (1 - 2*gamma) * exp(-(xx/alpha)^beta) +% +% This function goes from -(1-gamma) to (1-gamma). If you need a function +% that goes from 0.5 to (1-gamma), use weibull50 +% +% 1999-11 FH wrote it +% 2000-01 MC cleaned it up + +xx = xx(:)'; + +if nargin~=2 + error('Error in function Weibull: Wrong number of input arguments'); +end +if size(xx,1)~=1 + error('Error in function Weibull: variable xx must be a vector'); +end +if size(pars)~=[1,3] + error('Error in function Weibull: Wrong number of input arguments in pars!') +end + +alpha = pars(1); +beta = pars(2); +gamma = pars(3); + +if length(alpha)~=1 || length(beta)~=1 || length(gamma)~=1 + error('Variables ''alpha'',''beta'' and ''gamma'' must be scalar!'); +end + +f = (1 - gamma) - (1 - 2*gamma) * exp( -((xx./alpha).^beta)); + diff --git a/+psy/weibull50.m b/+psy/weibull50.m new file mode 100644 index 00000000..56dfe66d --- /dev/null +++ b/+psy/weibull50.m @@ -0,0 +1,40 @@ +function f = weibull50(pars,xx) +%WEIBULL50 Weibull function from 0.5 to 1, with lapse rate +% +% f = weibull50([alpha,beta,gamma],xx) is +% (1 - gamma) - (1/2 - gamma) * exp(-(xx/alpha)^beta) +% +% alpha is the threshold +% beta is the slope +% gamma is the % of errors users make anyhow +% +% See also: Weibull +% +% 2000-04 MC + +xx = xx(:)'; + +if nargin~=2 + error('Error in function Weibull: Wrong number of input arguments'); +end +if size(xx,1)~=1 + error('Error in function Weibull: variable xx must be a vector'); +end +if size(pars)~=[1,3] + error('Error in function Weibull: Wrong number of input arguments in pars!') +end + +alpha = pars(1); +beta = pars(2); +gamma = pars(3); + +if alpha<0 + error('alpha must be positive'); +end + +if length(alpha)~=1 || length(beta)~=1 || length(gamma)~=1 + error('Variables ''alpha'',''beta'' and ''gamma'' must be scalar!'); +end + +f = (1 - gamma) - (0.5 - gamma) * exp( -((xx./alpha).^beta)); + From edc0549afbd0802e8a27bbbd5c87552025a29f4f Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 2 Oct 2018 19:25:04 +0100 Subject: [PATCH 166/393] Fix'd typo in readme --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index b6632e3c..2e7e3036 100644 --- a/readme.md +++ b/readme.md @@ -34,7 +34,7 @@ cd Rigbox/ git checkout rigbox-lite ``` 3. Run the following to clone the submodules: -```git submodules update --init``` +```git submodule update --init``` 3. In MATLAB run 'addRigboxPaths.m' and restart the program. 4. Set the correct paths by following the instructions in Rigbox\+dat\paths.m on both computers. 5. On the stimulus server, load the hardware.mat file in Rigbox\Repositories\code\config\exampleRig and edit according to your specific hardware setup (link to detailed instructions above, under 'Getting started'). @@ -42,7 +42,7 @@ git checkout rigbox-lite To keep up to date, run the following: ``` git pull -git submodules update --remote +git submodule update --remote ``` ## Running an experiment From 4595b5d85cffd14390231c5cfc20f4f57b4d8f78 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 3 Oct 2018 17:43:26 +0100 Subject: [PATCH 167/393] Colourmap fix for discrimiation visualization --- cortexlab/+psy/plot2AUFC.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cortexlab/+psy/plot2AUFC.m b/cortexlab/+psy/plot2AUFC.m index be2e4724..820e51fb 100644 --- a/cortexlab/+psy/plot2AUFC.m +++ b/cortexlab/+psy/plot2AUFC.m @@ -53,7 +53,7 @@ function plot2AUFC(ax, block) psychoMCmap = reshape(permute(psychoM, [2 1 3]), numRespTypes*nCR, nCL)'; psychoMCmap(isnan(psychoMCmap))=-1; imagesc(ax, psychoMCmap) - colormap(colormap_pinkgreyscale) + colormap(psy.colormap_pinkgreyscale) set(ax, 'XTick', 1:nCR*numRespTypes, 'XTickLabel', cValsRight(repmat(1:nCR, [1 numRespTypes]))); set(ax, 'YTick', 1:nCL, 'YTickLabel', cValsLeft(1:nCL)); From 7b30f3d06523ef509cfaf60ce597f871d0861ebe Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 12 Oct 2018 17:03:34 +0100 Subject: [PATCH 168/393] Param profiles sorted ignoring case --- +dat/loadParamProfiles.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/+dat/loadParamProfiles.m b/+dat/loadParamProfiles.m index 2c82c14a..b63a70cb 100644 --- a/+dat/loadParamProfiles.m +++ b/+dat/loadParamProfiles.m @@ -18,7 +18,8 @@ loaded = load(masterPath, expType); %load profiles for specific experiment type warning(origState); if isfield(loaded, expType) - p = orderfields(loaded.(expType)); %extract those profiles to return + [~, I] = sort(lower(fieldnames(loaded.(expType)))); + p = orderfields(loaded.(expType), I); %extract those profiles to return end end From 276cd8e989a502dd59c419773083cec01a498eed Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 12 Oct 2018 17:07:07 +0100 Subject: [PATCH 169/393] Signals updates set once every 100ms --- +exp/SignalsExp.m | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/+exp/SignalsExp.m b/+exp/SignalsExp.m index aa7fe87e..e83adb98 100644 --- a/+exp/SignalsExp.m +++ b/+exp/SignalsExp.m @@ -653,6 +653,7 @@ function mainLoop(obj) %set looping flag obj.IsLooping = true; + t = obj.Clock.now; % begin the loop while obj.IsLooping %% create a list of handlers that have become due @@ -716,12 +717,15 @@ function mainLoop(obj) obj.Data.stimWindowRenderTimes(obj.StimWindowUpdateCount) = renderTime; obj.StimWindowInvalid = false; end - tic - sendSignalUpdates(obj); - q = toc; - if q>0.005 - fprintf(1, 'send updates took %.1fms\n', 1000*toc); + if (obj.Clock.now - t) > 0.1 + sendSignalUpdates(obj); + t = obj.Clock.now; end + +% q = toc; +% if q>0.005 +% fprintf(1, 'send updates took %.1fms\n', 1000*toc); +% end drawnow; % allow other callbacks to execute end ensureWindowReady(obj); % complete any outstanding refresh From ddfedd06ba61d8da19f2aeba1a3aae533e3b2b5a Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 17 Oct 2018 16:19:32 +0100 Subject: [PATCH 170/393] Water remaining rounded up, given rounded down in AlyxPanel --- +eui/AlyxPanel.m | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index c107b6f2..dd2d2d62 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -367,12 +367,14 @@ function dispWaterReq(obj, src, ~) colour = 'black'; % Mouse above 80% or no weight measured today weight_pct = '> 80%'; end + % Round up water remaining to the near 0.01 + remainder = ceil(s(idx).water_requirement_remaining*100)/100; % Set text set(obj.WaterRequiredText, 'ForegroundColor', colour, 'String', ... sprintf('Subject %s requires %.2f of %.2f today\n\t Weight today: %.2f (%s) Water today: %.2f', ... - obj.Subject, s(idx).water_requirement_remaining, s(idx).water_requirement_total, weight, weight_pct, sum([water gel]))); + obj.Subject, remainder, s(idx).water_requirement_total, weight, weight_pct, floor(sum([water gel])*100)/100)); % Set WaterRemaining attribute for changeWaterText callback - obj.WaterRemaining = s(idx).water_requirement_remaining; + obj.WaterRemaining = remainder; end catch me d = me.message; %FIXME: JSON no longer returned From b89c25359ebf016b0204c960fb883589301c2e8e Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 17 Oct 2018 18:31:53 +0100 Subject: [PATCH 171/393] Applied rounding everywhere in AlyxPanel --- +eui/AlyxPanel.m | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index dd2d2d62..77a2e6cc 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -368,11 +368,13 @@ function dispWaterReq(obj, src, ~) weight_pct = '> 80%'; end % Round up water remaining to the near 0.01 - remainder = ceil(s(idx).water_requirement_remaining*100)/100; + remainder = obj.round(s(idx).water_requirement_remaining, 'up'); % Set text set(obj.WaterRequiredText, 'ForegroundColor', colour, 'String', ... - sprintf('Subject %s requires %.2f of %.2f today\n\t Weight today: %.2f (%s) Water today: %.2f', ... - obj.Subject, remainder, s(idx).water_requirement_total, weight, weight_pct, floor(sum([water gel])*100)/100)); + sprintf(['Subject %s requires %.2f of %.2f today\n\t '... + 'Weight today: %.2f (%s) Water today: %.2f'], obj.Subject, ... + remainder, obj.round(s(idx).water_requirement_total, 'up'), weight, ... + weight_pct, obj.round(sum([water gel]), 'down'))); % Set WaterRemaining attribute for changeWaterText callback obj.WaterRemaining = remainder; end @@ -564,11 +566,11 @@ function viewSubjectHistory(obj, ax) ylabel(ax, 'weight as pct (%)'); axWater = axes('Parent',plotBox); - plot(axWater, dates, [records.water_given]+[records.hydrogel_given], '.-'); + plot(axWater, dates, obj.round([records.water_given]+[records.hydrogel_given], 'up'), '.-'); hold(axWater, 'on'); - plot(axWater, dates, [records.hydrogel_given], '.-'); - plot(axWater, dates, [records.water_given], '.-'); - plot(axWater, dates, [records.water_expected], 'r', 'LineWidth', 2.0); + plot(axWater, dates, obj.round([records.hydrogel_given], 'down'), '.-'); + plot(axWater, dates, obj.round([records.water_given], 'down'), '.-'); + plot(axWater, dates, obj.round([records.water_expected], 'up'), 'r', 'LineWidth', 2.0); box(axWater, 'off'); xlim(axWater, [min(dates) max(dates)]); set(axWater, 'XTickLabel', arrayfun(@(x)datestr(x, 'dd-mmm'), get(axWater, 'XTick'), 'uni', false)) @@ -622,11 +624,11 @@ function viewAllSubjects(obj) colorgen = @(colorNum,text) ['<html><body bgcolor=#',htmlColor(colorNum),'>',text,'</body></html>']; wrdat = cellfun(@(x)colorgen(1-double(x>0)*[0 0.3 0.3],... - sprintf('%.2f',x)), {wr.water_requirement_remaining}, 'uni', false); + sprintf('%.2f',obj.round(x, 'up'))), {wr.water_requirement_remaining}, 'uni', false); set(wrTable, 'ColumnName', {'Name', 'Water Required', 'Remaining Requirement'}, ... 'Data', horzcat({wr.nickname}', ... - cellfun(@(x)sprintf('%.2f',x),{wr.water_requirement_total}', 'uni', false), ... + cellfun(@(x)sprintf('%.2f',obj.round(x, 'up')),{wr.water_requirement_total}', 'uni', false), ... wrdat'), ... 'ColumnEditable', false(1,3)); end @@ -673,4 +675,18 @@ function log(obj, varargin) end end + methods (Static) + function A = round(a, direction, sigFigures) + if nargin < 3; sigFigures = 2; end + c = 1*10^sigFigures; + switch direction + case 'up' + A = ceil(a*c)/c; + case 'down' + A = ceil(a*c)/c; + otherwise + A = round(a*c)/c; + end + end + end end \ No newline at end of file From 0b6c2856e2d284498098871d417443731498de3a Mon Sep 17 00:00:00 2001 From: jaib1 <jaib1@mit.edu> Date: Wed, 17 Oct 2018 19:38:22 +0100 Subject: [PATCH 172/393] extra commenting --- alyx-matlab | 2 +- signals | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/alyx-matlab b/alyx-matlab index 480155b7..3e88d1e9 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit 480155b70bb8a4762400887f8ef6f05f84986099 +Subproject commit 3e88d1e9c61a9b04eb99c49f269a64db843cb017 diff --git a/signals b/signals index 6fbd1855..c44fab9c 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit 6fbd18551b63da12cf9b4fbeab5bccfaad70eba6 +Subproject commit c44fab9c057a4828d64a6514d04ce9a3fb0d2f47 From 0323e0317e419946bf086f6497fdd8771482abb9 Mon Sep 17 00:00:00 2001 From: Jai Bhagat <jaib1@mit.edu> Date: Thu, 18 Oct 2018 17:53:25 +0100 Subject: [PATCH 173/393] Updated installation instructions + packages information; fixed typos to-do: add more information on submodules? --- readme.md | 150 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 91 insertions(+), 59 deletions(-) diff --git a/readme.md b/readme.md index 2e7e3036..fa163b12 100644 --- a/readme.md +++ b/readme.md @@ -1,51 +1,69 @@ ---------- # Rigbox -Rigbox is a (mostly) object-oriented MATLAB software package for designing and controlling behavioural experiments. Principally, the steering wheel setup we developed to probe mouse behaviour. It requires two computers, one for stimulus presentation ('the stimulus server') and another for controlling and monitoring the experiment ('mc'). +Rigbox is a (mostly) object-oriented MATLAB software package for designing and controlling behavioural experiments (principally, the [steering wheel setup](https://www.ucl.ac.uk/cortexlab/tools/wheel) which [we](https://www.ucl.ac.uk/cortexlab) developed to probe mouse behaviour. Rigbox requires two machines, one for stimulus presentation ('the stimulus server') and another for controlling and monitoring the experiment ('mc'). ## Getting Started -The following is a brief description of how to install Rigbox on your experimental rig. However detailed, step-by-step information can be found [here](https://www.ucl.ac.uk/cortexlab/tools/wheel). +The following is a brief description of how to install Rigbox on your experimental rig. Additional detailed, step-by-step information can be found [here](https://www.ucl.ac.uk/cortexlab/tools/wheel). ## Prerequisites -Rigbox has a number of essential and optional software dependencies, listed below: -* Windows 7 or later -* [MATLAB](https://uk.mathworks.com/downloads/web_downloads/?s_iid=hp_ff_t_downloads) 2016a or later - * [Psychophsics Toolbox](https://github.com/Psychtoolbox-3/Psychtoolbox-3/releases) v3 or later - * [NI-DAQmx support package](https://uk.mathworks.com/hardware-support/nidaqmx.html) - * [GUI Layout Toolbox](https://uk.mathworks.com/matlabcentral/fileexchange/47982-gui-layout-toolbox) v2 or later - * Data Acquisition Toolbox - * Signal Processing Toolbox - * Instrument Control Toolbox + +Rigbox has the following software dependencies: +* Windows Operating System (7 or later) +* MATLAB (2016a or later) +* The following MathWorks MATLAB toolboxes: + * Data Acquisition Toolbox + * Signal Processing Toolbox + * Instrument Control Toolbox + * Statistics and Machine Learning Toolbox +* The following community MATLAB toolboxes: + * [GUI Layout Toolbox](https://uk.mathworks.com/matlabcentral/fileexchange/47982-gui-layout-toolbox) (v2 or later) + * [Psychophsics Toolbox](http://psychtoolbox.org/download.html) (v3 or later) + * [NI-DAQmx support package](https://uk.mathworks.com/hardware-support/nidaqmx.html) + +(* *Note*: You can download all required MathWorks MATLAB toolboxes directly within MATLAB via the "Add-Ons" button in the top Toolstrip with the "Home" tab selected.) +![MATLAB Home Toolstrip](http://i67.tinypic.com/k0zue.png) + +Afterwards, you can use the MATLAB "ver" command to bring up the list of installed MathWorks toolboxes. Additionally, Rigbox works with a number of extra submodules (included): -* [Signals](https://github.com/dendritic/signals) (for running bespoke experiment designs) - * Statistics and Machine Learning Toolbox - * [Microsoft Visual C++ Redistributable for Visual Studio 2015](https://www.microsoft.com/en-us/download/details.aspx?id=48145) -* [Alyx-matlab](https://github.com/cortex-lab/alyx-matlab) (for registering data to, and retrieving from, an Alyx database -* [NPY-matlab](https://github.com/kwikteam/npy-matlab) (for saving data in binary NPY format) - -## Installing -1. To install Rigbox, clone the repository in git. It is *not* recommended to clone directly into the MATLAB folder -```git clone https://github.com/cortex-lab/Rigbox.git``` -2. Pull the latest Rigbox-lite branch. This branch is currently the 'cleanest' one, however in the future it will likely be merged with the master branch. +* [signals](https://github.com/cortex-lab/signals) (for running bespoke experiment designs) +* [alyx-matlab](https://github.com/cortex-lab/alyx-matlab) (for registering data to, and retrieving from, an Alyx database) +* [npy-matlab](https://github.com/kwikteam/npy-matlab) (for saving data in binary NPY format) +* [wheelAnalysis](https://github.com/cortex-lab/wheelAnalysis) (for analyzing data from the steering wheel task) + +## Installation via git + +0. It is highly recommended to install Rigbox via git. If not already downloaded and installed, install [git](https://git-scm.com/download/win) (and the included minGW software environment and Git Bash MinTTY terminal emulator). After installing, launch the Git Bash terminal. +1. To install Rigbox, use the following commands in the Git Bash terminal to clone the repository from github to your local machine. (* *Note*: It is *not* recommended to clone directly into the MATLAB folder) +``` +cd ~ +git clone https://github.com/cortex-lab/Rigbox.git +``` +2. Pull the latest Rigbox-lite branch. This branch is currently the cleanest one, though in the future it will likely be merged with the master branch. ``` cd Rigbox/ git checkout rigbox-lite ``` -3. Run the following to clone the submodules: -```git submodule update --init``` -3. In MATLAB run 'addRigboxPaths.m' and restart the program. -4. Set the correct paths by following the instructions in Rigbox\+dat\paths.m on both computers. -5. On the stimulus server, load the hardware.mat file in Rigbox\Repositories\code\config\exampleRig and edit according to your specific hardware setup (link to detailed instructions above, under 'Getting started'). +3. Clone the submodules: +``` +git submodule update --init +``` +4. Open MATLAB, make sure Rigbox and all subdirectories are in your path, run: +> addRigboxPaths + +and restart MATLAB. + +5. Set the correct paths by following the instructions in the 'Rigbox\+dat\paths.m' file on both machines. +6. On the stimulus server, load 'Rigbox\Repositories\code\config\exampleRig\hardware.mat' and edit according to your specific hardware setup (link to detailed instructions above, under 'Getting started'). -To keep up to date, run the following: +To keep the submodules up to date, run the following in the Git Bash terminal (within the Rigbox directory): ``` git pull git submodule update --remote ``` - -## Running an experiment +## Running an experiment in MATLAB On the stimulus server, run: > srv.expServer @@ -53,58 +71,72 @@ On the stimulus server, run: On the mc computer, run: > mc -This opens a GUI that will allow you to choose a subject, edit some of the experimental parameters and press 'Start' to begin the basic steering wheel task on the stimulus server. +This opens a GUI that will allow you to choose a subject, edit some of the experimental parameters and press 'Start' to begin the basic steering wheel task on the stimulus server. + +## Code organization -# Code organization Below is a list of the principle directories and their general purpose. -## +dat -The data package contains all the code pertaining to the organization and logging of data. It contains functions that generate and parse unique experiment reference ids, that return the file paths where subject data and rig configuration information is stored. Other functions include those that manage experimental log entries and parameter profiles. This package is akin to a lab notebook. -## +eui -This package contains the code pertaining to the Rigbox user interface. It contains code for constructing the mc GUI (MControl.m), and for plotting live experiment data or generating tables for viewing experiment parameters and subject logs. This package is exclusively used by the mc computer. +### +dat + +The "data" package contains code pertaining to the organization and logging of data. It contains functions that generate and parse unique experiment reference ids, and return file paths where subject data and rig configuration information is stored. Other functions include those that manage experimental log entries and parameter profiles. A nice metaphor for this package is a lab notebook. -## +exp -The experiment package is for the initialization and running of behavioural experiments. These files define a framework for event- and state-based experiments. Actions such as visual stimulus presentation or reward delivery can be controlled by experiment phases, and experiment phases are managed by an event-handling system (e.g. ResponseEventInfo). +### +eui -The package also triggers auxiliary services (e.g. starting remote acquisition software), and loads parameters for presentation each trail. The principle two base classes that control these experiments are Experiment and its Signals counterpart, SignalsExp. +The "user interface" package contains code pertaining to the Rigbox user interface. It contains code for constructing the mc GUI (MControl.m), and for plotting live experiment data or generating tables for viewing experiment parameters and subject logs. -This package is almost exclusively used by the stimulus server +This package is exclusively used by the mc computer. -## +hw -The hardware package is for configuring, and interfacing with, hardware such as screens, DAQ devices, weighing scales and lick detectors. Withing this is the +ptb package which contains classes for interacting with PsychToolbox. +### +exp -The devices file loads and initializes all the hardware for a specific experimental rig. There are also classes for unifying system and hardware clocks. +The "experiments" package is for the initialization and running of behavioural experiments. It contains code that define a framework for event- and state-based experiments. Actions such as visual stimulus presentation or reward delivery can be controlled by experiment phases, and experiment phases are managed by an event-handling system (e.g. ResponseEventInfo). -## +psy -This package contains simple functions for processing and plotting psychometric data +The package also triggers auxiliary services (e.g. starting remote acquisition software), and loads parameters for presentation for each trail. The principle two base classes that control these experiments are 'Experiment' and its "signals package" counterpart, 'SignalsExp'. -## +srv -This package contains the expServer function as well as classes that manage communications between rig computers. +This package is almost exclusively used by the stimulus server. -The Service base class allows the stimulus server to start and stop auxiliary acquisition systems at the beginning and end of experiments +### +hw -The StimulusControl class is used by the mc computer to manage the stimulus server +The "hardware" package is for configuring, and interfacing with, hardware (such as screens, DAQ devices, weighing scales and lick detectors). Within this is the "+ptb" package which contains classes for interacting with PsychToolbox. -NB: Lower-level communication protocol code is found in the +io package +'devices.m' loads and initializes all the hardware for a specific experimental rig. There are also classes for unifying system and hardware clocks. -## cb-tools\burgbox -Burgbox contains many simply helper functions that are used by the main packages. Within this directory are further packages: +### +psy + +The "psychometrics" package contains simple functions for processing and plotting psychometric data. + +### +srv + +The "stim server" package contains the expServer function as well as classes that manage communications between rig computers. + +The 'Service' base class allows the stimulus server to start and stop auxiliary acquisition systems at the beginning and end of experiments. + +The 'StimulusControl' class is used by the mc computer to manage the stimulus server. + +* *Note*: Lower-level communication protocol code is found in the "cortexlab/+io" package. + +### cb-tools/burgbox + +Burgbox contains many simple helper functions that are used by the main packages. Within this directory are additional packages: * +bui --- Classes for managing graphics objects such as axes * +aud --- Functions for interacting with PsychoPortAudio * +file --- Functions for simplifying directory and file management, for instance returning the modified dates for specified folders or filtering an array of directories by those that exist * +fun --- Convenience functions for working with function handles in MATLAB, e.g. functions similar cellfun that are agnostic of input type, or ones that cache function outputs -* +img --- Classes that deal with image and frame data (DEPRICATED) +* +img --- Classes that deal with image and frame data (DEPRECATED) * +io --- Lower-level communications classes for managing UDP and TCP/IP Web sockets -* +plt --- A few small plotting functions (DEPRICATED) +* +plt --- A few small plotting functions (DEPRECATED) * +vis --- Functions for returning various windowed visual stimuli (i.g. gabor gratings) -* +ws --- An early Web socket package using SuperWebSocket (DEPRICATED) +* +ws --- An early Web socket package using SuperWebSocket (DEPRECATED) + +### cortexlab -## cortexlab -The cortexlab directory is intended for functions and classes that are rig or lab specific, for instance code that allows compatibility with other stimulus presentation packages used by cortexlab (i.e. MPEP) +The cortexlab directory is intended for functions and classes that are rig or cortexlab specific, for instance code that allows compatibility with other stimulus presentation packages used by cortexlab (e.g. MPEP) -## alyx-matlab/@Alyx -This class allows interation with an instance of the Alyx database. More information about Alyx can be found [here](http://alyx.readthedocs.io/en/latest/). Information about using the alyx-matlab class can be found in [alyx-matlab/Examples.m](https://github.com/cortex-lab/alyx-matlab/blob/alyx-as-class/Examples.m). +### submodules + +Additional information on the [alyx-matlab](https://github.com/cortex-lab/alyx-matlab), [npy-matlab](https://github.com/kwikteam/npy-matlab), [signals](https://github.com/cortex-lab/signals) and [wheelAnalysis](https://github.com/cortex-lab/wheelAnalysis) submodules can be found in their respective github repositories. ## Authors -The majority of the Rigbox code was written by [Chris Burgess](https://github.com/dendritic/) in 2013. It is now maintained and developed by a number of people at [CortexLab](https://www.ucl.ac.uk/cortexlab). + +The majority of the Rigbox code was written by [Chris Burgess](https://github.com/dendritic/) in 2013. It is now maintained and developed by a number of people at [CortexLab](https://www.ucl.ac.uk/cortexlab). From 00226a713ec77529fd7e1406cc64f1f62de34769 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 18 Oct 2018 22:47:52 +0100 Subject: [PATCH 174/393] mc & expServer pull latest code upon starting --- +dat/paths.m | 3 ++- +srv/expServer.m | 4 +++- cortexlab/+git/update.m | 37 +++++++++++++++++++++++++++++++++++++ mc.m | 2 ++ signals | 2 +- 5 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 cortexlab/+git/update.m diff --git a/+dat/paths.m b/+dat/paths.m index c126e5a8..37f96514 100644 --- a/+dat/paths.m +++ b/+dat/paths.m @@ -27,6 +27,7 @@ p.localAlyxQueue = 'C:\localAlyxQueue'; p.databaseURL = 'https://alyx.cortexlab.net'; % p.databaseURL = 'https://dev.alyx.internationalbrainlab.org/'; +p.gitExe = 'C:\Program Files\Git\cmd\git.exe'; % Under the new system of having data grouped by mouse % rather than data type, all experimental data are saved here. @@ -69,4 +70,4 @@ p = mergeStructs(customPaths, p); end -end \ No newline at end of file +end diff --git a/+srv/expServer.m b/+srv/expServer.m index c0706087..6e3cc761 100644 --- a/+srv/expServer.m +++ b/+srv/expServer.m @@ -19,6 +19,8 @@ function expServer(useTimelineOverride, bgColour) rewardId = 1; %% Initialisation +% Ensure code is up-to-date +git.update; % random seed random number generator rng('shuffle'); % communicator for receiving commands from clients @@ -362,4 +364,4 @@ function setClock(user, clock) log('Use of timeline disabled'); end end -end \ No newline at end of file +end diff --git a/cortexlab/+git/update.m b/cortexlab/+git/update.m new file mode 100644 index 00000000..da3cdfce --- /dev/null +++ b/cortexlab/+git/update.m @@ -0,0 +1,37 @@ +function update(essential) +% UPDATE Update Rigbox code +% TODO +% +if nargin == 0; essential = true; end +gitexepath = getOr(dat.paths, 'gitExe', 'C:\Program Files\Git\cmd\git.exe'); %TODO generalize +gitexepath = ['"' gitexepath '"']; +root = fileparts(which('addRigboxPaths')); +origDir = pwd; +cd(root) + +cmdstr = strjoin({gitexepath, 'pull'}); +[status, cmdout] = system(cmdstr); +if status ~= 0 + if essential + cd(origDir) + error('gitUpdate:pull:pullFailed', 'Failed to pull latest changes:, %s', cmdout) + else + warning('gitUpdate:pull:pullFailed', 'Failed to pull latest changes:, %s', cmdout) + end +end +% TODO: check if submodules are empty and use init flag +cmdstr = strjoin({gitexepath, 'submodule update --remote --merge'}); +status = system(cmdstr); +if status ~= 0 + if essential + cd(origDir) + error('gitUpdate:submodule:updateFailed', ... + 'Failed to pull latest changes for submodules:, %s', cmdout) + else + warning('gitUpdate:submodule:updateFailed', ... + 'Failed to pull latest changes for submodules:, %s', cmdout) + end +end + +cd(origDir) +end diff --git a/mc.m b/mc.m index d71b21a3..d3052b08 100644 --- a/mc.m +++ b/mc.m @@ -5,6 +5,8 @@ % 2013-06 CB created +% Ensure code is up-to-date +git.update; f = figure('Name', 'MC',... 'MenuBar', 'none',... 'Toolbar', 'none',... diff --git a/signals b/signals index 6fbd1855..bfb4acf8 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit 6fbd18551b63da12cf9b4fbeab5bccfaad70eba6 +Subproject commit bfb4acf8cf20e66e77917307297305fab2e087a0 From ef01a06b7a868e7c5525e12e8dfce8986c1679f1 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 19 Oct 2018 16:51:41 +0100 Subject: [PATCH 175/393] Support for new Alyx fields in sessions and water-administrations endpoints; removed AlyxInstance from property defaults --- +eui/AlyxPanel.m | 17 ++++++++--------- +eui/ExpPanel.m | 2 +- +exp/Experiment.m | 11 +++++++++-- +exp/SignalsExp.m | 15 ++++++++++++++- +srv/StimulusControl.m | 3 ++- .gitmodules | 4 ++-- alyx-matlab | 2 +- 7 files changed, 37 insertions(+), 17 deletions(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index c107b6f2..6d0783f0 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -146,9 +146,9 @@ % for future dates uicontrol('Parent', waterbox,... 'Style', 'pushbutton', ... - 'String', 'Give gel in future', ... + 'String', 'Give water in future', ... 'Enable', 'off',... - 'Callback', @(~,~)obj.giveFutureGel); + 'Callback', @(~,~)obj.giveFutureWater); % Check box to indicate whether water was gel or liquid obj.IsHydrogel = uicontrol('Parent', waterbox,... 'Style', 'checkbox', ... @@ -287,24 +287,23 @@ function giveWater(obj) % state of the 'is hydrogel' check box thisDate = now; amount = str2double(get(obj.WaterEntry, 'String')); - isHydrogel = logical(get(obj.IsHydrogel, 'Value')); + type = iff(get(obj.IsHydrogel, 'Value')==1, 'Hydrogel', 'Water'); if obj.AlyxInstance.IsLoggedIn && amount~=0 && ~isnan(amount) - wa = obj.AlyxInstance.postWater(obj.Subject, amount, thisDate, isHydrogel); + wa = obj.AlyxInstance.postWater(obj.Subject, amount, thisDate, type); if ~isempty(wa) % returned us a created water administration object successfully - wstr = iff(isHydrogel, 'Hydrogel', 'Water'); - obj.log('%s administration of %.2f for %s posted successfully to alyx', wstr, amount, obj.Subject); + obj.log('%s administration of %.2f for %s posted successfully to alyx', type, amount, obj.Subject); end end % update the water required text dispWaterReq(obj); end - function giveFutureGel(obj) + function giveFutureWater(obj) % Open a dialog allowing one to input water submissions for % future dates thisDate = now; prompt=sprintf('Enter space-separated numbers \n[tomorrow, day after that, day after that.. etc] \nEnter 0 to skip a day'); - answer = inputdlg(prompt,'Future Gel Amounts', [1 50]); + answer = inputdlg(prompt,'Future Amounts', [1 50]); if isempty(answer)||~obj.AlyxInstance.IsLoggedIn return % user pressed 'Close' or 'x' end @@ -312,7 +311,7 @@ function giveFutureGel(obj) weekendDates = thisDate + (1:length(amount)); for d = 1:length(weekendDates) if amount(d) > 0 - obj.AlyxInstance.postWater(obj.Subject, amount(d), weekendDates(d), 1); + obj.AlyxInstance.postWater(obj.Subject, amount(d), weekendDates(d), 'Water'); obj.log(['Hydrogel administration of %.2f for %s posted successfully to alyx for '... datestr(weekendDates(d))], amount(d), obj.Subject); end diff --git a/+eui/ExpPanel.m b/+eui/ExpPanel.m index f0ffcaf9..5ceab5dc 100644 --- a/+eui/ExpPanel.m +++ b/+eui/ExpPanel.m @@ -244,7 +244,7 @@ function expStopped(obj, rig, ~) end if ~any(amount); return; end % Return if no water was given try - ai.postWater(subject, amount*0.001, now, false); + ai.postWater(subject, amount*0.001, now, 'Water', ai.SessionURL); catch warning('Failed to post the water %s recieved during the experiment to Alyx', amount*0.001, subject); end diff --git a/+exp/Experiment.m b/+exp/Experiment.m index d78cdef5..b236aa93 100644 --- a/+exp/Experiment.m +++ b/+exp/Experiment.m @@ -786,8 +786,15 @@ function saveData(obj) obj.AlyxInstance.registerFile(savepaths{end}); % Save the session end time if ~isempty(obj.AlyxInstance.SessionURL) - obj.AlyxInstance.postData(obj.AlyxInstance.SessionURL,... - struct('end_time', obj.AlyxInstance.datestr(now), 'subject', subject), 'put'); + numTrials = obj.Data.numCompletedTrials; + if isfield(obj.Data, 'trial')&&isfield(obj.Data.trial, 'feedbackType') + numCorrect = sum([obj.Data.trial.feedbackType] == 1); + else + numCorrect = 0; + end + sessionData = struct('end_time', obj.AlyxInstance.datestr(now), ... + 'subject', subject, 'numberOfTrials', numTrials, 'numberOfCorrectTrials', numCorrect); + obj.AlyxInstance.postData(obj.AlyxInstance.SessionURL, sessionData, 'put'); else % Infer from date session and retrieve using expFilePath end diff --git a/+exp/SignalsExp.m b/+exp/SignalsExp.m index e83adb98..789ab556 100644 --- a/+exp/SignalsExp.m +++ b/+exp/SignalsExp.m @@ -891,8 +891,21 @@ function saveData(obj) % {subject, expDate, seq}, 'Block', []); % Save the session end time if ~isempty(obj.AlyxInstance.SessionURL) + numCorrect = []; + if isfield(obj.Data, 'events') + numTrials = length(obj.Data.events.endTrialValues); + if isfield(obj.Data.events, 'feedbackValues') + numCorrect = sum(obj.Data.events.feedbackValues == 1); + end + else + numTrials = 0; + numCorrect = 0; + end + sessionData = struct('end_time', obj.AlyxInstance.datestr(now), 'subject', subject); + if ~isempty(numTrials); sessionData.numberOfTrials = numTrials; end + if ~isempty(numCorrect); sessionData.numberOfCorrectTrials = numCorrect; end obj.AlyxInstance.postData(obj.AlyxInstance.SessionURL,... - struct('end_time', obj.AlyxInstance.datestr(now), 'subject', subject), 'put'); + sessionData, 'put'); else % Retrieve session from endpoint % subsessions = obj.AlyxInstance.getData(... diff --git a/+srv/StimulusControl.m b/+srv/StimulusControl.m index 9e6f8114..8147b603 100644 --- a/+srv/StimulusControl.m +++ b/+srv/StimulusControl.m @@ -43,7 +43,7 @@ end properties (Transient, Hidden) - AlyxInstance = Alyx('','') % Property to store rig specific Alyx token + AlyxInstance % Property to store rig specific Alyx instance end properties (Constant) @@ -67,6 +67,7 @@ end s = srv.StimulusControl; s.Name = name; + s.AlyxInstance = Alyx('',''); if isempty(regexp(uri, '^ws://', 'once')) uri = ['ws://' uri]; %default protocol prefix end diff --git a/.gitmodules b/.gitmodules index b6d3b966..8e0649d5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,14 +1,14 @@ [submodule "alyx-matlab"] path = alyx-matlab url = https://github.com/cortex-lab/alyx-matlab/ + branch = dev [submodule "signals"] path = signals url = https://github.com/cortex-lab/signals + branch = dev [submodule "npy-matlab"] path = npy-matlab url = https://github.com/kwikteam/npy-matlab [submodule "wheelAnalysis"] - path = wheelAnalysis - url = https://github.com/cortex-lab/wheelAnalysis.git diff --git a/alyx-matlab b/alyx-matlab index 480155b7..004d6c96 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit 480155b70bb8a4762400887f8ef6f05f84986099 +Subproject commit 004d6c9606d4a3d69f7f7fd638246a9ceb79b476 From 17db7f135310f266d3e1aa273ab4d8043627ac07 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 19 Oct 2018 16:53:35 +0100 Subject: [PATCH 176/393] Auto updating of code when mc and expServer start up; removed addpath from inferParameters --- +dat/paths.m | 1 + +exp/inferParameters.m | 11 +---------- +srv/expServer.m | 13 ++++++++----- cortexlab/+git/update.m | 41 +++++++++++++++++++++++++++++++++++++++++ mc.m | 2 ++ 5 files changed, 53 insertions(+), 15 deletions(-) create mode 100644 cortexlab/+git/update.m diff --git a/+dat/paths.m b/+dat/paths.m index c126e5a8..f1d06f83 100644 --- a/+dat/paths.m +++ b/+dat/paths.m @@ -27,6 +27,7 @@ p.localAlyxQueue = 'C:\localAlyxQueue'; p.databaseURL = 'https://alyx.cortexlab.net'; % p.databaseURL = 'https://dev.alyx.internationalbrainlab.org/'; +p.gitExe = 'C:\Program Files\Git\cmd\git.exe'; % Under the new system of having data grouped by mouse % rather than data type, all experimental data are saved here. diff --git a/+exp/inferParameters.m b/+exp/inferParameters.m index 9e4c7beb..945845d1 100644 --- a/+exp/inferParameters.m +++ b/+exp/inferParameters.m @@ -13,12 +13,9 @@ % end if ischar(expdef) && file.exists(expdef) expdeffun = fileFunction(expdef); - [funcDir, mfile] = fileparts(expdef); addpath(funcDir); - funArgs = nargin(str2func(mfile)); else expdeffun = expdef; expdef = which(func2str(expdef)); - funArgs = nargin(expdeffun); end net = sig.Net; @@ -34,14 +31,8 @@ try -% rig = 0; -% if funArgs == 7 - expdeffun(e.t, e.events, e.pars, e.visual, e.inputs, e.outputs, e.audio); -% else -% expdeffun(e.t, e.events, e.pars, e.visual, e.inputs, e.outputs, e.audio, rig); -% end + expdeffun(e.t, e.events, e.pars, e.visual, e.inputs, e.outputs, e.audio); -% expdeffun(e.t, e.events, e.pars, e.visual, e.inputs , e.outputs, e.audio); % paramNames will be the strings corresponding to the fields of e.pars % that the user tried to reference in her expdeffun. paramNames = e.pars.Subscripts.keys'; diff --git a/+srv/expServer.m b/+srv/expServer.m index c0706087..2af3eaf4 100644 --- a/+srv/expServer.m +++ b/+srv/expServer.m @@ -19,6 +19,8 @@ function expServer(useTimelineOverride, bgColour) rewardId = 1; %% Initialisation +% Pull latest changes from remote +git.update(true); % random seed random number generator rng('shuffle'); % communicator for receiving commands from clients @@ -170,14 +172,15 @@ function handleMessage(id, data, host) end case 'run' % exp run request - [expRef, preDelay, postDelay, Alyx] = args{:}; - Alyx.Headless = true; % Supress all dialog prompts + [expRef, preDelay, postDelay, AlyxInstance] = args{:}; + if isempty(AlyxInstance); AlyxInstance = Alyx('',''); end + AlyxInstance.Headless = true; % Supress all dialog prompts if dat.expExists(expRef) log('Starting experiment ''%s''', expRef); communicator.send(id, []); try communicator.send('status', {'starting', expRef}); - aborted = runExp(expRef, preDelay, postDelay, Alyx); + aborted = runExp(expRef, preDelay, postDelay, AlyxInstance); log('Experiment ''%s'' completed', expRef); communicator.send('status', {'completed', expRef, aborted}); catch runEx @@ -193,7 +196,7 @@ function handleMessage(id, data, host) case 'quit' if ~isempty(experiment) immediately = args{1}; - AlyxInstance = args{2}; + AlyxInstance = iff(isempty(args{2}), Alyx('',''), args{2}); AlyxInstance.Headless = true; if immediately log('Aborting experiment'); @@ -209,7 +212,7 @@ function handleMessage(id, data, host) log('Quit message received but no experiment is running\n'); end case 'updateAlyxInstance' %recieved new Alyx Instance from Stimulus Control - AlyxInstance = args{1}; %get struct + AlyxInstance = iff(isempty(args{1}), Alyx('',''), args{1}); AlyxInstance.Headless = true; if ~isempty(AlyxInstance) experiment.AlyxInstance = AlyxInstance; %set property for current experiment diff --git a/cortexlab/+git/update.m b/cortexlab/+git/update.m new file mode 100644 index 00000000..99d42ec9 --- /dev/null +++ b/cortexlab/+git/update.m @@ -0,0 +1,41 @@ +function update(fatalOnError) +% GIT.UPDATE Pull latest Rigbox code +% +% See also +if nargin == 0; fatalOnError = true; end +gitexepath = getOr(dat.paths, 'gitExe', 'C:\Program Files\Git\cmd\git.exe'); %TODO generalize +gitexepath = ['"', gitexepath, '"']; +root = fileparts(which('addRigboxPaths')); +origDir = pwd; +cd(root) + +cmdstr = strjoin({gitexepath, 'fetch'}); +[~, cmdout] = system(cmdstr); +if isempty(cmdout); return; end + +cmdstr = strjoin({gitexepath, 'merge'}); +[status, cmdout] = system(cmdstr); +if status ~= 0 + if fatalOnError + cd(origDir) + error('gitUpdate:pull:pullFailed', 'Failed to pull latest changes:, %s', cmdout) + else + warning('gitUpdate:pull:pullFailed', 'Failed to pull latest changes:, %s', cmdout) + end +end +% TODO: check if submodules are empty and use init flag +cmdstr = strjoin({gitexepath, 'submodule update --remote --merge'}); +status = system(cmdstr); +if status ~= 0 + if fatalOnError + cd(origDir) + error('gitUpdate:submodule:updateFailed', ... + 'Failed to pull latest changes for submodules:, %s', cmdout) + else + warning('gitUpdate:submodule:updateFailed', ... + 'Failed to pull latest changes for submodules:, %s', cmdout) + end +end + +cd(origDir) +end \ No newline at end of file diff --git a/mc.m b/mc.m index d71b21a3..071f4656 100644 --- a/mc.m +++ b/mc.m @@ -5,6 +5,8 @@ % 2013-06 CB created +% Pull latest changes from remote +git.update(true); f = figure('Name', 'MC',... 'MenuBar', 'none',... 'Toolbar', 'none',... From 971c002996aad885711aa76dc5c6c2c0d53b3691 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 19 Oct 2018 18:11:34 +0100 Subject: [PATCH 177/393] Hydrogel bool now false by default --- +eui/AlyxPanel.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index 6d0783f0..c66146dd 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -154,7 +154,7 @@ 'Style', 'checkbox', ... 'String', 'Hydrogel?', ... 'HorizontalAlignment', 'right',... - 'Value', true, ... + 'Value', false, ... 'Enable', 'off'); % Input for submitting amount of water obj.WaterEntry = uicontrol('Parent', waterbox,... From e6f101e9b482ab9f7141437dd13edca042408a53 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Mon, 22 Oct 2018 10:41:20 +0100 Subject: [PATCH 178/393] update to Alyx-matlab & branch rename --- alyx-matlab | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alyx-matlab b/alyx-matlab index 004d6c96..2c5e4977 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit 004d6c9606d4a3d69f7f7fd638246a9ceb79b476 +Subproject commit 2c5e497764d70c0a68fb001b2293c39345616b49 From a5e90b198e9d896a2e7d768d4c99d5d393c2401b Mon Sep 17 00:00:00 2001 From: jaib1 <jaib1@mit.edu> Date: Tue, 23 Oct 2018 12:01:24 +0100 Subject: [PATCH 179/393] signals dev branch changes into rigbox dev branch --- signals | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/signals b/signals index c44fab9c..13db4d1d 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit c44fab9c057a4828d64a6514d04ce9a3fb0d2f47 +Subproject commit 13db4d1d6a7e11427043b514867a3807eb1f12f7 From 83172d3cb168466f644f35fddfeba9720b095091 Mon Sep 17 00:00:00 2001 From: jaib1 <jaib1@mit.edu> Date: Tue, 23 Oct 2018 16:29:07 +0100 Subject: [PATCH 180/393] merged npy-matlab pull request to jaib1 from kwikteam of master branch --- npy-matlab | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/npy-matlab b/npy-matlab index a99e00f7..a7c4900b 160000 --- a/npy-matlab +++ b/npy-matlab @@ -1 +1 @@ -Subproject commit a99e00f78c72a7ec5f9c3074242ffaf242de9448 +Subproject commit a7c4900b62757e1b657f2cc983a5df3282abd674 From 038768eb5b2cbcaf66bd63eb7e312e4bfcf32388 Mon Sep 17 00:00:00 2001 From: jaib1 <jaib1@mit.edu> Date: Tue, 23 Oct 2018 17:47:13 +0100 Subject: [PATCH 181/393] changed .gitmodules to have submodules point to jaib1 github repos instead of cortex-lab repos --- .gitmodules | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.gitmodules b/.gitmodules index 8e0649d5..32b91d63 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,14 +1,16 @@ [submodule "alyx-matlab"] path = alyx-matlab - url = https://github.com/cortex-lab/alyx-matlab/ + url = https://github.com/jaib1/alyx-matlab/ branch = dev [submodule "signals"] path = signals - url = https://github.com/cortex-lab/signals + url = https://github.com/jaib1/signals branch = dev [submodule "npy-matlab"] path = npy-matlab - url = https://github.com/kwikteam/npy-matlab + url = https://github.com/jaib1/npy-matlab + branch = dev [submodule "wheelAnalysis"] path = wheelAnalysis - url = https://github.com/cortex-lab/wheelAnalysis.git + url = https://github.com/jaib1/wheelAnalysis + branch = dev From 186c6260fa0a580488bc4546f1fce30f75a1ab96 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 23 Oct 2018 18:54:12 +0100 Subject: [PATCH 182/393] Git update cd to orig path on early return --- alyx-matlab | 2 +- cortexlab/+git/update.m | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/alyx-matlab b/alyx-matlab index 2c5e4977..ddc6f766 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit 2c5e497764d70c0a68fb001b2293c39345616b49 +Subproject commit ddc6f766fc25e1a57cc6c61a90aa5083cbd46543 diff --git a/cortexlab/+git/update.m b/cortexlab/+git/update.m index 99d42ec9..c2544f0e 100644 --- a/cortexlab/+git/update.m +++ b/cortexlab/+git/update.m @@ -11,7 +11,10 @@ function update(fatalOnError) cmdstr = strjoin({gitexepath, 'fetch'}); [~, cmdout] = system(cmdstr); -if isempty(cmdout); return; end +if isempty(cmdout) + cd(origDir) + return +end cmdstr = strjoin({gitexepath, 'merge'}); [status, cmdout] = system(cmdstr); From 8cc73a5d75d335e05e20399721726ea62cedee30 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 23 Oct 2018 19:30:27 +0100 Subject: [PATCH 183/393] Echo in code update and fix to warning in ExpPanel --- +eui/ExpPanel.m | 2 +- cortexlab/+git/update.m | 20 +++++++++++--------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/+eui/ExpPanel.m b/+eui/ExpPanel.m index 5ceab5dc..e3434f14 100644 --- a/+eui/ExpPanel.m +++ b/+eui/ExpPanel.m @@ -246,7 +246,7 @@ function expStopped(obj, rig, ~) try ai.postWater(subject, amount*0.001, now, 'Water', ai.SessionURL); catch - warning('Failed to post the water %s recieved during the experiment to Alyx', amount*0.001, subject); + warning('Failed to post the %.2fml %s recieved during the experiment to Alyx', amount*0.001, subject); end end end diff --git a/cortexlab/+git/update.m b/cortexlab/+git/update.m index c2544f0e..1d5ca6c1 100644 --- a/cortexlab/+git/update.m +++ b/cortexlab/+git/update.m @@ -9,26 +9,28 @@ function update(fatalOnError) origDir = pwd; cd(root) +disp('Updating code...') + cmdstr = strjoin({gitexepath, 'fetch'}); -[~, cmdout] = system(cmdstr); -if isempty(cmdout) - cd(origDir) - return -end +system(cmdstr, '-echo'); +% if isempty(cmdout) +% cd(origDir) +% return +% end cmdstr = strjoin({gitexepath, 'merge'}); -[status, cmdout] = system(cmdstr); +[status, cmdout] = system(cmdstr, '-echo'); if status ~= 0 if fatalOnError cd(origDir) - error('gitUpdate:pull:pullFailed', 'Failed to pull latest changes:, %s', cmdout) + error('gitUpdate:pull:pullFailed', 'Failed to pull latest changes, %s', cmdout) else - warning('gitUpdate:pull:pullFailed', 'Failed to pull latest changes:, %s', cmdout) + warning('gitUpdate:pull:pullFailed', 'Failed to pull latest changes, %s', cmdout) end end % TODO: check if submodules are empty and use init flag cmdstr = strjoin({gitexepath, 'submodule update --remote --merge'}); -status = system(cmdstr); +status = system(cmdstr, '-echo'); if status ~= 0 if fatalOnError cd(origDir) From 1cccfc116d7103f9e4160c2febc64cbf1e4be12a Mon Sep 17 00:00:00 2001 From: Jai Bhagat <jaib1@mit.edu> Date: Thu, 25 Oct 2018 11:39:16 +0100 Subject: [PATCH 184/393] Changed .gitmodules to point back to cortex-lab subrepos for pull request --- .gitmodules | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.gitmodules b/.gitmodules index 32b91d63..0022ed5b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,16 +1,16 @@ [submodule "alyx-matlab"] path = alyx-matlab - url = https://github.com/jaib1/alyx-matlab/ + url = https://github.com/cortex-lab/alyx-matlab/ branch = dev [submodule "signals"] path = signals - url = https://github.com/jaib1/signals + url = https://github.com/cortex-lab/signals branch = dev [submodule "npy-matlab"] path = npy-matlab - url = https://github.com/jaib1/npy-matlab - branch = dev + url = https://github.com/kwikteam/npy-matlab + branch = master [submodule "wheelAnalysis"] path = wheelAnalysis - url = https://github.com/jaib1/wheelAnalysis - branch = dev + url = https://github.com/cortex-lab/wheelAnalysis + branch = master From 13c85a8ff09399f9335283589fd4c2e8e57b1fd7 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 25 Oct 2018 12:14:47 +0100 Subject: [PATCH 185/393] Reset lick counter NB: May ditch this method in future in favour of faster 'signalsy' approach --- +exp/SignalsExp.m | 1 + npy-matlab | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/+exp/SignalsExp.m b/+exp/SignalsExp.m index 789ab556..44b5f624 100644 --- a/+exp/SignalsExp.m +++ b/+exp/SignalsExp.m @@ -221,6 +221,7 @@ function useRig(obj, rig) obj.Wheel = rig.mouseInput; if isfield(rig, 'lickDetector') obj.LickDetector = rig.lickDetector; + obj.LickDetector.zero(); end if ~isempty(obj.DaqController.SignalGenerators) outputNames = fieldnames(obj.Outputs); % Get list of all outputs specified in expDef function diff --git a/npy-matlab b/npy-matlab index a7c4900b..a99e00f7 160000 --- a/npy-matlab +++ b/npy-matlab @@ -1 +1 @@ -Subproject commit a7c4900b62757e1b657f2cc983a5df3282abd674 +Subproject commit a99e00f78c72a7ec5f9c3074242ffaf242de9448 From ad3b8da26d6a05b35db38fec4d930a72c8f1e23a Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 25 Oct 2018 18:39:24 +0100 Subject: [PATCH 186/393] Lick detector zero'd + git.update uses 'where git' --- +exp/SignalsExp.m | 3 +++ cortexlab/+git/update.m | 11 +++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/+exp/SignalsExp.m b/+exp/SignalsExp.m index 44b5f624..0331d4bc 100644 --- a/+exp/SignalsExp.m +++ b/+exp/SignalsExp.m @@ -153,6 +153,8 @@ obj.Events.newTrial = net.origin('newTrial'); obj.Events.expStop = net.origin('expStop'); obj.Inputs.wheel = net.origin('wheel'); + obj.Inputs.wheelMM = obj.Inputs.wheel.map(@(x)obj.Wheel.MillimetresFactor*(x-obj.Wheel.ZeroOffset)); + obj.Inputs.wheelDeg = obj.Inputs.wheel.map(@(x)((x-obj.Wheel.ZeroOffset)/(1024*4))*360); obj.Inputs.lick = net.origin('lick'); obj.Inputs.keyboard = net.origin('keyboard'); % get global parameters & conditional parameters structs @@ -219,6 +221,7 @@ function useRig(obj, rig) end obj.DaqController = rig.daqController; obj.Wheel = rig.mouseInput; + obj.Wheel.zero(); if isfield(rig, 'lickDetector') obj.LickDetector = rig.lickDetector; obj.LickDetector.zero(); diff --git a/cortexlab/+git/update.m b/cortexlab/+git/update.m index 211fe203..14814d6b 100644 --- a/cortexlab/+git/update.m +++ b/cortexlab/+git/update.m @@ -16,11 +16,18 @@ function update(fatalOnError, scheduled) end disp('Updating code...') -gitexepath = getOr(dat.paths, 'gitExe', 'C:\Program Files\Git\cmd\git.exe'); %TODO generalize -gitexepath = ['"', gitexepath, '"']; +% Get the path to the Git exe +gitexepath = getOr(dat.paths, 'gitExe'); +if isempty(gitexepath) + [~,gitexepath] = system('where git'); +end +gitexepath = ['"', strtrim(gitexepath), '"']; + +% Temporarily change directory into Rigbox origDir = pwd; cd(root) +% Check if there are changes before pulling % cmdstr = strjoin({gitexepath, 'fetch'}); % system(cmdstr, '-echo'); % if isempty(cmdout) From 6ceb4e6fe76c6e42a8e253c62ff36d076e20734b Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 25 Oct 2018 18:49:33 +0100 Subject: [PATCH 187/393] Removed premature commit. Wheel now zero'd + where git in update func --- +exp/SignalsExp.m | 1 + cortexlab/+git/update.m | 11 +++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/+exp/SignalsExp.m b/+exp/SignalsExp.m index 44b5f624..d261de2c 100644 --- a/+exp/SignalsExp.m +++ b/+exp/SignalsExp.m @@ -219,6 +219,7 @@ function useRig(obj, rig) end obj.DaqController = rig.daqController; obj.Wheel = rig.mouseInput; + obj.Wheel.zero(); if isfield(rig, 'lickDetector') obj.LickDetector = rig.lickDetector; obj.LickDetector.zero(); diff --git a/cortexlab/+git/update.m b/cortexlab/+git/update.m index 211fe203..14814d6b 100644 --- a/cortexlab/+git/update.m +++ b/cortexlab/+git/update.m @@ -16,11 +16,18 @@ function update(fatalOnError, scheduled) end disp('Updating code...') -gitexepath = getOr(dat.paths, 'gitExe', 'C:\Program Files\Git\cmd\git.exe'); %TODO generalize -gitexepath = ['"', gitexepath, '"']; +% Get the path to the Git exe +gitexepath = getOr(dat.paths, 'gitExe'); +if isempty(gitexepath) + [~,gitexepath] = system('where git'); +end +gitexepath = ['"', strtrim(gitexepath), '"']; + +% Temporarily change directory into Rigbox origDir = pwd; cd(root) +% Check if there are changes before pulling % cmdstr = strjoin({gitexepath, 'fetch'}); % system(cmdstr, '-echo'); % if isempty(cmdout) From af0738b88338dcd49a96fbb350d961cfbf4bbfd5 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 25 Oct 2018 18:50:57 +0100 Subject: [PATCH 188/393] rm unfinished code --- +exp/SignalsExp.m | 2 -- 1 file changed, 2 deletions(-) diff --git a/+exp/SignalsExp.m b/+exp/SignalsExp.m index 0331d4bc..d261de2c 100644 --- a/+exp/SignalsExp.m +++ b/+exp/SignalsExp.m @@ -153,8 +153,6 @@ obj.Events.newTrial = net.origin('newTrial'); obj.Events.expStop = net.origin('expStop'); obj.Inputs.wheel = net.origin('wheel'); - obj.Inputs.wheelMM = obj.Inputs.wheel.map(@(x)obj.Wheel.MillimetresFactor*(x-obj.Wheel.ZeroOffset)); - obj.Inputs.wheelDeg = obj.Inputs.wheel.map(@(x)((x-obj.Wheel.ZeroOffset)/(1024*4))*360); obj.Inputs.lick = net.origin('lick'); obj.Inputs.keyboard = net.origin('keyboard'); % get global parameters & conditional parameters structs From 9ee295f718a9917f17aa3c1ab78f3e0306bf1c9c Mon Sep 17 00:00:00 2001 From: jaib1 <jaib1@mit.edu> Date: Fri, 26 Oct 2018 14:41:46 +0100 Subject: [PATCH 189/393] added future training flag to AlyxPanel --- +eui/AlyxPanel.m | 1336 +++++++++++++++++++++++----------------------- 1 file changed, 670 insertions(+), 666 deletions(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index 675b8787..9dc3e7ab 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -1,691 +1,695 @@ classdef AlyxPanel < handle - % EUI.ALYXPANEL A GUI for interating with the Alyx database - % This class is emplyed by mc (but may also be used stand-alone) to - % post weights and water administations to the Alyx database. - % - % eui.AlyxPanel() opens a stand-alone GUI. eui.AlyxPanel(parent) - % constructs the panel inside a parent object. - % - % Use the login button to retrieve a token from the database. - % Use the subject drop-down to select the subject. - % Subject weights can be entered using the 'Manual weighing' button. - % Previous weighings and water infomation can be viewed by pressing - % the 'Subject history' button. - % Water administrations can be recorded by entering a value in ml - % into the text box. Pressing return does not post the water, but - % updates the text to the right of the box, showing the amount of - % water remaining (i.e. the amount below the subject's calculated - % minimum requirement for that day. The check box to the right of - % the text box is to indicate whether the water was liquid - % (unchecked) or gel (checked). To post the water to Alyx, press the - % 'Give water' button. - % To post gel for future date (for example weekend hydrogel), Click - % the 'Give gel in future' button and enter in all the values - % starting at tomorrow then the day after, etc. - % The 'All WR subjects' button shows the amount of water remaining - % today for all mice that are currently on water restriction. - % - % The 'default' subject is for testing and is usually ignored. - % - % See also ALYX, EUI.MCONTROL - % - % 2017-03 NS created - % 2017-10 MW made into class - properties (SetAccess = private) - AlyxInstance % An Alyx object to interfacing with the database - SubjectList % List of active subjects from database - Subject = 'default' % The name of the currently selected subject - end - - properties (Access = private) - LoggingDisplay % Control for showing log output - RootContainer % Handle of the uix.Panel object named 'Alyx' - NewExpSubject % Drop-down menu subject list - LoginText % Text displaying whether/which user is logged in - LoginButton % Button to log in to Alyx - WeightButton % Button to submit weight to Alyx - WaterEntry % Text box for entering the amout of water to give - IsHydrogel % UI checkbox indicating whether to water to be given is in gel form - WaterRequiredText % Handle to text UI element displaying the water required - WaterRemainingText % Handle to text UI element displaying the water remaining - LoginTimer % Timer to keep track of how long the user has been logged in, when this expires the user is automatically logged out - WeightTimer % Timer to reset weight button text when scale no longer gives new readings - WaterRemaining % Holds the current water required for the selected subject - end - - events (NotifyAccess = 'protected') - Connected % Notified when logged in to database - Disconnected % Notified when logged out of database - end - - methods - function obj = AlyxPanel(parent, active) - % Constructor to build all the UI elements and set callbacks to the - % relevant functions. If a handle to parant UI object is not - % specified, a seperate figure is created. An optional handle to a - % logging display panal may be provided, otherwise one is created. If - % the active flag is set to false (default is true), the panel is - % inactive and the instance of Alyx will be set to headless. - % - % See also Alyx - - obj.AlyxInstance = Alyx('',''); - if ~nargin % No parant object: create new figure - f = figure('Name', 'alyx GUI',... - 'MenuBar', 'none',... - 'Toolbar', 'none',... - 'NumberTitle', 'off',... - 'Units', 'normalized',... - 'OuterPosition', [0.1 0.1 0.4 .4]); - parent = uiextras.VBox('Parent', f,... - 'Visible', 'on'); - % subject selector - sbox = uix.HBox('Parent', parent); - bui.label('Select subject: ', sbox); - obj.NewExpSubject = bui.Selector(sbox, {'default'}); % Subject dropdown box - % set a callback on subject selection so that we can show water - % requirements for new mice as they are selected. This should - % be set by any other GUI that instantiates this object (e.g. - % MControl using this as a panel. - obj.NewExpSubject.addlistener('SelectionChanged', @(src, evt)obj.dispWaterReq(src, evt)); - end - - % Default to active AlyxPanel - if nargin < 2; active = true; end - - obj.RootContainer = uix.Panel('Parent', parent, 'Title', 'Alyx'); - alyxbox = uiextras.VBox('Parent', obj.RootContainer); - - loginbox = uix.HBox('Parent', alyxbox); - % Login infomation - obj.LoginText = bui.label('Not logged in', loginbox); - % Button to log in and out of Alyx - obj.LoginButton = uicontrol('Parent', loginbox,... - 'Style', 'pushbutton', ... - 'String', 'Login', ... - 'Enable', 'on',... - 'Callback', @(~,~)obj.login); - loginbox.Widths = [-1 75]; - - % If active flag set as false, make Alyx headless - if ~active - obj.AlyxInstance.Headless = true; - set(obj.LoginButton, 'Enable', 'off') - end - - waterReqbox = uix.HBox('Parent', alyxbox); - obj.WaterRequiredText = bui.label('Log in to see water requirements', waterReqbox); % water required text - % Button to refresh all data retrieved from Alyx - uicontrol('Parent', waterReqbox,... - 'Style', 'pushbutton', ... - 'String', 'Refresh', ... - 'Enable', 'off',... - 'Callback', @(~,~)obj.dispWaterReq); - waterReqbox.Widths = [-1 75]; - - waterbox = uix.HBox('Parent', alyxbox); - % Button to launch a dialog displaying water and weight info for a given mouse - uicontrol('Parent', waterbox,... - 'Style', 'pushbutton', ... - 'String', 'Subject history', ... - 'Enable', 'off',... - 'Callback', @(~,~)obj.viewSubjectHistory); - % Button to launch a dialog displaying water and weight info for all mice - uicontrol('Parent', waterbox,... - 'Style', 'pushbutton', ... - 'String', 'All WR subjects', ... - 'Enable', 'off',... - 'Callback', @(~,~)obj.viewAllSubjects); - % Button to open a dialog for manually submitting a mouse weight - obj.WeightButton = uicontrol('Parent', waterbox,... - 'Style', 'pushbutton', ... - 'String', 'Manual weighing', ... - 'Enable', 'off',... - 'Callback', @(~,~)obj.recordWeight); - % Button to launch dialog for submitting gel administrations - % for future dates - uicontrol('Parent', waterbox,... - 'Style', 'pushbutton', ... - 'String', 'Give water in future', ... - 'Enable', 'off',... - 'Callback', @(~,~)obj.giveFutureWater); - % Check box to indicate whether water was gel or liquid - obj.IsHydrogel = uicontrol('Parent', waterbox,... - 'Style', 'checkbox', ... - 'String', 'Hydrogel?', ... - 'HorizontalAlignment', 'right',... - 'Value', false, ... - 'Enable', 'off'); - % Input for submitting amount of water - obj.WaterEntry = uicontrol('Parent', waterbox,... - 'Style', 'edit',... - 'BackgroundColor', [1 1 1],... - 'HorizontalAlignment', 'right',... - 'Enable', 'off',... - 'String', '0.00', ... - 'Callback', @(src, evt)obj.changeWaterText(src, evt)); - % Button for submitting water administration - uicontrol('Parent', waterbox,... - 'Style', 'pushbutton', ... - 'String', 'Give water', ... - 'Enable', 'off',... - 'Callback', @(~,~)giveWater(obj)); - % Label Indicating the amount of water remaining - obj.WaterRemainingText = bui.label('[]', waterbox); - waterbox.Widths = [100 100 100 100 75 75 75 75]; - - launchbox = uix.HBox('Parent', alyxbox); - % Button for launching subject page in browser - uicontrol('Parent', launchbox,... - 'Style', 'pushbutton', ... - 'String', 'Launch webpage for Subject', ... - 'Enable', 'off',... - 'Callback', @(~,~)obj.launchSubjectURL); - % Button for launching (and creating) a session for a given subject in the browser - uicontrol('Parent', launchbox,... - 'Style', 'pushbutton', ... - 'String', 'Launch webpage for Session', ... - 'Enable', 'off',... - 'Callback', @(~,~)obj.launchSessionURL); - - if ~nargin - % logging message area - obj.LoggingDisplay = uicontrol('Parent', parent, 'Style', 'listbox',... - 'Enable', 'inactive', 'String', {}); - parent.Sizes = [50 150 150]; - else - % Use parent's logging display - obj.LoggingDisplay = findobj('Tag', 'Logging Display'); - end + % EUI.ALYXPANEL A GUI for interating with the Alyx database + % This class is emplyed by mc (but may also be used stand-alone) to + % post weights and water administations to the Alyx database. + % + % eui.AlyxPanel() opens a stand-alone GUI. eui.AlyxPanel(parent) + % constructs the panel inside a parent object. + % + % Use the login button to retrieve a token from the database. + % Use the subject drop-down to select the subject. + % Subject weights can be entered using the 'Manual weighing' button. + % Previous weighings and water infomation can be viewed by pressing + % the 'Subject history' button. + % Water administrations can be recorded by entering a value in ml + % into the text box. Pressing return does not post the water, but + % updates the text to the right of the box, showing the amount of + % water remaining (i.e. the amount below the subject's calculated + % minimum requirement for that day. The check box to the right of + % the text box is to indicate whether the water was liquid + % (unchecked) or gel (checked). To post the water to Alyx, press the + % 'Give water' button. + % To post gel for future date (for example weekend hydrogel), Click + % the 'Give gel in future' button and enter in all the values + % starting at tomorrow then the day after, etc. + % The 'All WR subjects' button shows the amount of water remaining + % today for all mice that are currently on water restriction. + % + % The 'default' subject is for testing and is usually ignored. + % + % See also ALYX, EUI.MCONTROL + % + % 2017-03 NS created + % 2017-10 MW made into class + properties (SetAccess = private) + AlyxInstance % An Alyx object to interfacing with the database + SubjectList % List of active subjects from database + Subject = 'default' % The name of the currently selected subject end - function delete(obj) - % To be called before destroying AlyxPanel object. Deletes the - % loggin timer - disp('AlyxPanel destructor called'); - if obj.RootContainer.isvalid; delete(obj.RootContainer); end - if ~isempty(obj.LoginTimer) % If there is a timer object - stop(obj.LoginTimer) % Stop the timer... - delete(obj.LoginTimer) % ... delete it... - obj.LoginTimer = []; % ... and remove it - end + properties (Access = private) + LoggingDisplay % Control for showing log output + RootContainer % Handle of the uix.Panel object named 'Alyx' + NewExpSubject % Drop-down menu subject list + LoginText % Text displaying whether/which user is logged in + LoginButton % Button to log in to Alyx + WeightButton % Button to submit weight to Alyx + WaterEntry % Text box for entering the amout of water to give + IsHydrogel % UI checkbox indicating whether to water to be given is in gel form + WaterRequiredText % Handle to text UI element displaying the water required + WaterRemainingText % Handle to text UI element displaying the water remaining + LoginTimer % Timer to keep track of how long the user has been logged in, when this expires the user is automatically logged out + WeightTimer % Timer to reset weight button text when scale no longer gives new readings + WaterRemaining % Holds the current water required for the selected subject end - function login(obj) - % Used both to log in and out of Alyx. Logging means to - % generate an Alyx token with which to send/request data. - % Logging out does not cause the token to expire, instead the - % token is simply deleted from this object. - - % Reset headless flag in case user wishes to retry connection - obj.AlyxInstance.Headless = false; - % Are we logging in or out? - if ~obj.AlyxInstance.IsLoggedIn % logging in - % attempt login - obj.AlyxInstance = obj.AlyxInstance.login(); % returns an instance if success, empty if you cancel - if obj.AlyxInstance.IsLoggedIn % successful - % Start log in timer, to automatically log out after 30 - % minutes of 'inactivity' (defined as not calling - % dispWaterReq) - obj.LoginTimer = timer('StartDelay', 30*60, 'TimerFcn',... - @(~,~)obj.login, 'BusyMode', 'queue', 'Name', 'Login Timer'); - start(obj.LoginTimer) - % Enable all buttons - set(findall(obj.RootContainer, '-property', 'Enable'), 'Enable', 'on'); - set(obj.LoginText, 'ForegroundColor', 'black',... - 'String', ['You are logged in as ', obj.AlyxInstance.User]); % display which user is logged in - set(obj.LoginButton, 'String', 'Logout'); - - % try updating the subject selectors in other panels - newSubs = obj.AlyxInstance.listSubjects; - obj.NewExpSubject.Option = newSubs; - obj.SubjectList = newSubs; - - notify(obj, 'Connected'); % Notify listeners of login - obj.log('Logged into Alyx successfully as %s', obj.AlyxInstance.User); - - % any database subjects that weren't in the old list of - % subjects will need a folder in the main repository. - firstTimeSubs = newSubs(~ismember(newSubs, dat.listSubjects)); - for fts = 1:length(firstTimeSubs) - thisDir = fullfile(dat.reposPath('main', 'master'), firstTimeSubs{fts}); - if ~exist(thisDir, 'dir') - fprintf(1, 'making directory for %s\n', firstTimeSubs{fts}); - mkdir(thisDir); - end - end - elseif obj.AlyxInstance.Headless - % Panel inactive or login failed due to Alyx being down - set(findall(obj.RootContainer, '-property', 'Enable'), 'Enable', 'on'); - set(obj.LoginText, 'ForegroundColor', [0.91, 0.41, 0.17],... - 'String', 'Unable to reach Alyx, posts to be queued'); - set(obj.LoginButton, 'String', 'Retry'); % Retry button - obj.log('Failed to reach Alyx server, please retry later'); - else - obj.log('Did not log into Alyx'); - end - else % logging out - obj.AlyxInstance = obj.AlyxInstance.logout; - if ~isempty(obj.LoginTimer) % If there is a timer object - stop(obj.LoginTimer) % Stop the timer... - delete(obj.LoginTimer) % ... delete it... - obj.LoginTimer = []; % ... and remove it - end - set(obj.LoginText, 'String', 'Not logged in') - % Disable all buttons - set(findall(obj.RootContainer, '-property', 'Enable'), 'Enable', 'off') - set(obj.LoginButton, 'Enable', 'on', 'String', 'Login') % ... except the login button - notify(obj, 'Disconnected'); % Notify listeners of logout - obj.log('Logged out of Alyx'); - end - obj.dispWaterReq() - end - - function giveWater(obj) - % Callback to the give water button. Posts the value entered - % in the text box as either liquid or gel depending on the - % state of the 'is hydrogel' check box - thisDate = now; - amount = str2double(get(obj.WaterEntry, 'String')); - type = iff(get(obj.IsHydrogel, 'Value')==1, 'Hydrogel', 'Water'); - if obj.AlyxInstance.IsLoggedIn && amount~=0 && ~isnan(amount) - wa = obj.AlyxInstance.postWater(obj.Subject, amount, thisDate, type); - if ~isempty(wa) % returned us a created water administration object successfully - obj.log('%s administration of %.2f for %s posted successfully to alyx', type, amount, obj.Subject); - end - end - % update the water required text - dispWaterReq(obj); + events (NotifyAccess = 'protected') + Connected % Notified when logged in to database + Disconnected % Notified when logged out of database end - function giveFutureWater(obj) - % Open a dialog allowing one to input water submissions for - % future dates - thisDate = now; - prompt=sprintf('Enter space-separated numbers \n[tomorrow, day after that, day after that.. etc] \nEnter 0 to skip a day'); - answer = inputdlg(prompt,'Future Amounts', [1 50]); - if isempty(answer)||~obj.AlyxInstance.IsLoggedIn - return % user pressed 'Close' or 'x' - end - amount = str2num(answer{:}); %#ok<ST2NM> - weekendDates = thisDate + (1:length(amount)); - for d = 1:length(weekendDates) - if amount(d) > 0 - obj.AlyxInstance.postWater(obj.Subject, amount(d), weekendDates(d), 'Water'); - obj.log(['Hydrogel administration of %.2f for %s posted successfully to alyx for '... - datestr(weekendDates(d))], amount(d), obj.Subject); + methods + function obj = AlyxPanel(parent, active) + % Constructor to build all the UI elements and set callbacks to the + % relevant functions. If a handle to parant UI object is not + % specified, a seperate figure is created. An optional handle to a + % logging display panal may be provided, otherwise one is created. If + % the active flag is set to false (default is true), the panel is + % inactive and the instance of Alyx will be set to headless. + % + % See also Alyx + + obj.AlyxInstance = Alyx('',''); + if ~nargin % No parant object: create new figure + f = figure('Name', 'alyx GUI',... + 'MenuBar', 'none',... + 'Toolbar', 'none',... + 'NumberTitle', 'off',... + 'Units', 'normalized',... + 'OuterPosition', [0.1 0.1 0.4 .4]); + parent = uiextras.VBox('Parent', f,... + 'Visible', 'on'); + % subject selector + sbox = uix.HBox('Parent', parent); + bui.label('Select subject: ', sbox); + obj.NewExpSubject = bui.Selector(sbox, {'default'}); % Subject dropdown box + % set a callback on subject selection so that we can show water + % requirements for new mice as they are selected. This should + % be set by any other GUI that instantiates this object (e.g. + % MControl using this as a panel. + obj.NewExpSubject.addlistener('SelectionChanged', @(src, evt)obj.dispWaterReq(src, evt)); + end + + % Default to active AlyxPanel + if nargin < 2; active = true; end + + obj.RootContainer = uix.Panel('Parent', parent, 'Title', 'Alyx'); + alyxbox = uiextras.VBox('Parent', obj.RootContainer); + + loginbox = uix.HBox('Parent', alyxbox); + % Login infomation + obj.LoginText = bui.label('Not logged in', loginbox); + % Button to log in and out of Alyx + obj.LoginButton = uicontrol('Parent', loginbox,... + 'Style', 'pushbutton', ... + 'String', 'Login', ... + 'Enable', 'on',... + 'Callback', @(~,~)obj.login); + loginbox.Widths = [-1 75]; + + % If active flag set as false, make Alyx headless + if ~active + obj.AlyxInstance.Headless = true; + set(obj.LoginButton, 'Enable', 'off') + end + + waterReqbox = uix.HBox('Parent', alyxbox); + obj.WaterRequiredText = bui.label('Log in to see water requirements', waterReqbox); % water required text + % Button to refresh all data retrieved from Alyx + uicontrol('Parent', waterReqbox,... + 'Style', 'pushbutton', ... + 'String', 'Refresh', ... + 'Enable', 'off',... + 'Callback', @(~,~)obj.dispWaterReq); + waterReqbox.Widths = [-1 75]; + + waterbox = uix.HBox('Parent', alyxbox); + % Button to launch a dialog displaying water and weight info for a given mouse + uicontrol('Parent', waterbox,... + 'Style', 'pushbutton', ... + 'String', 'Subject history', ... + 'Enable', 'off',... + 'Callback', @(~,~)obj.viewSubjectHistory); + % Button to launch a dialog displaying water and weight info for all mice + uicontrol('Parent', waterbox,... + 'Style', 'pushbutton', ... + 'String', 'All WR subjects', ... + 'Enable', 'off',... + 'Callback', @(~,~)obj.viewAllSubjects); + % Button to open a dialog for manually submitting a mouse weight + obj.WeightButton = uicontrol('Parent', waterbox,... + 'Style', 'pushbutton', ... + 'String', 'Manual weighing', ... + 'Enable', 'off',... + 'Callback', @(~,~)obj.recordWeight); + % Button to launch dialog for submitting gel administrations + % for future dates + uicontrol('Parent', waterbox,... + 'Style', 'pushbutton', ... + 'String', 'Give water in future', ... + 'Enable', 'off',... + 'Callback', @(~,~)obj.giveFutureWater); + % Check box to indicate whether water was gel or liquid + obj.IsHydrogel = uicontrol('Parent', waterbox,... + 'Style', 'checkbox', ... + 'String', 'Hydrogel?', ... + 'HorizontalAlignment', 'right',... + 'Value', false, ... + 'Enable', 'off'); + % Input for submitting amount of water + obj.WaterEntry = uicontrol('Parent', waterbox,... + 'Style', 'edit',... + 'BackgroundColor', [1 1 1],... + 'HorizontalAlignment', 'right',... + 'Enable', 'off',... + 'String', '0.00', ... + 'Callback', @(src, evt)obj.changeWaterText(src, evt)); + % Button for submitting water administration + uicontrol('Parent', waterbox,... + 'Style', 'pushbutton', ... + 'String', 'Give water', ... + 'Enable', 'off',... + 'Callback', @(~,~)giveWater(obj)); + % Label Indicating the amount of water remaining + obj.WaterRemainingText = bui.label('[]', waterbox); + waterbox.Widths = [100 100 100 100 75 75 75 75]; + + launchbox = uix.HBox('Parent', alyxbox); + % Button for launching subject page in browser + uicontrol('Parent', launchbox,... + 'Style', 'pushbutton', ... + 'String', 'Launch webpage for Subject', ... + 'Enable', 'off',... + 'Callback', @(~,~)obj.launchSubjectURL); + % Button for launching (and creating) a session for a given subject in the browser + uicontrol('Parent', launchbox,... + 'Style', 'pushbutton', ... + 'String', 'Launch webpage for Session', ... + 'Enable', 'off',... + 'Callback', @(~,~)obj.launchSessionURL); + + if ~nargin + % logging message area + obj.LoggingDisplay = uicontrol('Parent', parent, 'Style', 'listbox',... + 'Enable', 'inactive', 'String', {}); + parent.Sizes = [50 150 150]; + else + % Use parent's logging display + obj.LoggingDisplay = findobj('Tag', 'Logging Display'); + end end - end - end - - function dispWaterReq(obj, src, ~) - % Display the amount of water required by the selected subject - % for it to reach its minimum requirement. This function is - % also used to update the selected subject, for example it is - % this funtion to use as a callback to subject dropdown - % listeners - ai = obj.AlyxInstance; - % Set the selected subject if it is an input - if nargin>1; obj.Subject = src.Selected; end - if ~ai.IsLoggedIn - set(obj.WaterRequiredText, 'ForegroundColor', 'black',... - 'String', 'Log in to see water requirements'); - return - end - % Refresh the timer as the user isn't inactive - stop(obj.LoginTimer); start(obj.LoginTimer) - try - s = ai.getData('water-restricted-subjects'); % struct with data about restricted subjects - idx = strcmp(obj.Subject, {s.nickname}); - if ~any(idx) % Subject not on water restriction - set(obj.WaterRequiredText, 'ForegroundColor', 'black',... - 'String', sprintf('Subject %s not on water restriction', obj.Subject)); - else - % Get information on weight and water given - endpnt = sprintf('water-requirement/%s?start_date=%s&end_date=%s',... - obj.Subject, datestr(now, 'yyyy-mm-dd'),datestr(now, 'yyyy-mm-dd')); - wr = ai.getData(endpnt); % Get today's weight and water record - if ~isempty(wr.records) - record = wr.records(end); - else - record = struct(); - end - weight = getOr(record, 'weight_measured', NaN); % Get today's measured weight - water = getOr(record, 'water_given', 0); % Get total water given - gel = getOr(record, 'hydrogel_given', 0); % Get total gel given - weight_expected = getOr(record, 'weight_expected', NaN); - % Set colour based on weight percentage - weight_pct = (weight-wr.implant_weight)/(weight_expected-wr.implant_weight); - if weight_pct < 0.8 % Mouse below 80% original weight - colour = [0.91, 0.41, 0.17]; % Orange - weight_pct = '< 80%'; - elseif weight_pct < 0.7 % Mouse below 70% original weight - colour = 'red'; - weight_pct = '< 70%'; - else - colour = 'black'; % Mouse above 80% or no weight measured today - weight_pct = '> 80%'; - end - % Round up water remaining to the near 0.01 - remainder = obj.round(s(idx).water_requirement_remaining, 'up'); - % Set text - set(obj.WaterRequiredText, 'ForegroundColor', colour, 'String', ... - sprintf(['Subject %s requires %.2f of %.2f today\n\t '... - 'Weight today: %.2f (%s) Water today: %.2f'], obj.Subject, ... - remainder, obj.round(s(idx).water_requirement_total, 'up'), weight, ... - weight_pct, obj.round(sum([water gel]), 'down'))); - % Set WaterRemaining attribute for changeWaterText callback - obj.WaterRemaining = remainder; + + function delete(obj) + % To be called before destroying AlyxPanel object. Deletes the + % loggin timer + disp('AlyxPanel destructor called'); + if obj.RootContainer.isvalid; delete(obj.RootContainer); end + if ~isempty(obj.LoginTimer) % If there is a timer object + stop(obj.LoginTimer) % Stop the timer... + delete(obj.LoginTimer) % ... delete it... + obj.LoginTimer = []; % ... and remove it + end end - catch me - d = me.message; %FIXME: JSON no longer returned - if isfield(d, 'detail') && strcmp(d.detail, 'Not found.') - set(obj.WaterRequiredText, 'ForegroundColor', 'black',... - 'String', sprintf('Subject %s not found in alyx', obj.Subject)); + + function login(obj) + % Used both to log in and out of Alyx. Logging means to + % generate an Alyx token with which to send/request data. + % Logging out does not cause the token to expire, instead the + % token is simply deleted from this object. + + % Reset headless flag in case user wishes to retry connection + obj.AlyxInstance.Headless = false; + % Are we logging in or out? + if ~obj.AlyxInstance.IsLoggedIn % logging in + % attempt login + obj.AlyxInstance = obj.AlyxInstance.login(); % returns an instance if success, empty if you cancel + if obj.AlyxInstance.IsLoggedIn % successful + % Start log in timer, to automatically log out after 30 + % minutes of 'inactivity' (defined as not calling + % dispWaterReq) + obj.LoginTimer = timer('StartDelay', 30*60, 'TimerFcn',... + @(~,~)obj.login, 'BusyMode', 'queue', 'Name', 'Login Timer'); + start(obj.LoginTimer) + % Enable all buttons + set(findall(obj.RootContainer, '-property', 'Enable'), 'Enable', 'on'); + set(obj.LoginText, 'ForegroundColor', 'black',... + 'String', ['You are logged in as ', obj.AlyxInstance.User]); % display which user is logged in + set(obj.LoginButton, 'String', 'Logout'); + + % try updating the subject selectors in other panels + newSubs = obj.AlyxInstance.listSubjects; + obj.NewExpSubject.Option = newSubs; + obj.SubjectList = newSubs; + + notify(obj, 'Connected'); % Notify listeners of login + obj.log('Logged into Alyx successfully as %s', obj.AlyxInstance.User); + + % any database subjects that weren't in the old list of + % subjects will need a folder in the main repository. + firstTimeSubs = newSubs(~ismember(newSubs, dat.listSubjects)); + for fts = 1:length(firstTimeSubs) + thisDir = fullfile(dat.reposPath('main', 'master'), firstTimeSubs{fts}); + if ~exist(thisDir, 'dir') + fprintf(1, 'making directory for %s\n', firstTimeSubs{fts}); + mkdir(thisDir); + end + end + elseif obj.AlyxInstance.Headless + % Panel inactive or login failed due to Alyx being down + set(findall(obj.RootContainer, '-property', 'Enable'), 'Enable', 'on'); + set(obj.LoginText, 'ForegroundColor', [0.91, 0.41, 0.17],... + 'String', 'Unable to reach Alyx, posts to be queued'); + set(obj.LoginButton, 'String', 'Retry'); % Retry button + obj.log('Failed to reach Alyx server, please retry later'); + else + obj.log('Did not log into Alyx'); + end + else % logging out + obj.AlyxInstance = obj.AlyxInstance.logout; + if ~isempty(obj.LoginTimer) % If there is a timer object + stop(obj.LoginTimer) % Stop the timer... + delete(obj.LoginTimer) % ... delete it... + obj.LoginTimer = []; % ... and remove it + end + set(obj.LoginText, 'String', 'Not logged in') + % Disable all buttons + set(findall(obj.RootContainer, '-property', 'Enable'), 'Enable', 'off') + set(obj.LoginButton, 'Enable', 'on', 'String', 'Login') % ... except the login button + notify(obj, 'Disconnected'); % Notify listeners of logout + obj.log('Logged out of Alyx'); + end + obj.dispWaterReq() end - end - end - - function changeWaterText(obj, src, ~) - % Update the panel text to show the amount of water still - % required for the subject to reach its minimum requirement. - % This text is updated before the value in the water text box - % has been posted to Alyx. For example if the user is unsure - % how much gel over the minimum they have weighed out, pressing - % return will display this without posting to Alyx - % - % See also DISPWATERREQ, GIVEWATER - if obj.AlyxInstance.IsLoggedIn && ~isempty(obj.WaterRemaining) - rem = obj.WaterRemaining; - curr = str2double(src.String); - set(obj.WaterRemainingText, 'String', sprintf('(%.2f)', rem-curr)); - end - end - - function recordWeight(obj, weight, subject) - % Post a subject's weight to Alyx. If no inputs are provided, - % create an input dialog for the user to input a weight. If no - % subject is provided, use this object's currently selected - % subject. - % - % See also VIEWSUBJECTHISTORY, VIEWALLSUBJECTS - ai = obj.AlyxInstance; - if nargin < 3; subject = obj.Subject; end - if nargin < 2 - prompt = {sprintf('weight of %s:', subject)}; - dlgTitle = 'Manual weight logging'; - numLines = 1; - defaultAns = {'',''}; - weight = inputdlg(prompt, dlgTitle, numLines, defaultAns); - if isempty(weight); return; end - end - % inputdlg returns weight as a cell, otherwise it may now be - weight = ensureCell(weight); % ensure it's a cell - % convert to double if weight is a string - weight = iff(ischar(weight{1}), str2double(weight{1}), weight{1}); - try - w = postWeight(ai, weight, subject); - obj.log('Alyx weight posting succeeded: %.2f for %s', w.weight, w.subject); - catch - if ~ai.IsLoggedIn % if not logged in, save the weight for later - obj.log('Warning: Weight not posted to Alyx; will be posted upon login.'); - else - obj.log('Warning: Alyx weight posting failed!'); + + function giveWater(obj) + % Callback to the give water button. Posts the value entered + % in the text box as either liquid or gel depending on the + % state of the 'is hydrogel' check box + thisDate = now; + amount = str2double(get(obj.WaterEntry, 'String')); + type = iff(get(obj.IsHydrogel, 'Value')==1, 'Hydrogel', 'Water'); + if obj.AlyxInstance.IsLoggedIn && amount~=0 && ~isnan(amount) + wa = obj.AlyxInstance.postWater(obj.Subject, amount, thisDate, type); + if ~isempty(wa) % returned us a created water administration object successfully + obj.log('%s administration of %.2f for %s posted successfully to alyx', type, amount, obj.Subject); + end + end + % update the water required text + dispWaterReq(obj); end - end - % Update weight and refresh login timer - obj.dispWaterReq - end - - function launchSessionURL(obj) - % Launch the Webpage for the current base session in the - % default Web browser. If no session exists for today's date, - % a new base session is created accordingly. - % - % See also LAUNCHSUBJECTURL - ai = obj.AlyxInstance; - % determine whether there is a session for this subj and date - thisDate = ai.datestr(now); - sessions = ai.getData(['sessions?type=Base&subject=' obj.Subject]); - - % If the date of this latest base session is not the same date - % as today, then create a new one for today - if isempty(sessions) || ~strcmp(sessions{end}.start_time(1:10), thisDate(1:10)) - % Ask user whether he/she wants to create new session - % Construct a questdlg with three options - choice = questdlg('Would you like to create a new base session?', ... - ['No base session exists for ' datestr(now, 'yyyy-mm-dd')], ... - 'Yes','No','No'); - % Handle response - switch choice - case 'Yes' - % Create our base session - d = struct; - d.subject = obj.Subject; - d.procedures = {'Behavior training/tasks'}; - d.narrative = 'auto-generated session'; - d.start_time = thisDate; - d.type = 'Base'; + + function giveFutureWater(obj) + % Open a dialog allowing one to input water submissions for + % future dates + thisDate = now; + prompt=sprintf('Enter space-separated numbers \n[tomorrow, day after that, day after that.. etc] \nEnter "0" to skip a day\nEnter "-1" to indicate training for that day'); + a=inputdlg(prompt,'Future Amounts', [1 50]); + if isempty(answer)||~obj.AlyxInstance.IsLoggedIn + return % user pressed 'Close' or 'x' + end + amt = str2num(answer{:}); % amount of water + futDates = thisDate + (1:length(amt)); %datenum of all input future dates - thisSess = ai.postData('sessions', d); - if ~isfield(thisSess,'subject') % fail - warning('Submitted base session did not return appropriate values'); - warning('Submitted data below:'); - disp(d) - warning('Return values below:'); - disp(thisSess) - return - else % success - obj.log(['Created new base session in Alyx for ' obj.Subject]); + futTrnDates = futDates(amt<0); %future training dates + futWtrDates = futDates(amt>0); %future water giving dates + amtWtrDates = amt(amt>0); %amount of water to give on future water dates + dat.saveParamProfile('WeekendWater', obj.Subject, futTrnDates); + + for d = 1:length(futWtrDates) + obj.AlyxInstance.postWater(obj.Subject, amtWtrDates(d), futWtrDates(d), 'Water'); + obj.log(['Hydrogel administration of %.2f for %s posted successfully to alyx for '... + datestr(futWtrDates(d))], amtWtrDates(d), obj.Subject); end - case 'No' - return end - else - thisSess = sessions{end}; - end - - % parse the uuid from the url in the session object - u = thisSess.url; - uuid = u(find(u=='/', 1, 'last')+1:end); - - % make the admin url - adminURL = fullfile(ai.BaseURL, 'admin', 'actions', 'session', uuid, 'change'); - - % launch the website - web(adminURL, '-browser'); - end - - function launchSubjectURL(obj) - ai = obj.AlyxInstance; - if ai.IsLoggedIn - s = ai.getData(ai.makeEndpoint(['subjects/' obj.Subject])); - subjURL = fullfile(ai.BaseURL, 'admin', 'subjects', 'subject', s.id, 'change'); % this is wrong - need uuid - web(subjURL, '-browser'); - end - end - - function viewSubjectHistory(obj, ax) - % View historical information about a subject. - % Opens a new window and plots a set of weight graphs as well - % as displaying a table with the water and weight entries for - % the selected subject. If an axes handle is provided, this - % function plots a single weight graph - - % If not logged in or 'default' is selected, return - if ~obj.AlyxInstance.IsLoggedIn||strcmp(obj.Subject, 'default'); return; end - % collect the data for the table - endpnt = sprintf('water-requirement/%s?start_date=2016-01-01&end_date=%s', obj.Subject, datestr(now, 'yyyy-mm-dd')); - wr = obj.AlyxInstance.getData(endpnt); - iw = iff(isempty(wr.implant_weight), 0, wr.implant_weight); - records = catStructs(wr.records, nan); - % no weighings found - if isempty(wr.records) - obj.log('No weight data found for subject %s', obj.Subject); - return - end - expected = [records.weight_expected]; - expected(expected==0) = nan; - dates = cellfun(@(x)datenum(x), {records.date}); - - % build the figure to show it - if nargin==1 - f = figure('Name', obj.Subject, 'NumberTitle', 'off'); % popup a new figure for this - p = get(f, 'Position'); - set(f, 'Position', [p(1) p(2) 1100 p(4)]); - histbox = uix.HBox('Parent', f, 'BackgroundColor', 'w'); - plotBox = uix.VBox('Parent', histbox, 'BackgroundColor', 'w'); - ax = axes('Parent', plotBox); - end - - plot(ax, dates, [records.weight_measured], '.-'); - hold(ax, 'on'); - plot(ax, dates, ((expected-iw)*0.7)+iw, 'r', 'LineWidth', 2.0); - plot(ax, dates, ((expected-iw)*0.8)+iw, 'LineWidth', 2.0, 'Color', [244, 191, 66]/255); - box(ax, 'off'); - % Change the plot x axis limits - if numel(dates) > 1; xlim(ax, [min(dates) max(dates)]); end - if nargin == 1 - set(ax, 'XTickLabel', arrayfun(@(x)datestr(x, 'dd-mmm'), get(ax, 'XTick'), 'uni', false)) - else - xticks(ax, 'auto') - ax.XTickLabel = arrayfun(@(x)datestr(x, 'dd-mmm'), get(ax, 'XTick'), 'uni', false); - end - ylabel(ax, 'weight (g)'); - - if nargin==1 - ax = axes('Parent', plotBox); - plot(ax, dates, ([records.weight_measured]-iw)./(expected-iw), '.-'); - hold(ax, 'on'); - plot(ax, dates, 0.7*ones(size(dates)), 'r', 'LineWidth', 2.0); - plot(ax, dates, 0.8*ones(size(dates)), 'LineWidth', 2.0, 'Color', [244, 191, 66]/255); - box(ax, 'off'); - xlim(ax, [min(dates) max(dates)]); - set(ax, 'XTickLabel', arrayfun(@(x)datestr(x, 'dd-mmm'), get(ax, 'XTick'), 'uni', false)) - ylabel(ax, 'weight as pct (%)'); - axWater = axes('Parent',plotBox); - plot(axWater, dates, obj.round([records.water_given]+[records.hydrogel_given], 'up'), '.-'); - hold(axWater, 'on'); - plot(axWater, dates, obj.round([records.hydrogel_given], 'down'), '.-'); - plot(axWater, dates, obj.round([records.water_given], 'down'), '.-'); - plot(axWater, dates, obj.round([records.water_expected], 'up'), 'r', 'LineWidth', 2.0); - box(axWater, 'off'); - xlim(axWater, [min(dates) max(dates)]); - set(axWater, 'XTickLabel', arrayfun(@(x)datestr(x, 'dd-mmm'), get(axWater, 'XTick'), 'uni', false)) - ylabel(axWater, 'water/hydrogel (mL)'); + function dispWaterReq(obj, src, ~) + % Display the amount of water required by the selected subject + % for it to reach its minimum requirement. This function is + % also used to update the selected subject, for example it is + % this funtion to use as a callback to subject dropdown + % listeners + ai = obj.AlyxInstance; + % Set the selected subject if it is an input + if nargin>1; obj.Subject = src.Selected; end + if ~ai.IsLoggedIn + set(obj.WaterRequiredText, 'ForegroundColor', 'black',... + 'String', 'Log in to see water requirements'); + return + end + % Refresh the timer as the user isn't inactive + stop(obj.LoginTimer); start(obj.LoginTimer) + try + s = ai.getData('water-restricted-subjects'); % struct with data about restricted subjects + idx = strcmp(obj.Subject, {s.nickname}); + if ~any(idx) % Subject not on water restriction + set(obj.WaterRequiredText, 'ForegroundColor', 'black',... + 'String', sprintf('Subject %s not on water restriction', obj.Subject)); + else + % Get information on weight and water given + endpnt = sprintf('water-requirement/%s?start_date=%s&end_date=%s',... + obj.Subject, datestr(now, 'yyyy-mm-dd'),datestr(now, 'yyyy-mm-dd')); + wr = ai.getData(endpnt); % Get today's weight and water record + if ~isempty(wr.records) + record = wr.records(end); + else + record = struct(); + end + weight = getOr(record, 'weight_measured', NaN); % Get today's measured weight + water = getOr(record, 'water_given', 0); % Get total water given + gel = getOr(record, 'hydrogel_given', 0); % Get total gel given + weight_expected = getOr(record, 'weight_expected', NaN); + % Set colour based on weight percentage + weight_pct = (weight-wr.implant_weight)/(weight_expected-wr.implant_weight); + if weight_pct < 0.8 % Mouse below 80% original weight + colour = [0.91, 0.41, 0.17]; % Orange + weight_pct = '< 80%'; + elseif weight_pct < 0.7 % Mouse below 70% original weight + colour = 'red'; + weight_pct = '< 70%'; + else + colour = 'black'; % Mouse above 80% or no weight measured today + weight_pct = '> 80%'; + end + % Round up water remaining to the near 0.01 + remainder = obj.round(s(idx).water_requirement_remaining, 'up'); + % Set text + set(obj.WaterRequiredText, 'ForegroundColor', colour, 'String', ... + sprintf(['Subject %s requires %.2f of %.2f today\n\t '... + 'Weight today: %.2f (%s) Water today: %.2f'], obj.Subject, ... + remainder, obj.round(s(idx).water_requirement_total, 'up'), weight, ... + weight_pct, obj.round(sum([water gel]), 'down'))); + % Set WaterRemaining attribute for changeWaterText callback + obj.WaterRemaining = remainder; + end + catch me + d = me.message; %FIXME: JSON no longer returned + if isfield(d, 'detail') && strcmp(d.detail, 'Not found.') + set(obj.WaterRequiredText, 'ForegroundColor', 'black',... + 'String', sprintf('Subject %s not found in alyx', obj.Subject)); + end + end + end - % Create table of useful weight and water information, - % sorted by date - histTable = uitable('Parent', histbox,... - 'FontName', 'Consolas',... - 'RowName', []); - weightsByDate = num2cell([records.weight_measured]); - weightsByDate = cellfun(@(x)sprintf('%.1f', x), weightsByDate, 'uni', false); - weightsByDate(isnan([records.weight_measured])) = {[]}; - weightPctByDate = num2cell(([records.weight_measured]-iw)./(expected-iw)); - weightPctByDate = cellfun(@(x)sprintf('%.1f', x*100), weightPctByDate, 'uni', false); - weightPctByDate(isnan([records.weight_measured])) = {[]}; + function changeWaterText(obj, src, ~) + % Update the panel text to show the amount of water still + % required for the subject to reach its minimum requirement. + % This text is updated before the value in the water text box + % has been posted to Alyx. For example if the user is unsure + % how much gel over the minimum they have weighed out, pressing + % return will display this without posting to Alyx + % + % See also DISPWATERREQ, GIVEWATER + if obj.AlyxInstance.IsLoggedIn && ~isempty(obj.WaterRemaining) + rem = obj.WaterRemaining; + curr = str2double(src.String); + set(obj.WaterRemainingText, 'String', sprintf('(%.2f)', rem-curr)); + end + end - dat = horzcat(... - arrayfun(@(x)datestr(x), dates', 'uni', false), ... - weightsByDate', ... - arrayfun(@(x)sprintf('%.1f', 0.8*(x-iw)+iw), [records.weight_expected]', 'uni', false), ... - weightPctByDate'); - waterDat = (... - num2cell(horzcat([records.water_given]', [records.hydrogel_given]', ... - [records.water_given]'+[records.hydrogel_given]', [records.water_expected]',... - [records.water_given]'+[records.hydrogel_given]'-[records.water_expected]'))); - waterDat = cellfun(@(x)sprintf('%.2f', x), waterDat, 'uni', false); - dat = horzcat(dat, waterDat); + function recordWeight(obj, weight, subject) + % Post a subject's weight to Alyx. If no inputs are provided, + % create an input dialog for the user to input a weight. If no + % subject is provided, use this object's currently selected + % subject. + % + % See also VIEWSUBJECTHISTORY, VIEWALLSUBJECTS + ai = obj.AlyxInstance; + if nargin < 3; subject = obj.Subject; end + if nargin < 2 + prompt = {sprintf('weight of %s:', subject)}; + dlgTitle = 'Manual weight logging'; + numLines = 1; + defaultAns = {'',''}; + weight = inputdlg(prompt, dlgTitle, numLines, defaultAns); + if isempty(weight); return; end + end + % inputdlg returns weight as a cell, otherwise it may now be + weight = ensureCell(weight); % ensure it's a cell + % convert to double if weight is a string + weight = iff(ischar(weight{1}), str2double(weight{1}), weight{1}); + try + w = postWeight(ai, weight, subject); + obj.log('Alyx weight posting succeeded: %.2f for %s', w.weight, w.subject); + catch + if ~ai.IsLoggedIn % if not logged in, save the weight for later + obj.log('Warning: Weight not posted to Alyx; will be posted upon login.'); + else + obj.log('Warning: Alyx weight posting failed!'); + end + end + % Update weight and refresh login timer + obj.dispWaterReq + end - set(histTable, 'ColumnName', {'date', 'meas. weight', '80% weight', 'weight pct', 'water', 'hydrogel', 'total', 'min water', 'excess'}, ... - 'Data', dat(end:-1:1,:),... - 'ColumnEditable', false(1,5)); - histbox.Widths = [ -1 725]; - end - end - - function viewAllSubjects(obj) - ai = obj.AlyxInstance; - if ai.IsLoggedIn - wr = ai.getData(ai.makeEndpoint('water-restricted-subjects')); - - % build a figure to show it - f = figure; % popup a new figure for this - wrBox = uix.VBox('Parent', f); - wrTable = uitable('Parent', wrBox,... - 'FontName', 'Consolas',... - 'RowName', []); + function launchSessionURL(obj) + % Launch the Webpage for the current base session in the + % default Web browser. If no session exists for today's date, + % a new base session is created accordingly. + % + % See also LAUNCHSUBJECTURL + ai = obj.AlyxInstance; + % determine whether there is a session for this subj and date + thisDate = ai.datestr(now); + sessions = ai.getData(['sessions?type=Base&subject=' obj.Subject]); + + % If the date of this latest base session is not the same date + % as today, then create a new one for today + if isempty(sessions) || ~strcmp(sessions{end}.start_time(1:10), thisDate(1:10)) + % Ask user whether he/she wants to create new session + % Construct a questdlg with three options + choice = questdlg('Would you like to create a new base session?', ... + ['No base session exists for ' datestr(now, 'yyyy-mm-dd')], ... + 'Yes','No','No'); + % Handle response + switch choice + case 'Yes' + % Create our base session + d = struct; + d.subject = obj.Subject; + d.procedures = {'Behavior training/tasks'}; + d.narrative = 'auto-generated session'; + d.start_time = thisDate; + d.type = 'Base'; + + thisSess = ai.postData('sessions', d); + if ~isfield(thisSess,'subject') % fail + warning('Submitted base session did not return appropriate values'); + warning('Submitted data below:'); + disp(d) + warning('Return values below:'); + disp(thisSess) + return + else % success + obj.log(['Created new base session in Alyx for ' obj.Subject]); + end + case 'No' + return + end + else + thisSess = sessions{end}; + end + + % parse the uuid from the url in the session object + u = thisSess.url; + uuid = u(find(u=='/', 1, 'last')+1:end); + + % make the admin url + adminURL = fullfile(ai.BaseURL, 'admin', 'actions', 'session', uuid, 'change'); + + % launch the website + web(adminURL, '-browser'); + end - htmlColor = @(colorNum)reshape(dec2hex(round(colorNum'*255),2)',1,6); - % colorgen = @(colorNum,text) ['<html><table border=0 width=400 bgcolor=#',htmlColor(colorNum),'><TR><TD>',text,'</TD></TR> </table></html>']; - colorgen = @(colorNum,text) ['<html><body bgcolor=#',htmlColor(colorNum),'>',text,'</body></html>']; + function launchSubjectURL(obj) + ai = obj.AlyxInstance; + if ai.IsLoggedIn + s = ai.getData(ai.makeEndpoint(['subjects/' obj.Subject])); + subjURL = fullfile(ai.BaseURL, 'admin', 'subjects', 'subject', s.id, 'change'); % this is wrong - need uuid + web(subjURL, '-browser'); + end + end - wrdat = cellfun(@(x)colorgen(1-double(x>0)*[0 0.3 0.3],... - sprintf('%.2f',obj.round(x, 'up'))), {wr.water_requirement_remaining}, 'uni', false); + function viewSubjectHistory(obj, ax) + % View historical information about a subject. + % Opens a new window and plots a set of weight graphs as well + % as displaying a table with the water and weight entries for + % the selected subject. If an axes handle is provided, this + % function plots a single weight graph + + % If not logged in or 'default' is selected, return + if ~obj.AlyxInstance.IsLoggedIn||strcmp(obj.Subject, 'default'); return; end + % collect the data for the table + endpnt = sprintf('water-requirement/%s?start_date=2016-01-01&end_date=%s', obj.Subject, datestr(now, 'yyyy-mm-dd')); + wr = obj.AlyxInstance.getData(endpnt); + iw = iff(isempty(wr.implant_weight), 0, wr.implant_weight); + records = catStructs(wr.records, nan); + % no weighings found + if isempty(wr.records) + obj.log('No weight data found for subject %s', obj.Subject); + return + end + expected = [records.weight_expected]; + expected(expected==0) = nan; + dates = cellfun(@(x)datenum(x), {records.date}); + + % build the figure to show it + if nargin==1 + f = figure('Name', obj.Subject, 'NumberTitle', 'off'); % popup a new figure for this + p = get(f, 'Position'); + set(f, 'Position', [p(1) p(2) 1100 p(4)]); + histbox = uix.HBox('Parent', f, 'BackgroundColor', 'w'); + plotBox = uix.VBox('Parent', histbox, 'BackgroundColor', 'w'); + ax = axes('Parent', plotBox); + end + + plot(ax, dates, [records.weight_measured], '.-'); + hold(ax, 'on'); + plot(ax, dates, ((expected-iw)*0.7)+iw, 'r', 'LineWidth', 2.0); + plot(ax, dates, ((expected-iw)*0.8)+iw, 'LineWidth', 2.0, 'Color', [244, 191, 66]/255); + box(ax, 'off'); + % Change the plot x axis limits + if numel(dates) > 1; xlim(ax, [min(dates) max(dates)]); end + if nargin == 1 + set(ax, 'XTickLabel', arrayfun(@(x)datestr(x, 'dd-mmm'), get(ax, 'XTick'), 'uni', false)) + else + xticks(ax, 'auto') + ax.XTickLabel = arrayfun(@(x)datestr(x, 'dd-mmm'), get(ax, 'XTick'), 'uni', false); + end + ylabel(ax, 'weight (g)'); + + if nargin==1 + ax = axes('Parent', plotBox); + plot(ax, dates, ([records.weight_measured]-iw)./(expected-iw), '.-'); + hold(ax, 'on'); + plot(ax, dates, 0.7*ones(size(dates)), 'r', 'LineWidth', 2.0); + plot(ax, dates, 0.8*ones(size(dates)), 'LineWidth', 2.0, 'Color', [244, 191, 66]/255); + box(ax, 'off'); + xlim(ax, [min(dates) max(dates)]); + set(ax, 'XTickLabel', arrayfun(@(x)datestr(x, 'dd-mmm'), get(ax, 'XTick'), 'uni', false)) + ylabel(ax, 'weight as pct (%)'); + + axWater = axes('Parent',plotBox); + plot(axWater, dates, obj.round([records.water_given]+[records.hydrogel_given], 'up'), '.-'); + hold(axWater, 'on'); + plot(axWater, dates, obj.round([records.hydrogel_given], 'down'), '.-'); + plot(axWater, dates, obj.round([records.water_given], 'down'), '.-'); + plot(axWater, dates, obj.round([records.water_expected], 'up'), 'r', 'LineWidth', 2.0); + box(axWater, 'off'); + xlim(axWater, [min(dates) max(dates)]); + set(axWater, 'XTickLabel', arrayfun(@(x)datestr(x, 'dd-mmm'), get(axWater, 'XTick'), 'uni', false)) + ylabel(axWater, 'water/hydrogel (mL)'); + + % Create table of useful weight and water information, + % sorted by date + histTable = uitable('Parent', histbox,... + 'FontName', 'Consolas',... + 'RowName', []); + weightsByDate = num2cell([records.weight_measured]); + weightsByDate = cellfun(@(x)sprintf('%.1f', x), weightsByDate, 'uni', false); + weightsByDate(isnan([records.weight_measured])) = {[]}; + weightPctByDate = num2cell(([records.weight_measured]-iw)./(expected-iw)); + weightPctByDate = cellfun(@(x)sprintf('%.1f', x*100), weightPctByDate, 'uni', false); + weightPctByDate(isnan([records.weight_measured])) = {[]}; + + dat = horzcat(... + arrayfun(@(x)datestr(x), dates', 'uni', false), ... + weightsByDate', ... + arrayfun(@(x)sprintf('%.1f', 0.8*(x-iw)+iw), [records.weight_expected]', 'uni', false), ... + weightPctByDate'); + waterDat = (... + num2cell(horzcat([records.water_given]', [records.hydrogel_given]', ... + [records.water_given]'+[records.hydrogel_given]', [records.water_expected]',... + [records.water_given]'+[records.hydrogel_given]'-[records.water_expected]'))); + waterDat = cellfun(@(x)sprintf('%.2f', x), waterDat, 'uni', false); + dat = horzcat(dat, waterDat); + + set(histTable, 'ColumnName', {'date', 'meas. weight', '80% weight', 'weight pct', 'water', 'hydrogel', 'total', 'min water', 'excess'}, ... + 'Data', dat(end:-1:1,:),... + 'ColumnEditable', false(1,5)); + histbox.Widths = [ -1 725]; + end + end - set(wrTable, 'ColumnName', {'Name', 'Water Required', 'Remaining Requirement'}, ... - 'Data', horzcat({wr.nickname}', ... - cellfun(@(x)sprintf('%.2f',obj.round(x, 'up')),{wr.water_requirement_total}', 'uni', false), ... - wrdat'), ... - 'ColumnEditable', false(1,3)); - end - end - - function updateWeightButton(obj, src, ~) - % Function for changing the text on the weight button to reflect the - % current weight value obtained by the scale. This function must be - % a callback for the hw.WeighingScale NewReading event. If a new - % reading isn't read for 10 sec the manual weighing option is made - % available instead. - % - % Example: - % aiPanel = eui.AlyxPanel; - % lh = event.listener(obj.WeighingScale, 'NewReading',... - % @(src,evt)aiPanel.updateWeightButton(src,evt)); - % - % See also hw.WeighingScale, eui.MControl - set(obj.WeightButton, 'String', sprintf('Record %.1fg', src.readGrams), 'Callback', @(~,~)obj.recordWeight(src.readGrams)) - obj.WeightTimer = timer('Name', 'Last Weight',... - 'TimerFcn', @(~,~)set(obj.WeightButton, 'String', 'Manual weighing', 'Callback', @(~,~)obj.recordWeight),... - 'StopFcn', @(src,~)delete(src), 'StartDelay', 10); - start(obj.WeightTimer) + function viewAllSubjects(obj) + ai = obj.AlyxInstance; + if ai.IsLoggedIn + wr = ai.getData(ai.makeEndpoint('water-restricted-subjects')); + + % build a figure to show it + f = figure; % popup a new figure for this + wrBox = uix.VBox('Parent', f); + wrTable = uitable('Parent', wrBox,... + 'FontName', 'Consolas',... + 'RowName', []); + + htmlColor = @(colorNum)reshape(dec2hex(round(colorNum'*255),2)',1,6); + % colorgen = @(colorNum,text) ['<html><table border=0 width=400 bgcolor=#',htmlColor(colorNum),'><TR><TD>',text,'</TD></TR> </table></html>']; + colorgen = @(colorNum,text) ['<html><body bgcolor=#',htmlColor(colorNum),'>',text,'</body></html>']; + + wrdat = cellfun(@(x)colorgen(1-double(x>0)*[0 0.3 0.3],... + sprintf('%.2f',obj.round(x, 'up'))), {wr.water_requirement_remaining}, 'uni', false); + + set(wrTable, 'ColumnName', {'Name', 'Water Required', 'Remaining Requirement'}, ... + 'Data', horzcat({wr.nickname}', ... + cellfun(@(x)sprintf('%.2f',obj.round(x, 'up')),{wr.water_requirement_total}', 'uni', false), ... + wrdat'), ... + 'ColumnEditable', false(1,3)); + end + end + + function updateWeightButton(obj, src, ~) + % Function for changing the text on the weight button to reflect the + % current weight value obtained by the scale. This function must be + % a callback for the hw.WeighingScale NewReading event. If a new + % reading isn't read for 10 sec the manual weighing option is made + % available instead. + % + % Example: + % aiPanel = eui.AlyxPanel; + % lh = event.listener(obj.WeighingScale, 'NewReading',... + % @(src,evt)aiPanel.updateWeightButton(src,evt)); + % + % See also hw.WeighingScale, eui.MControl + set(obj.WeightButton, 'String', sprintf('Record %.1fg', src.readGrams), 'Callback', @(~,~)obj.recordWeight(src.readGrams)) + obj.WeightTimer = timer('Name', 'Last Weight',... + 'TimerFcn', @(~,~)set(obj.WeightButton, 'String', 'Manual weighing', 'Callback', @(~,~)obj.recordWeight),... + 'StopFcn', @(src,~)delete(src), 'StartDelay', 10); + start(obj.WeightTimer) + end + + function log(obj, varargin) + % Function for displaying timestamped information about + % occurrences. If the LoggingDisplay property is unset, the + % message is printed to the command prompt. + % log(formatSpec, A1,... An) + % + % See also FPRINTF + message = sprintf(varargin{:}); + if ~isempty(obj.LoggingDisplay) + timestamp = datestr(now, 'dd-mm-yyyy HH:MM:SS'); + str = sprintf('[%s] %s', timestamp, message); + current = cellflat(get(obj.LoggingDisplay, 'String')); + %NB: If more that one instance of MATLAB is open, we use + %the last opened LoggingDisplay + set(obj.LoggingDisplay(end), 'String', [current; str], 'Value', numel(current) + 1); + else + fprintf(message) + end + end end - function log(obj, varargin) - % Function for displaying timestamped information about - % occurrences. If the LoggingDisplay property is unset, the - % message is printed to the command prompt. - % log(formatSpec, A1,... An) - % - % See also FPRINTF - message = sprintf(varargin{:}); - if ~isempty(obj.LoggingDisplay) - timestamp = datestr(now, 'dd-mm-yyyy HH:MM:SS'); - str = sprintf('[%s] %s', timestamp, message); - current = cellflat(get(obj.LoggingDisplay, 'String')); - %NB: If more that one instance of MATLAB is open, we use - %the last opened LoggingDisplay - set(obj.LoggingDisplay(end), 'String', [current; str], 'Value', numel(current) + 1); - else - fprintf(message) - end - end - end - - methods (Static) - function A = round(a, direction, sigFigures) - if nargin < 3; sigFigures = 2; end - c = 1*10^sigFigures; - switch direction - case 'up' - A = ceil(a*c)/c; - case 'down' - A = ceil(a*c)/c; - otherwise - A = round(a*c)/c; - end + methods (Static) + function A = round(a, direction, sigFigures) + if nargin < 3; sigFigures = 2; end + c = 1*10^sigFigures; + switch direction + case 'up' + A = ceil(a*c)/c; + case 'down' + A = ceil(a*c)/c; + otherwise + A = round(a*c)/c; + end + end end - end end \ No newline at end of file From 150a61cb73e3c2cc27f8b4981fc3e835e56418af Mon Sep 17 00:00:00 2001 From: jaib1 <jaib1@mit.edu> Date: Fri, 26 Oct 2018 16:37:03 +0100 Subject: [PATCH 190/393] Updated Output to log when giving water in AlyxPanel --- +eui/AlyxPanel.m | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index 9dc3e7ab..2126838a 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -291,7 +291,7 @@ function giveWater(obj) if obj.AlyxInstance.IsLoggedIn && amount~=0 && ~isnan(amount) wa = obj.AlyxInstance.postWater(obj.Subject, amount, thisDate, type); if ~isempty(wa) % returned us a created water administration object successfully - obj.log('%s administration of %.2f for %s posted successfully to alyx', type, amount, obj.Subject); + obj.log('%s administration of %.2f for "%s" posted successfully to alyx', type, amount, obj.Subject); end end % update the water required text @@ -303,22 +303,29 @@ function giveFutureWater(obj) % future dates thisDate = now; prompt=sprintf('Enter space-separated numbers \n[tomorrow, day after that, day after that.. etc] \nEnter "0" to skip a day\nEnter "-1" to indicate training for that day'); - a=inputdlg(prompt,'Future Amounts', [1 50]); - if isempty(answer)||~obj.AlyxInstance.IsLoggedIn + amtStr=inputdlg(prompt,'Future Amounts', [1 50]); + if isempty(amtStr)||~obj.AlyxInstance.IsLoggedIn return % user pressed 'Close' or 'x' end - amt = str2num(answer{:}); % amount of water + amt = str2num(amtStr{:}); % amount of water futDates = thisDate + (1:length(amt)); %datenum of all input future dates futTrnDates = futDates(amt<0); %future training dates + dat.saveParamProfile('WeekendWater', obj.Subject, futTrnDates); + for d = 1:length(futTrnDates) + [~,day] = weekday(datestr(futTrnDates(d)), 'long'); + obj.log(['"%s" marked for training for ' day ' %s'],... + obj.Subject, datestr(futTrnDates(d), 'dd mmmm yyyy')); + end + futWtrDates = futDates(amt>0); %future water giving dates amtWtrDates = amt(amt>0); %amount of water to give on future water dates - dat.saveParamProfile('WeekendWater', obj.Subject, futTrnDates); for d = 1:length(futWtrDates) obj.AlyxInstance.postWater(obj.Subject, amtWtrDates(d), futWtrDates(d), 'Water'); - obj.log(['Hydrogel administration of %.2f for %s posted successfully to alyx for '... - datestr(futWtrDates(d))], amtWtrDates(d), obj.Subject); + [~,day] = weekday(datestr(futTrnDates(d)), 'long'); + obj.log(['Water administration of %.2f for "%s" posted successfully to alyx for ' day ' %s'],... + amtWtrDates(d), obj.Subject, datestr(futWtrDates(d), 'dd mmmm yyyy')); end end From 53bce3b245151383e747e79ca44f133f94b53677 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Mon, 29 Oct 2018 11:17:54 +0000 Subject: [PATCH 191/393] Future water bug fix, better error handling is dispWtrReq and consistent log formatting --- +eui/AlyxPanel.m | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index 2126838a..73277f52 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -300,32 +300,36 @@ function giveWater(obj) function giveFutureWater(obj) % Open a dialog allowing one to input water submissions for - % future dates + % future dates. If a -1 is inputted for a particular date, the + % date is saved in the 'WeekendWater' struct of the + % paramProfiles file. This may be used to notify weekend staff + % of the experimentor's intent to train on that date. thisDate = now; - prompt=sprintf('Enter space-separated numbers \n[tomorrow, day after that, day after that.. etc] \nEnter "0" to skip a day\nEnter "-1" to indicate training for that day'); - amtStr=inputdlg(prompt,'Future Amounts', [1 50]); + prompt = sprintf(['Enter space-separated numbers \n'... + '[tomorrow, day after that, day after that.. etc] \n',... + 'Enter "0" to skip a day\nEnter "-1" to indicate training for that day\n']); + amtStr = inputdlg(prompt,'Future Amounts', [1 50]); if isempty(amtStr)||~obj.AlyxInstance.IsLoggedIn return % user pressed 'Close' or 'x' end - amt = str2num(amtStr{:}); % amount of water - futDates = thisDate + (1:length(amt)); %datenum of all input future dates + amt = str2num(amtStr{:}); %#ok<ST2NM> % amount of water + futDates = thisDate + (1:length(amt)); % datenum of all input future dates - futTrnDates = futDates(amt<0); %future training dates + futTrnDates = futDates(amt < 0); % future training dates dat.saveParamProfile('WeekendWater', obj.Subject, futTrnDates); - for d = 1:length(futTrnDates) - [~,day] = weekday(datestr(futTrnDates(d)), 'long'); - obj.log(['"%s" marked for training for ' day ' %s'],... - obj.Subject, datestr(futTrnDates(d), 'dd mmmm yyyy')); - end + [~,days] = weekday(futTrnDates, 'long'); + delim = iff(size(days,1) < 3, ' and ', {', ', ' and '}); + obj.log('%s marked for training on %s',... + obj.Subject, strjoin(strtrim(string(days)), delim)); - futWtrDates = futDates(amt>0); %future water giving dates - amtWtrDates = amt(amt>0); %amount of water to give on future water dates + futWtrDates = futDates(amt > 0); % future water giving dates + amtWtrDates = amt(amt > 0); % amount of water to give on future water dates for d = 1:length(futWtrDates) obj.AlyxInstance.postWater(obj.Subject, amtWtrDates(d), futWtrDates(d), 'Water'); - [~,day] = weekday(datestr(futTrnDates(d)), 'long'); - obj.log(['Water administration of %.2f for "%s" posted successfully to alyx for ' day ' %s'],... - amtWtrDates(d), obj.Subject, datestr(futWtrDates(d), 'dd mmmm yyyy')); + [~,day] = weekday(futWtrDates(d), 'long'); + obj.log('Water administration of %.2f for %s posted successfully to alyx for %s %s',... + amtWtrDates(d), obj.Subject, day, datestr(futWtrDates(d), 'dd mmm yyyy')); end end @@ -393,6 +397,8 @@ function dispWaterReq(obj, src, ~) if isfield(d, 'detail') && strcmp(d.detail, 'Not found.') set(obj.WaterRequiredText, 'ForegroundColor', 'black',... 'String', sprintf('Subject %s not found in alyx', obj.Subject)); + else + rethrow(me) end end end @@ -465,7 +471,7 @@ function launchSessionURL(obj) % Ask user whether he/she wants to create new session % Construct a questdlg with three options choice = questdlg('Would you like to create a new base session?', ... - ['No base session exists for ' datestr(now, 'yyyy-mm-dd')], ... + ['No base session exists for ' datestr(now, 'yyyy-mmm-dd')], ... 'Yes','No','No'); % Handle response switch choice From 633416a4ab64aa5363d6d3f56646402762d6910c Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Mon, 29 Oct 2018 14:22:26 +0000 Subject: [PATCH 192/393] Merge from dev, auto update of code every monday --- +srv/expServer.m | 2 +- .gitmodules | 4 ---- signals | 2 +- wheelAnalysis | 2 +- 4 files changed, 3 insertions(+), 7 deletions(-) diff --git a/+srv/expServer.m b/+srv/expServer.m index 586f03b3..be5b3081 100644 --- a/+srv/expServer.m +++ b/+srv/expServer.m @@ -20,7 +20,7 @@ function expServer(useTimelineOverride, bgColour) %% Initialisation % Pull latest changes from remote -git.update(true); +git.update(true, 2); % Update ever Monday % random seed random number generator rng('shuffle'); % communicator for receiving commands from clients diff --git a/.gitmodules b/.gitmodules index 0022ed5b..d6e7a40f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,16 +1,12 @@ [submodule "alyx-matlab"] path = alyx-matlab url = https://github.com/cortex-lab/alyx-matlab/ - branch = dev [submodule "signals"] path = signals url = https://github.com/cortex-lab/signals - branch = dev [submodule "npy-matlab"] path = npy-matlab url = https://github.com/kwikteam/npy-matlab - branch = master [submodule "wheelAnalysis"] path = wheelAnalysis url = https://github.com/cortex-lab/wheelAnalysis - branch = master diff --git a/signals b/signals index 13db4d1d..4fa58ead 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit 13db4d1d6a7e11427043b514867a3807eb1f12f7 +Subproject commit 4fa58ead37f36e6a40c96f3cafc5a60227fa1be4 diff --git a/wheelAnalysis b/wheelAnalysis index 16979786..05f90203 160000 --- a/wheelAnalysis +++ b/wheelAnalysis @@ -1 +1 @@ -Subproject commit 169797868da3fe93e1581cb4d581cdc4f4d9cd34 +Subproject commit 05f902033bc834c98624d5634be3cf91b737f250 From f943f4849ba79cdeb7184c82417905a1105529dd Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 31 Oct 2018 09:20:58 +0000 Subject: [PATCH 193/393] Returns index of named arg --- cb-tools/burgbox/namedArg.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cb-tools/burgbox/namedArg.m b/cb-tools/burgbox/namedArg.m index 1da86053..5e5820fb 100644 --- a/cb-tools/burgbox/namedArg.m +++ b/cb-tools/burgbox/namedArg.m @@ -1,4 +1,4 @@ -function [present, value] = namedArg(args, name) +function [present, value, defIdx] = namedArg(args, name) %NAMEDARG Returns value from name,value argument pairs % [present, value] = NAMEDARG(args, name) returns flag for presence and % value of the argument 'name' in a list potentially containing adjacent From e4cdf247ac6354600eb319fcb17db4b957bb7196 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 31 Oct 2018 09:39:44 +0000 Subject: [PATCH 194/393] Bug fix and SPX222 support --- +hw/WeighingScale.m | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/+hw/WeighingScale.m b/+hw/WeighingScale.m index 1a2630a0..f674802a 100644 --- a/+hw/WeighingScale.m +++ b/+hw/WeighingScale.m @@ -2,15 +2,18 @@ %HW.WEIGHINGSCALE Interface to a weighing scale connected via serial % Allows you to read the current weight from scales and tare it. This % class has been tested only with the ES-300HA 300gx0.01g Precision - % Scale + RS232 to USB Converter. + % Scale, and Ohaus SPX222 Scout Portable Balance 220G x 0.01g + RS232 + % to USB Converter. % % Part of Rigbox - - % 2013-02 CB created + + % 2013-02 CB created properties + Name = 'ES-300HA' % 'SPX222' ComPort = 'COM1' - TareCommand = hex2dec('54') + TareCommand = hex2dec('54') % For SPX222 use 'T' + FormatSpec = '%s %f %*s' % For SPX222 use '%f' Port = [] end @@ -38,6 +41,15 @@ function init(obj) set(obj.Port, 'BytesAvailableFcn', @obj.onBytesAvail); fopen(obj.Port); fprintf('Opened scales on "%s"\n', obj.ComPort); + switch obj.Name + case 'SPX222' + % Optional settings may be set manually instead + fprintf(obj.Port, 'IP'); % Auto print stable non-zero weight and stable zero reading + fprintf(obj.Port, '1M'); % Weight mode + fprintf(obj.Port, '1U'); % Weight unit grammes + otherwise + % Do nothing + end end end @@ -54,13 +66,14 @@ function delete(obj) cleanup(obj); end - function onBytesAvail(obj, src, evt) + function onBytesAvail(obj, src, ~) nr = src.BytesAvailable/13; for i = 1:nr - d = sscanf(fscanf(src),'%s %f %*s'); - g = d(2); - if d(1) == 45 - g = -g; + g = sscanf(fscanf(src), obj.FormatSpec); + if length(g) > 1 && g(1) == 45 + g = -g(2); % 45 == '-' + elseif length(g) > 1 && g(1) == 43 + g = g(2); % 43 == '+' end obj.LastGrams = g; notify(obj, 'NewReading'); @@ -69,4 +82,3 @@ function onBytesAvail(obj, src, evt) end end - From 1c05914056d57577d0e669365bc84b3025c0ebea Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 31 Oct 2018 11:25:09 +0000 Subject: [PATCH 195/393] Updates to signals & wheelAnalysis modules --- signals | 2 +- wheelAnalysis | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/signals b/signals index 13db4d1d..51f56c0d 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit 13db4d1d6a7e11427043b514867a3807eb1f12f7 +Subproject commit 51f56c0d90ed2232eb3255a1773d45ad695adaf4 diff --git a/wheelAnalysis b/wheelAnalysis index 16979786..05f90203 160000 --- a/wheelAnalysis +++ b/wheelAnalysis @@ -1 +1 @@ -Subproject commit 169797868da3fe93e1581cb4d581cdc4f4d9cd34 +Subproject commit 05f902033bc834c98624d5634be3cf91b737f250 From 7148e9ef5ed20fb261ada2444441407092611b93 Mon Sep 17 00:00:00 2001 From: jaib1 <jaib1@mit.edu> Date: Wed, 31 Oct 2018 13:14:37 +0000 Subject: [PATCH 196/393] commentings on SignalsExp plus last signals subrepo commit --- +exp/SignalsExp.m | 1789 +++++++++++++++++++++++---------------------- 1 file changed, 900 insertions(+), 889 deletions(-) diff --git a/+exp/SignalsExp.m b/+exp/SignalsExp.m index 789ab556..679f18d2 100644 --- a/+exp/SignalsExp.m +++ b/+exp/SignalsExp.m @@ -1,921 +1,932 @@ classdef SignalsExp < handle - %EXP.SIGNALSEXP Base class for stimuli-delivering experiments - % The class defines a framework for event- and state-based experiments. - % Visual and auditory stimuli can be controlled by experiment phases. - % Phases changes are managed by an event-handling system. - % - % Part of Rigbox - - % 2012-11 CB created - - properties - %An array of event handlers. Each should specify the name of the - %event that activates it, callback functions to be executed when - %activated and an optional delay between the event and activation. - %They should be objects of class EventHandler. - EventHandlers = exp.EventHandler.empty - - %Timekeeper used by the experiment. Clocks return the current time. See - %the Clock class definition for more information. - Clock = hw.ptb.Clock - - %Key for terminating an experiment whilst running. Shoud be a - %Psychtoolbox keyscan code (see PTB KbName function). - QuitKey = KbName('q') - - PauseKey = KbName('esc') %Key for pausing an experiment - - %String description of the type of experiment, to be saved into the - %block data field 'expType'. - Type = '' - - %Reference for the rig that this experiment is being run on, to be - %saved into the block data field 'rigName'. - RigName - - %Communcator object for sending signals updates to mc. Set by - %expServer - Communicator = io.DummyCommunicator - - %Delay (secs) before starting main experiment phase after experiment - %init phase has completed - PreDelay = 0 - - %Delay (secs) before beginning experiment cleanup phase after - %main experiment phase has completed (assuming an immediate abort - %wasn't requested). - PostDelay = 0 - - %Flag indicating whether the experiment is paused - IsPaused = false - - %Holds the wheel object, 'mouseInput' from the rig object. See also - %USERIG, HW.DAQROTARYENCODER - Wheel - - %Holds the object for interating with the lick detector. See also - %HW.DAQEDGECOUNTER - LickDetector - - %Holds the object for interating with the DAQ outputs (reward valve, - %etc.) See also HW.DAQCONTROLLER - DaqController - - %Get the handle to the PTB window opened by expServer - StimWindowPtr - - TextureById - - LayersByStim - - %Occulus viewing model - Occ - - Time - - Inputs - - Outputs - - Events - - Visual - - Audio % = aud.AudioRegistry - - %Holds the parameters structure for this experiment - Params - - ParamsLog - - %The bounds for the photodiode square - SyncBounds - - %Sync colour cycle (usually [0, 255]) - cycles through these each - %time the screen flips. - SyncColourCycle - - %Index into SyncColourCycle for next sync colour - NextSyncIdx - - %Alyx instance from client. See also SAVEDATA - AlyxInstance = [] - end - - properties (SetAccess = protected) - %Number of stimulus window flips - StimWindowUpdateCount = 0 - - %Data from the currently running experiment, if any. - Data = struct - - %Currently active phases of the experiment. Cell array of their names - %(i.e. strings) - ActivePhases = {} - - Listeners - - Net - - SignalUpdates = struct('name', cell(500,1), 'value', cell(500,1), 'timestamp', cell(500,1)) - NumSignalUpdates = 0 - - end - - properties (Access = protected) - %Set triggers awaiting activation: a list of Triggered objects, which - %are awaiting activation pending completion of their delay period. - Pending - - IsLooping = false %flag indicating whether to continue in experiment loop - - AsyncFlipping = false - - StimWindowInvalid = false - end - - methods - function obj = SignalsExp(paramStruct, rig) - clock = rig.clock; - clockFun = @clock.now; - obj.TextureById = containers.Map('KeyType', 'char', 'ValueType', 'uint32'); - obj.LayersByStim = containers.Map; - obj.Inputs = sig.Registry(clockFun); - obj.Outputs = sig.Registry(clockFun); - obj.Visual = StructRef; - obj.Audio = audstream.Registry(rig.audioDevices); - obj.Events = sig.Registry(clockFun); - %% configure signals - net = sig.Net; - obj.Net = net; - obj.Time = net.origin('t'); - obj.Events.expStart = net.origin('expStart'); - obj.Events.newTrial = net.origin('newTrial'); - obj.Events.expStop = net.origin('expStop'); - obj.Inputs.wheel = net.origin('wheel'); - obj.Inputs.lick = net.origin('lick'); - obj.Inputs.keyboard = net.origin('keyboard'); - % get global parameters & conditional parameters structs - [~, globalStruct, allCondStruct] = toConditionServer(... - exp.Parameters(paramStruct)); - % start the first trial after expStart - advanceTrial = net.origin('advanceTrial'); - % configure parameters signal - globalPars = net.origin('globalPars'); - allCondPars = net.origin('condPars'); - [obj.Params, hasNext, obj.Events.repeatNum] = exp.trialConditions(... - globalPars, allCondPars, advanceTrial); - - obj.Events.trialNum = obj.Events.newTrial.scan(@plus, 0); % track trial number - - lastTrialOver = then(~hasNext, true); - -% obj.Events.expStop = then(~hasNext, true); - % run experiment definition - if ischar(paramStruct.defFunction) - expDefFun = fileFunction(paramStruct.defFunction); - obj.Data.expDef = paramStruct.defFunction; - else - expDefFun = paramStruct.defFunction; - obj.Data.expDef = func2str(expDefFun); - end - fprintf('takes %i args\n', nargout(expDefFun)); - expDefFun(obj.Time, obj.Events, obj.Params, obj.Visual, obj.Inputs,... - obj.Outputs, obj.Audio); - % listeners - obj.Listeners = [ - obj.Events.expStart.map(true).into(advanceTrial) %expStart signals advance - obj.Events.endTrial.into(advanceTrial) %endTrial signals advance - advanceTrial.map(true).keepWhen(hasNext).into(obj.Events.newTrial) %newTrial if more - lastTrialOver.into(obj.Events.expStop) %newTrial if more - onValue(obj.Events.expStop, @(~)quit(obj));]; -% obj.Events.trialNum.onValue(fun.partial(@fprintf, 'trial %i started\n'))]; - % initialise the parameter signals - globalPars.post(rmfield(globalStruct, 'defFunction')); - allCondPars.post(allCondStruct); - %% data struct - -% obj.Params = obj.Params.map(@(v)v, [], @(n,s)sig.Logger([n '[L]'],s)); - %initialise stim window frame times array, large enough for ~2 hours - obj.Data.stimWindowUpdateTimes = zeros(60*60*60*2, 1); - obj.Data.stimWindowRenderTimes = zeros(60*60*60*2, 1); -% obj.Data.stimWindowUpdateLags = zeros(60*60*60*2, 1); - obj.ParamsLog = obj.Params.log(); - obj.useRig(rig); + %EXP.SIGNALSEXP Base class for stimuli-delivering experiments + % The class defines a framework for event- and state-based experiments. + % Visual and auditory stimuli can be controlled by experiment phases. + % Phases changes are managed by an event-handling system. + % + % Part of Rigbox + + % 2012-11 CB created + + %% properties (default - all public) + + properties + %An array of event handlers. Each should specify the name of the + %event that activates it, callback functions to be executed when + %activated and an optional delay between the event and activation. + %They should be objects of class EventHandler. + EventHandlers = exp.EventHandler.empty + + %Timekeeper used by the experiment. Clocks return the current time. See + %the Clock class definition for more information. + Clock = hw.ptb.Clock + + %Key for terminating an experiment whilst running. Shoud be a + %Psychtoolbox keyscan code (see PTB KbName function). + QuitKey = KbName('q') + + PauseKey = KbName('esc') %Key for pausing an experiment + + %String description of the type of experiment, to be saved into the + %block data field 'expType'. + Type = '' + + %Reference for the rig that this experiment is being run on, to be + %saved into the block data field 'rigName'. + RigName + + %Communcator object for sending signals updates to mc. Set by + %expServer + Communicator = io.DummyCommunicator + + %Delay (secs) before starting main experiment phase after experiment + %init phase has completed + PreDelay = 0 + + %Delay (secs) before beginning experiment cleanup phase after + %main experiment phase has completed (assuming an immediate abort + %wasn't requested). + PostDelay = 0 + + %Flag indicating whether the experiment is paused + IsPaused = false + + %Holds the wheel object, 'mouseInput' from the rig object. See also + %USERIG, HW.DAQROTARYENCODER + Wheel + + %Holds the object for interating with the lick detector. See also + %HW.DAQEDGECOUNTER + LickDetector + + %Holds the object for interating with the DAQ outputs (reward valve, + %etc.) See also HW.DAQCONTROLLER + DaqController + + %Get the handle to the PTB window opened by expServer + StimWindowPtr + + TextureById + + LayersByStim + + %Occulus viewing model + Occ + + Time + + Inputs + + Outputs + + Events + + Visual + + Audio % = aud.AudioRegistry + + %Holds the parameters structure for this experiment + Params + + ParamsLog + + %The bounds for the photodiode square + SyncBounds + + %Sync colour cycle (usually [0, 255]) - cycles through these each + %time the screen flips. + SyncColourCycle + + %Index into SyncColourCycle for next sync colour + NextSyncIdx + + %Alyx instance from client. See also SAVEDATA + AlyxInstance = [] end - function useRig(obj, rig) - obj.Clock = rig.clock; - obj.Data.rigName = rig.name; - obj.SyncBounds = rig.stimWindow.SyncBounds; - obj.SyncColourCycle = rig.stimWindow.SyncColourCycle; - obj.NextSyncIdx = 1; - obj.StimWindowPtr = rig.stimWindow.PtbHandle; - obj.Occ = vis.init(obj.StimWindowPtr); - if isfield(rig, 'screens') - obj.Occ.screens = rig.screens; - else - warning('squeak:hw', 'No screen configuration specified. Visual locations will be wrong.'); - end - obj.DaqController = rig.daqController; - obj.Wheel = rig.mouseInput; - if isfield(rig, 'lickDetector') - obj.LickDetector = rig.lickDetector; - end - if ~isempty(obj.DaqController.SignalGenerators) - outputNames = fieldnames(obj.Outputs); % Get list of all outputs specified in expDef function - for m = 1:length(outputNames) - id = find(strcmp(outputNames{m},... - obj.DaqController.ChannelNames)); % Find matching channel from rig hardware file - if id % if the output is present, create callback - obj.Listeners = [obj.Listeners - obj.Outputs.(outputNames{m}).onValue(@(v)obj.DaqController.command([zeros(size(v,1),id-1) v])) % pad value with zeros in order to output to correct channel - obj.Outputs.(outputNames{m}).onValue(@(v)fprintf('delivering output of %.2f\n',v)) - ]; - elseif strcmp(outputNames{m}, 'reward') % special case; rewardValve is always first signals generator in list - obj.Listeners = [obj.Listeners - obj.Outputs.reward.onValue(@(v)obj.DaqController.command(v)) - obj.Outputs.reward.onValue(@(v)fprintf('delivering reward of %.2f\n', v)) - ]; - end - end - end - end - - function abortPendingHandlers(obj, handler) - if nargin < 2 - % Sets all pending triggers inactive - [obj.Pending(:).isActive] = deal(false); - else - % Sets pending triggers for specified handler inactive - abortList = ([obj.Pending.handler] == handler); - [obj.Pending(abortList).isActive] = deal(false); - end - end + %% properties (SetAccess = protected) - function startPhase(obj, name, time) - % Starts a phase - % - % startPhase(NAME, TIME) causes a phase to start. The phase is added - % to the list of active phases. The change is also signalled to any - % interested triggers as having occured at TIME. - % - % Note: The time specified is signalled to the triggers, which is - % important for maintaining rigid timing offsets even if there are - % delays in calling this function. e.g. if a trigger is set to go off - % one second after a phase starts, the trigger will become due one - % second after the time specified, *not* one second from calling this - % function. - - % make sure the phase isn't already active - if ~any(strcmpi(obj.ActivePhases, name)) - % add the phase from list - obj.ActivePhases = [obj.ActivePhases; name]; - - % action any triggers contingent on this phase change - fireEvent(obj, exp.EventInfo([name 'Started'], time, obj)); - end + properties (SetAccess = protected) + %Number of stimulus window flips + StimWindowUpdateCount = 0 + + %Data from the currently running experiment, if any. + Data = struct + + %Currently active phases of the experiment. Cell array of their names + %(i.e. strings) + ActivePhases = {} + + Listeners + + Net + + SignalUpdates = struct('name', cell(500,1), 'value', cell(500,1), 'timestamp', cell(500,1)) + NumSignalUpdates = 0 + end - - function endPhase(obj, name, time) - % Ends a phase - % - % endPhase(NAME, TIME) causes a phase to end. The phase is removed - % from the list of active phases. The event is also signalled to any - % interested triggers as having occured at TIME. - % - % Note: The time specified is signalled to the triggers, which is - % important for maintaining rigid timing offsets even if there are - % delays in calling this function. e.g. if a trigger is set to go off - % one second after a phase starts, the trigger will become due one - % second after the time specified, *not* one second from calling this - % function. - - % make sure the phase is active - if any(strcmpi(obj.ActivePhases, name)) - % remove the phase from list - obj.ActivePhases(strcmpi(obj.ActivePhases, name)) = []; - - % action any triggers contingent on this phase change - fireEvent(obj, exp.EventInfo([name 'Ended'], time, obj)); - end - end - function addEventHandler(obj, handler, varargin) - % Adds one or more event handlers - % - % addEventHandler(HANLDER) adds one or more handlers specified by the - % HANLDER parameter (either a single object of class EventHandler, or - % an array of them), to this experiment's list of handlers. - if iscell(handler) - handler = cell2mat(handler); - end - obj.EventHandlers = [obj.EventHandlers; handler(:)]; - if ~isempty(varargin) - % deal with extra handle arguments recursively - addEventHandler(obj, varargin{:}); - end + %% properties (SetAccess && GetAccess = protected) + + properties (Access = protected) + %Set triggers awaiting activation: a list of Triggered objects, which + %are awaiting activation pending completion of their delay period. + Pending + + IsLooping = false %flag indicating whether to continue in experiment loop + + AsyncFlipping = false + + StimWindowInvalid = false end - function data = run(obj, ref) - % Runs the experiment - % - % run(REF) will start the experiment running, first initialising - % everything, then running the experiment loop until the experiment - % is complete. REF is a reference to be saved with the block data - % under the 'expRef' field, and will be used to ascertain the - % location to save the data into. If REF is an empty, i.e. [], the - % data won't be saved. - - if ~isempty(ref) - %ensure experiment ref exists - assert(dat.expExists(ref), 'Experiment ref ''%s'' does not exist', ref); - end - - %do initialisation - init(obj); - - obj.Data.expRef = ref; %record the experiment reference - - %Trigger the 'experimentInit' event so any handlers will be called - initInfo = exp.EventInfo('experimentInit', obj.Clock.now, obj); - fireEvent(obj, initInfo); - - %set pending handler to begin the experiment 'PreDelay' secs from now - start = exp.EventHandler('experimentInit', exp.StartPhase('experiment')); - start.addCallback(@(varargin) obj.Events.expStart.post(ref)); - obj.Pending = dueHandlerInfo(obj, start, initInfo, obj.Clock.now + obj.PreDelay); - - %refresh the stimulus window - Screen('Flip', obj.StimWindowPtr); - - try - % start the experiment loop - mainLoop(obj); - - %post comms notification with event name and time - if isempty(obj.AlyxInstance) - post(obj, 'AlyxRequest', obj.Data.expRef); %request token from client - pause(0.2) + %% methods (default - all public) + + methods + function obj = SignalsExp(paramStruct, rig) + % configure clock, ...? + clock = rig.clock; %don't like shadowing the clock fn here... + clockFun = @clock.now; + obj.TextureById = containers.Map('KeyType', 'char', 'ValueType', 'uint32'); + obj.LayersByStim = containers.Map; + obj.Inputs = sig.Registry(clockFun); + obj.Outputs = sig.Registry(clockFun); + obj.Visual = StructRef; + obj.Audio = audstream.Registry(rig.audioDevices); + obj.Events = sig.Registry(clockFun); + % configure signals + net = sig.Net; + obj.Net = net; + obj.Time = net.origin('t'); + obj.Events.expStart = net.origin('expStart'); + obj.Events.newTrial = net.origin('newTrial'); + obj.Events.expStop = net.origin('expStop'); + obj.Inputs.wheel = net.origin('wheel'); + obj.Inputs.lick = net.origin('lick'); + obj.Inputs.keyboard = net.origin('keyboard'); + % get global parameters & conditional parameters structs + [~, globalStruct, allCondStruct] = toConditionServer(... + exp.Parameters(paramStruct)); + % start the first trial after expStart + advanceTrial = net.origin('advanceTrial'); + % configure parameters signal + globalPars = net.origin('globalPars'); + allCondPars = net.origin('condPars'); + [obj.Params, hasNext, obj.Events.repeatNum] = exp.trialConditions(... + globalPars, allCondPars, advanceTrial); + + obj.Events.trialNum = obj.Events.newTrial.scan(@plus, 0); % track trial number + + lastTrialOver = then(~hasNext, true); + + % obj.Events.expStop = then(~hasNext, true); + % run experiment definition + if ischar(paramStruct.defFunction) + expDefFun = fileFunction(paramStruct.defFunction); + obj.Data.expDef = paramStruct.defFunction; + else + expDefFun = paramStruct.defFunction; + obj.Data.expDef = func2str(expDefFun); + end + fprintf('takes %i args\n', nargout(expDefFun)); + expDefFun(obj.Time, obj.Events, obj.Params, obj.Visual, obj.Inputs,... + obj.Outputs, obj.Audio); + % listeners + obj.Listeners = [ + obj.Events.expStart.map(true).into(advanceTrial) %expStart signals advance + obj.Events.endTrial.into(advanceTrial) %endTrial signals advance + advanceTrial.map(true).keepWhen(hasNext).into(obj.Events.newTrial) %newTrial if more + lastTrialOver.into(obj.Events.expStop) %newTrial if more + onValue(obj.Events.expStop, @(~)quit(obj));]; + % obj.Events.trialNum.onValue(fun.partial(@fprintf, 'trial %i started\n'))]; + % initialise the parameter signals + globalPars.post(rmfield(globalStruct, 'defFunction')); + allCondPars.post(allCondStruct); + % data struct + + % obj.Params = obj.Params.map(@(v)v, [], @(n,s)sig.Logger([n '[L]'],s)); + % initialise stim window frame times array, large enough for ~2 hours + obj.Data.stimWindowUpdateTimes = zeros(60*60*60*2, 1); + obj.Data.stimWindowRenderTimes = zeros(60*60*60*2, 1); + % obj.Data.stimWindowUpdateLags = zeros(60*60*60*2, 1); + obj.ParamsLog = obj.Params.log(); + obj.useRig(rig); end - %Trigger the 'experimentCleanup' event so any handlers will be called - cleanupInfo = exp.EventInfo('experimentCleanup', obj.Clock.now, obj); - fireEvent(obj, cleanupInfo); + function useRig(obj, rig) + obj.Clock = rig.clock; + obj.Data.rigName = rig.name; + obj.SyncBounds = rig.stimWindow.SyncBounds; + obj.SyncColourCycle = rig.stimWindow.SyncColourCycle; + obj.NextSyncIdx = 1; + obj.StimWindowPtr = rig.stimWindow.PtbHandle; + obj.Occ = vis.init(obj.StimWindowPtr); + if isfield(rig, 'screens') + obj.Occ.screens = rig.screens; + else + warning('squeak:hw', 'No screen configuration specified. Visual locations will be wrong.'); + end + obj.DaqController = rig.daqController; + obj.Wheel = rig.mouseInput; + if isfield(rig, 'lickDetector') + obj.LickDetector = rig.lickDetector; + end + if ~isempty(obj.DaqController.SignalGenerators) + outputNames = fieldnames(obj.Outputs); % Get list of all outputs specified in expDef function + for m = 1:length(outputNames) + id = find(strcmp(outputNames{m},... + obj.DaqController.ChannelNames)); % Find matching channel from rig hardware file + if id % if the output is present, create callback + obj.Listeners = [obj.Listeners + obj.Outputs.(outputNames{m}).onValue(@(v)obj.DaqController.command([zeros(size(v,1),id-1) v])) % pad value with zeros in order to output to correct channel + obj.Outputs.(outputNames{m}).onValue(@(v)fprintf('delivering output of %.2f\n',v)) + ]; + elseif strcmp(outputNames{m}, 'reward') % special case; rewardValve is always first signals generator in list + obj.Listeners = [obj.Listeners + obj.Outputs.reward.onValue(@(v)obj.DaqController.command(v)) + obj.Outputs.reward.onValue(@(v)fprintf('delivering reward of %.2f\n', v)) + ]; + end + end + end + end - %do our cleanup - cleanup(obj); + function abortPendingHandlers(obj, handler) + if nargin < 2 + % Sets all pending triggers inactive + [obj.Pending(:).isActive] = deal(false); + else + % Sets pending triggers for specified handler inactive + abortList = ([obj.Pending.handler] == handler); + [obj.Pending(abortList).isActive] = deal(false); + end + end - %return the data structure that has been built up - data = obj.Data; + function startPhase(obj, name, time) + % Starts a phase + % + % startPhase(NAME, TIME) causes a phase to start. The phase is added + % to the list of active phases. The change is also signalled to any + % interested triggers as having occured at TIME. + % + % Note: The time specified is signalled to the triggers, which is + % important for maintaining rigid timing offsets even if there are + % delays in calling this function. e.g. if a trigger is set to go off + % one second after a phase starts, the trigger will become due one + % second after the time specified, *not* one second from calling this + % function. + + % make sure the phase isn't already active + if ~any(strcmpi(obj.ActivePhases, name)) + % add the phase from list + obj.ActivePhases = [obj.ActivePhases; name]; - if ~isempty(ref) - saveData(obj); %save the data + % action any triggers contingent on this phase change + fireEvent(obj, exp.EventInfo([name 'Started'], time, obj)); + end end - catch ex - %mark that an exception occured in the block data, then save - obj.Data.endStatus = 'exception'; - obj.Data.exceptionMessage = ex.message; - if ~isempty(ref) - saveData(obj); %save the data + + function endPhase(obj, name, time) + % Ends a phase + % + % endPhase(NAME, TIME) causes a phase to end. The phase is removed + % from the list of active phases. The event is also signalled to any + % interested triggers as having occured at TIME. + % + % Note: The time specified is signalled to the triggers, which is + % important for maintaining rigid timing offsets even if there are + % delays in calling this function. e.g. if a trigger is set to go off + % one second after a phase starts, the trigger will become due one + % second after the time specified, *not* one second from calling this + % function. + + % make sure the phase is active + if any(strcmpi(obj.ActivePhases, name)) + % remove the phase from list + obj.ActivePhases(strcmpi(obj.ActivePhases, name)) = []; + + % action any triggers contingent on this phase change + fireEvent(obj, exp.EventInfo([name 'Ended'], time, obj)); + end end - ensureWindowReady(obj); % complete any outstanding refresh - %rethrow the exception - rethrow(ex) - end - end - - function bool = inPhase(obj, name) - % Reports whether currently in specified phase - % - % inPhase(NAME) checks whether the experiment is currently in the - % phase called NAME. - bool = any(strcmpi(obj.ActivePhases, name)); - end - - function log(obj, field, value) - % Logs the value in the experiment data - if isfield(obj.Data, field) - obj.Data.(field) = [obj.Data.(field) value]; - else - obj.Data.(field) = value; - end - end - - function quit(obj, immediately) - if isempty(obj.Events.expStop.Node.CurrValue) - obj.Events.expStop.post(true); - end - %stop delay timers. todo: need to use a less global tag - tmrs = timerfind('Tag', 'sig.delay'); - if ~isempty(tmrs) - stop(tmrs); - delete(tmrs); - end - - % set any pending handlers inactive - abortPendingHandlers(obj); - - % clear all phases except 'experiment' "dirtily", i.e. without - % setting off any triggers for those phases. - % *** IN FUTURE MAY CHANGE SO THAT WE DO END TRIAL CLEANLY *** - if nargin < 2 - immediately = false; - end - - if inPhase(obj, 'experiment') - obj.ActivePhases = {'experiment'}; % clear active phases except experiment - % end the experiment phase "cleanly", i.e. with triggers - endPhase(obj, 'experiment', obj.Clock.now); - else - obj.ActivePhases = {}; %clear active phases - end - - if immediately - %flag as 'aborted' meaning terminated early, and as quickly as possible - obj.Data.endStatus = 'aborted'; - else - %flag as 'quit', meaning quit before all trials were naturally complete, - %but still shut down with usual cleanup delays etc - obj.Data.endStatus = 'quit'; - end - - if immediately || obj.PostDelay == 0 - obj.IsLooping = false; %unset looping flag now - else - %add a pending handler to unset looping flag - %NB, since we create a pending item directly, the EventHandler delay - %and triggering event name are only set for clarity and wont be - %used - endExp = exp.EventHandler('experimentEnded'); %event name just for clarity - endExp.Delay = obj.PostDelay; %delay just for clarity - endExp.addCallback(@stopLooping); - pending = dueHandlerInfo(obj, endExp, [], obj.Clock.now + obj.PostDelay); - obj.Pending = [obj.Pending, pending]; - end - - function stopLooping(varargin) - obj.IsLooping = false; - end - end - - function ensureWindowReady(obj) - % complete any outstanding asynchronous flip - if obj.AsyncFlipping - % wait for flip to complete, and record the time - time = Screen('AsyncFlipEnd', obj.StimWindowPtr); - obj.AsyncFlipping = false; - time = fromPtb(obj.Clock, time); %convert ptb/sys time to our clock's time -% assert(obj.Data.stimWindowUpdateTimes(obj.StimWindowUpdateCount) == 0); - obj.Data.stimWindowUpdateTimes(obj.StimWindowUpdateCount) = time; -% lag = time - obj.Data.stimWindowRenderTimes(obj.StimWindowUpdateCount); -% obj.Data.stimWindowUpdateLags(obj.StimWindowUpdateCount) = lag; - end - end - - function queueSignalUpdate(obj, name, value) - timestamp = clock; - nupdates = obj.NumSignalUpdates; - if nupdates == length(obj.SignalUpdates) - %grow message queue by doubling in size - obj.SignalUpdates(2*end+1).value = []; - end - idx = nupdates + 1; - obj.SignalUpdates(idx).name = name; - obj.SignalUpdates(idx).value = value; - obj.SignalUpdates(idx).timestamp = timestamp; - obj.NumSignalUpdates = idx; - end - - function post(obj, id, msg) - send(obj.Communicator, id, msg); - end - - function sendSignalUpdates(obj) - try - if obj.NumSignalUpdates > 0 - post(obj, 'signals', obj.SignalUpdates(1:obj.NumSignalUpdates)); + + function addEventHandler(obj, handler, varargin) + % Adds one or more event handlers + % + % addEventHandler(HANLDER) adds one or more handlers specified by the + % HANLDER parameter (either a single object of class EventHandler, or + % an array of them), to this experiment's list of handlers. + if iscell(handler) + handler = cell2mat(handler); + end + obj.EventHandlers = [obj.EventHandlers; handler(:)]; + if ~isempty(varargin) + % deal with extra handle arguments recursively + addEventHandler(obj, varargin{:}); + end end - catch ex - warning(getReport(ex)); - end - obj.NumSignalUpdates = 0; - end - - function loadVisual(obj, name) - %% configure signals - layersSig = obj.Visual.(name).Node.CurrValue.layers; - obj.Listeners = [obj.Listeners - layersSig.onValue(fun.partial(@obj.newLayerValues, name))]; - newLayerValues(obj, name, layersSig.Node.CurrValue); - -% %% load textures -% layerData = obj.LayersByStim(name); -% Screen('BeginOpenGL', win); -% try -% for ii = 1:numel(layerData) -% id = layerData(ii).textureId; -% if ~obj.TextureById.isKey(id) -% obj.TextureById(id) = ... -% vis.loadLayerTextures(layerData(ii)); -% end -% end -% catch glEx -% Screen('EndOpenGL', win); -% rethrow(glEx); -% end -% Screen('EndOpenGL', win); - end - - function newLayerValues(obj, name, val) -% fprintf('new layer value for %s\n', name); -% show = [val.show] - if isKey(obj.LayersByStim, name) - prev = obj.LayersByStim(name); - prevshow = any([prev.show]); - else - prevshow = false; - end - obj.LayersByStim(name) = val; - - if any([val.show]) || prevshow - obj.StimWindowInvalid = true; - end - - end - - function delete(obj) - disp('delete exp.SqueakExp'); - obj.Net.delete(); - end - end - - methods (Access = protected) - function init(obj) - % Performs initialisation before running - % - % init() is called when the experiment is run before the experiment - % loop begins. Subclasses can override to perform their own - % initialisation, but must chain a call to this. - - % create and initialise a key press queue for responding to input - KbQueueCreate(); - KbQueueStart(); - - % MATLAB time stamp for starting the experiment - obj.Data.startDateTime = now; - obj.Data.startDateTimeStr = datestr(obj.Data.startDateTime); - - %init end status to nothing - obj.Data.endStatus = []; - - % load each visual stimulus - cellfun(@obj.loadVisual, fieldnames(obj.Visual)); - % each event signal should send signal updates - queuefun = @(n,s)s.onValue(fun.partial(@queueSignalUpdate, obj, n)); - evtlist = mapToCell(@(n,v)queuefun(['events.' n],v),... - fieldnames(obj.Events), struct2cell(obj.Events)); - outlist = mapToCell(@(n,v)queuefun(['outputs.' n],v),... - fieldnames(obj.Outputs), struct2cell(obj.Outputs)); - inlist = mapToCell(@(n,v)queuefun(['inputs.' n],v),... - fieldnames(obj.Inputs), struct2cell(obj.Inputs)); - parslist = queuefun('pars', obj.Params); - obj.Listeners = vertcat(obj.Listeners, ... - evtlist(:), outlist(:), inlist(:), parslist(:)); - end - - function cleanup(obj) - % Performs cleanup after experiment completes - % - % cleanup() is called when the experiment is run after the experiment - % loop completes. Subclasses can override to perform their own - % cleanup, but must chain a call to this. - - stopdatetime = now; - %clear the stimulus window - Screen('Flip', obj.StimWindowPtr); - - % collate the logs - %events - obj.Data.events = logs(obj.Events, obj.Clock.ReferenceTime); - %params - parsLog = obj.ParamsLog.Node.CurrValue; - obj.Data.paramsValues = [parsLog.value]; - obj.Data.paramsTimes = [parsLog.time]; - %inputs - obj.Data.inputs = logs(obj.Inputs, obj.Clock.ReferenceTime); - %outputs - obj.Data.outputs = logs(obj.Outputs, obj.Clock.ReferenceTime); - %audio -% obj.Data.audio = logs(audio, clockZeroTime); - - % MATLAB time stamp for ending the experiment - obj.Data.endDateTime = stopdatetime; - obj.Data.endDateTimeStr = datestr(obj.Data.endDateTime); - - % some useful data - obj.Data.duration = etime(... - datevec(obj.Data.endDateTime), datevec(obj.Data.startDateTime)); - - %clip the stim window update times array - obj.Data.stimWindowUpdateTimes((obj.StimWindowUpdateCount + 1):end) = []; -% obj.Data.stimWindowUpdateLags((obj.StimWindowUpdateCount + 1):end) = []; - obj.Data.stimWindowRenderTimes((obj.StimWindowUpdateCount + 1):end) = []; - - % release resources - obj.Listeners = []; - deleteGlTextures(obj); - KbQueueStop(); - KbQueueRelease(); - - % delete cached experiment definition function from memory - [~, exp_func] = fileparts(obj.Data.expDef); - clear(exp_func) - end - - function deleteGlTextures(obj) - tex = cell2mat(obj.TextureById.values); - win = obj.StimWindowPtr; - fprintf('Deleting %i textures\n', numel(tex)); - Screen('AsyncFlipEnd', win); - Screen('BeginOpenGL', win); - glDeleteTextures(numel(tex), tex); - obj.TextureById.remove(obj.TextureById.keys); - Screen('EndOpenGL', win); - end - - function mainLoop(obj) - % Executes the main experiment loop - % - % mainLoop() enters a loop that updates the stimulus window, checks - % for and deals with inputs, updates state and activates triggers. - % Will run until the experiment completes (phase 'experiment' ends). - - %set looping flag - obj.IsLooping = true; - t = obj.Clock.now; - % begin the loop - while obj.IsLooping - %% create a list of handlers that have become due - dueIdx = find([obj.Pending.dueTime] <= now(obj.Clock)); - ndue = length(dueIdx); - - %% check for and process any input - checkInput(obj); - - %% execute pending event handlers that have become due - for i = 1:ndue - due = obj.Pending(dueIdx(i)); - if due.isActive % check handler is still active - activateEventHandler(obj, due.handler, due.eventInfo, due.dueTime); - obj.Pending(dueIdx(i)).isActive = false; % set as inactive in pending - end + + function data = run(obj, ref) + % Runs the experiment + % + % run(REF) will start the experiment running, first initialising + % everything, then running the experiment loop until the experiment + % is complete. REF is a reference to be saved with the block data + % under the 'expRef' field, and will be used to ascertain the + % location to save the data into. If REF is an empty, i.e. [], the + % data won't be saved. + + if ~isempty(ref) + %ensure experiment ref exists + assert(dat.expExists(ref), 'Experiment ref ''%s'' does not exist', ref); + end + + %do initialisation + init(obj); + + obj.Data.expRef = ref; %record the experiment reference + + %Trigger the 'experimentInit' event so any handlers will be called + initInfo = exp.EventInfo('experimentInit', obj.Clock.now, obj); + fireEvent(obj, initInfo); + + %set pending handler to begin the experiment 'PreDelay' secs from now + start = exp.EventHandler('experimentInit', exp.StartPhase('experiment')); + start.addCallback(@(varargin) obj.Events.expStart.post(ref)); + obj.Pending = dueHandlerInfo(obj, start, initInfo, obj.Clock.now + obj.PreDelay); + + %refresh the stimulus window + Screen('Flip', obj.StimWindowPtr); + + try + % start the experiment loop + mainLoop(obj); + + %post comms notification with event name and time + if isempty(obj.AlyxInstance) + post(obj, 'AlyxRequest', obj.Data.expRef); %request token from client + pause(0.2) + end + + %Trigger the 'experimentCleanup' event so any handlers will be called + cleanupInfo = exp.EventInfo('experimentCleanup', obj.Clock.now, obj); + fireEvent(obj, cleanupInfo); + + %do our cleanup + cleanup(obj); + + %return the data structure that has been built up + data = obj.Data; + + if ~isempty(ref) + saveData(obj); %save the data + end + catch ex + %mark that an exception occured in the block data, then save + obj.Data.endStatus = 'exception'; + obj.Data.exceptionMessage = ex.message; + if ~isempty(ref) + saveData(obj); %save the data + end + ensureWindowReady(obj); % complete any outstanding refresh + %rethrow the exception + rethrow(ex) + end end - - % now remove executed (or otherwise inactived) ones from pending - inactiveIdx = ~[obj.Pending.isActive]; - obj.Pending(inactiveIdx) = []; - - %% signalling -% tic - wx = readAbsolutePosition(obj.Wheel); - post(obj.Inputs.wheel, wx); - if ~isempty(obj.LickDetector) - % read and log the current lick count - [nlicks, ~, licked] = readPosition(obj.LickDetector); - if licked - post(obj.Inputs.lick, nlicks); - fprintf('lick count now %i\n', nlicks); - end + + function bool = inPhase(obj, name) + % Reports whether currently in specified phase + % + % inPhase(NAME) checks whether the experiment is currently in the + % phase called NAME. + bool = any(strcmpi(obj.ActivePhases, name)); end - post(obj.Time, now(obj.Clock)); - runSchedule(obj.Net); - -% runSchedule(obj.Net); -% nChars = overfprintf(nChars, 'post took %.1fms\n', 1000*toc); - - %% redraw the stimulus window if it has been invalidated - if obj.StimWindowInvalid - ensureWindowReady(obj); % complete any outstanding refresh - % draw the visual frame -% tic - drawFrame(obj); -% toc; - if ~isempty(obj.SyncBounds) % render sync rectangle - % render sync region with next colour in cycle - col = obj.SyncColourCycle(obj.NextSyncIdx,:); - % render rectangle in the sync region bounds in the required colour - Screen('FillRect', obj.StimWindowPtr, col, obj.SyncBounds); - % cyclically increment the next sync idx - obj.NextSyncIdx = mod(obj.NextSyncIdx, size(obj.SyncColourCycle, 1)) + 1; - end - renderTime = now(obj.Clock); - % start the 'flip' of the frame onto the screen - Screen('AsyncFlipBegin', obj.StimWindowPtr); - obj.AsyncFlipping = true; - obj.StimWindowUpdateCount = obj.StimWindowUpdateCount + 1; - obj.Data.stimWindowRenderTimes(obj.StimWindowUpdateCount) = renderTime; - obj.StimWindowInvalid = false; + + function log(obj, field, value) + % Logs the value in the experiment data + if isfield(obj.Data, field) + obj.Data.(field) = [obj.Data.(field) value]; + else + obj.Data.(field) = value; + end end - if (obj.Clock.now - t) > 0.1 - sendSignalUpdates(obj); - t = obj.Clock.now; + + function quit(obj, immediately) + if isempty(obj.Events.expStop.Node.CurrValue) + obj.Events.expStop.post(true); + end + %stop delay timers. todo: need to use a less global tag + tmrs = timerfind('Tag', 'sig.delay'); + if ~isempty(tmrs) + stop(tmrs); + delete(tmrs); + end + + % set any pending handlers inactive + abortPendingHandlers(obj); + + % clear all phases except 'experiment' "dirtily", i.e. without + % setting off any triggers for those phases. + % *** IN FUTURE MAY CHANGE SO THAT WE DO END TRIAL CLEANLY *** + if nargin < 2 + immediately = false; + end + + if inPhase(obj, 'experiment') + obj.ActivePhases = {'experiment'}; % clear active phases except experiment + % end the experiment phase "cleanly", i.e. with triggers + endPhase(obj, 'experiment', obj.Clock.now); + else + obj.ActivePhases = {}; %clear active phases + end + + if immediately + %flag as 'aborted' meaning terminated early, and as quickly as possible + obj.Data.endStatus = 'aborted'; + else + %flag as 'quit', meaning quit before all trials were naturally complete, + %but still shut down with usual cleanup delays etc + obj.Data.endStatus = 'quit'; + end + + if immediately || obj.PostDelay == 0 + obj.IsLooping = false; %unset looping flag now + else + %add a pending handler to unset looping flag + %NB, since we create a pending item directly, the EventHandler delay + %and triggering event name are only set for clarity and wont be + %used + endExp = exp.EventHandler('experimentEnded'); %event name just for clarity + endExp.Delay = obj.PostDelay; %delay just for clarity + endExp.addCallback(@stopLooping); + pending = dueHandlerInfo(obj, endExp, [], obj.Clock.now + obj.PostDelay); + obj.Pending = [obj.Pending, pending]; + end + + function stopLooping(varargin) + obj.IsLooping = false; + end end -% q = toc; -% if q>0.005 -% fprintf(1, 'send updates took %.1fms\n', 1000*toc); -% end - drawnow; % allow other callbacks to execute - end - ensureWindowReady(obj); % complete any outstanding refresh - end - - function checkInput(obj) - % Checks for and handles inputs during experiment - % - % checkInput() is called during the experiment loop to check for and - % handle any inputs. This function specifically checks for any - % keyboard input that occurred since the last check, and passes that - % information on to handleKeyboardInput. Subclasses should override - % this function to check for any non-keyboard inputs of interest, but - % must chain a call to this function. - [pressed, keysPressed] = KbQueueCheck(); - if pressed - if any(keysPressed(obj.QuitKey)) - % handle the quit key being pressed - if strcmp(obj.Data.endStatus, 'quit') - %quitting already in progress - a second time means fast abort - fprintf('Abort fast (quit key pressed during quitting)\n'); - obj.quit(true); - else - fprintf('Quit key pressed\n'); - obj.quit(false); - end - elseif any(keysPressed(obj.PauseKey)) - fprintf('Pause key pressed\n'); - if obj.IsPaused - resume(obj); - else - pause(obj); - end - else -% key = keysPressed(find(keysPressed~=obj.QuitKey&... -% keysPressed~=obj.PauseKey,1,'first')); - key = KbName(keysPressed); - if ~isempty(key) - post(obj.Inputs.keyboard, key(1)); - end + function ensureWindowReady(obj) + % complete any outstanding asynchronous flip + if obj.AsyncFlipping + % wait for flip to complete, and record the time + time = Screen('AsyncFlipEnd', obj.StimWindowPtr); + obj.AsyncFlipping = false; + time = fromPtb(obj.Clock, time); %convert ptb/sys time to our clock's time + % assert(obj.Data.stimWindowUpdateTimes(obj.StimWindowUpdateCount) == 0); + obj.Data.stimWindowUpdateTimes(obj.StimWindowUpdateCount) = time; + % lag = time - obj.Data.stimWindowRenderTimes(obj.StimWindowUpdateCount); + % obj.Data.stimWindowUpdateLags(obj.StimWindowUpdateCount) = lag; + end end - end - end - - function fireEvent(obj, eventInfo, logEvent) - %fireEvent Called when an event occurs to log and handle them - % fireEvent(EVENTINFO) logs the time of the event, and checks the list - % of experiment event handlers for any that are listening to the event - % detailed in EVENTINFO. Those that are will be activated after their - % desired delay period. EVENTINFO must be an object of class EventInfo. - - event = eventInfo.Event; - - %post comms notification with event name and time - tnow = now(obj.Clock); - msg = {'update', obj.Data.expRef, 'event', event, tnow}; - post(obj, 'status', msg); - - if nargin < 3 - % log events by default - logEvent = true; - end - - if logEvent - % Save the actual time the event completed. For events that occur - % during a trial, timestamps are saved within the trial data, otherwise - % we just save in experiment-wide data. - log(obj, [event 'Time'], tnow); - end - - % create a list of handlers for this event - if isempty(obj.EventHandlers) - % handle special case bug in matlab - % if EventHandlers is empty, the alternate case below will fail so - % we handle it here - handleEventNames = {}; - else - handleEventNames = {obj.EventHandlers.Event}; - end - - evexp = ['(^|\|)' event '($|\|)']; - match = ~strcmp(regexp(handleEventNames, evexp, 'match', 'once'), ''); - handlers = obj.EventHandlers(match); - nhandlers = length(handlers); - for i = 1:nhandlers - if islogical(handlers(i).Delay) && handlers(i).Delay == false - % delay is false, so activate immediately - due = eventInfo.Time; - immediate = true; - else - % delayed handler - due = eventInfo.Time + handlers(i).Delay.secs; - immediate = false; + + function queueSignalUpdate(obj, name, value) + timestamp = clock; + nupdates = obj.NumSignalUpdates; + if nupdates == length(obj.SignalUpdates) + %grow message queue by doubling in size + obj.SignalUpdates(2*end+1).value = []; + end + idx = nupdates + 1; + obj.SignalUpdates(idx).name = name; + obj.SignalUpdates(idx).value = value; + obj.SignalUpdates(idx).timestamp = timestamp; + obj.NumSignalUpdates = idx; + end + + function post(obj, id, msg) + send(obj.Communicator, id, msg); end - - % if the handler has no delay, then activate it now, - % otherwise add it to our pending list - if immediate - activateEventHandler(obj, handlers(i), eventInfo, due); - else - pending = dueHandlerInfo(obj, handlers(i), eventInfo, due); - obj.Pending = [obj.Pending, pending]; % append to pending handlers + + function sendSignalUpdates(obj) + try + if obj.NumSignalUpdates > 0 + post(obj, 'signals', obj.SignalUpdates(1:obj.NumSignalUpdates)); + end + catch ex + warning(getReport(ex)); + end + obj.NumSignalUpdates = 0; + end + + function loadVisual(obj, name) + % configure signals + layersSig = obj.Visual.(name).Node.CurrValue.layers; + obj.Listeners = [obj.Listeners + layersSig.onValue(fun.partial(@obj.newLayerValues, name))]; + newLayerValues(obj, name, layersSig.Node.CurrValue); + + % %% load textures + % layerData = obj.LayersByStim(name); + % Screen('BeginOpenGL', win); + % try + % for ii = 1:numel(layerData) + % id = layerData(ii).textureId; + % if ~obj.TextureById.isKey(id) + % obj.TextureById(id) = ... + % vis.loadLayerTextures(layerData(ii)); + % end + % end + % catch glEx + % Screen('EndOpenGL', win); + % rethrow(glEx); + % end + % Screen('EndOpenGL', win); + end + + function newLayerValues(obj, name, val) + % fprintf('new layer value for %s\n', name); + % show = [val.show] + if isKey(obj.LayersByStim, name) + prev = obj.LayersByStim(name); + prevshow = any([prev.show]); + else + prevshow = false; + end + obj.LayersByStim(name) = val; + + if any([val.show]) || prevshow + obj.StimWindowInvalid = true; + end + + end + + function delete(obj) + disp('delete exp.SqueakExp'); + obj.Net.delete(); end - end - end - - function s = dueHandlerInfo(~, handler, eventInfo, dueTime) - s = struct('handler', handler,... - 'eventInfo', eventInfo,... - 'dueTime', dueTime,... - 'isActive', true); % handlers starts active - end - - function drawFrame(obj) - % Called to draw current stimulus window frame - % - % drawFrame(obj) does nothing in this class but can be overrriden - % in a subclass to draw the stimulus frame when it is invalidated - win = obj.StimWindowPtr; - layerValues = cell2mat(obj.LayersByStim.values()); - Screen('BeginOpenGL', win); - vis.draw(win, obj.Occ, layerValues, obj.TextureById); - Screen('EndOpenGL', win); end - function activateEventHandler(obj, handler, eventInfo, dueTime) - activate(handler, eventInfo, dueTime); - % if the handler requests the stimulus window be invalided, do so. - if handler.InvalidateStimWindow - obj.StimWindow.invalidate; - end - end + %% methods (SetAccess && GetAccess = protected) - function saveData(obj) - % save the data to the appropriate locations indicated by expRef - savepaths = dat.expFilePath(obj.Data.expRef, 'block'); - superSave(savepaths, struct('block', obj.Data)); - [subject, ~, ~] = dat.parseExpRef(obj.Data.expRef); - - % if this is a 'ChoiceWorld' experiment, let's save out for - % relevant data for basic behavioural analysis and register them to - % Alyx - if contains(lower(obj.Data.expDef), 'choiceworld') ... - && ~strcmp(subject, 'default') && isfield(obj.Data, 'events') ... - && ~strcmp(obj.Data.endStatus,'aborted') - try - fullpath = alf.block2ALF(obj.Data); - obj.AlyxInstance.registerFile(fullpath); - catch ex - warning(ex.identifier, 'Failed to register alf files: %s.', ex.message); + methods (Access = protected) + function init(obj) + % Performs initialisation before running + % + % init() is called when the experiment is run before the experiment + % loop begins. Subclasses can override to perform their own + % initialisation, but must chain a call to this. + + % create and initialise a key press queue for responding to input + KbQueueCreate(); + KbQueueStart(); + + % MATLAB time stamp for starting the experiment + obj.Data.startDateTime = now; + obj.Data.startDateTimeStr = datestr(obj.Data.startDateTime); + + %init end status to nothing + obj.Data.endStatus = []; + + % load each visual stimulus + cellfun(@obj.loadVisual, fieldnames(obj.Visual)); + % each event signal should send signal updates + queuefun = @(n,s)s.onValue(fun.partial(@queueSignalUpdate, obj, n)); + evtlist = mapToCell(@(n,v)queuefun(['events.' n],v),... + fieldnames(obj.Events), struct2cell(obj.Events)); + outlist = mapToCell(@(n,v)queuefun(['outputs.' n],v),... + fieldnames(obj.Outputs), struct2cell(obj.Outputs)); + inlist = mapToCell(@(n,v)queuefun(['inputs.' n],v),... + fieldnames(obj.Inputs), struct2cell(obj.Inputs)); + parslist = queuefun('pars', obj.Params); + obj.Listeners = vertcat(obj.Listeners, ... + evtlist(:), outlist(:), inlist(:), parslist(:)); + end + + function cleanup(obj) + % Performs cleanup after experiment completes + % + % cleanup() is called when the experiment is run after the experiment + % loop completes. Subclasses can override to perform their own + % cleanup, but must chain a call to this. + + stopdatetime = now; + %clear the stimulus window + Screen('Flip', obj.StimWindowPtr); + + % collate the logs + %events + obj.Data.events = logs(obj.Events, obj.Clock.ReferenceTime); + %params + parsLog = obj.ParamsLog.Node.CurrValue; + obj.Data.paramsValues = [parsLog.value]; + obj.Data.paramsTimes = [parsLog.time]; + %inputs + obj.Data.inputs = logs(obj.Inputs, obj.Clock.ReferenceTime); + %outputs + obj.Data.outputs = logs(obj.Outputs, obj.Clock.ReferenceTime); + %audio + % obj.Data.audio = logs(audio, clockZeroTime); + + % MATLAB time stamp for ending the experiment + obj.Data.endDateTime = stopdatetime; + obj.Data.endDateTimeStr = datestr(obj.Data.endDateTime); + + % some useful data + obj.Data.duration = etime(... + datevec(obj.Data.endDateTime), datevec(obj.Data.startDateTime)); + + %clip the stim window update times array + obj.Data.stimWindowUpdateTimes((obj.StimWindowUpdateCount + 1):end) = []; + % obj.Data.stimWindowUpdateLags((obj.StimWindowUpdateCount + 1):end) = []; + obj.Data.stimWindowRenderTimes((obj.StimWindowUpdateCount + 1):end) = []; + + % release resources + obj.Listeners = []; + deleteGlTextures(obj); + KbQueueStop(); + KbQueueRelease(); + + % delete cached experiment definition function from memory + [~, exp_func] = fileparts(obj.Data.expDef); + clear(exp_func) + end + + function deleteGlTextures(obj) + tex = cell2mat(obj.TextureById.values); + win = obj.StimWindowPtr; + fprintf('Deleting %i textures\n', numel(tex)); + Screen('AsyncFlipEnd', win); + Screen('BeginOpenGL', win); + glDeleteTextures(numel(tex), tex); + obj.TextureById.remove(obj.TextureById.keys); + Screen('EndOpenGL', win); + end + + function mainLoop(obj) + % Executes the main experiment loop + % + % mainLoop() enters a loop that updates the stimulus window, checks + % for and deals with inputs, updates state and activates triggers. + % Will run until the experiment completes (phase 'experiment' ends). + + %set looping flag + obj.IsLooping = true; + t = obj.Clock.now; + % begin the loop + while obj.IsLooping + % create a list of handlers that have become due + dueIdx = find([obj.Pending.dueTime] <= now(obj.Clock)); + ndue = length(dueIdx); + + % check for and process any input + checkInput(obj); + + % execute pending event handlers that have become due + for i = 1:ndue + due = obj.Pending(dueIdx(i)); + if due.isActive % check handler is still active + activateEventHandler(obj, due.handler, due.eventInfo, due.dueTime); + obj.Pending(dueIdx(i)).isActive = false; % set as inactive in pending + end + end + + % now remove executed (or otherwise inactived) ones from pending + inactiveIdx = ~[obj.Pending.isActive]; + obj.Pending(inactiveIdx) = []; + + % signalling + % tic + wx = readAbsolutePosition(obj.Wheel); + post(obj.Inputs.wheel, wx); + if ~isempty(obj.LickDetector) + % read and log the current lick count + [nlicks, ~, licked] = readPosition(obj.LickDetector); + if licked + post(obj.Inputs.lick, nlicks); + fprintf('lick count now %i\n', nlicks); + end + end + post(obj.Time, now(obj.Clock)); + runSchedule(obj.Net); + + % runSchedule(obj.Net); + % nChars = overfprintf(nChars, 'post took %.1fms\n', 1000*toc); + + % redraw the stimulus window if it has been invalidated + if obj.StimWindowInvalid + ensureWindowReady(obj); % complete any outstanding refresh + % draw the visual frame + % tic + drawFrame(obj); + % toc; + if ~isempty(obj.SyncBounds) % render sync rectangle + % render sync region with next colour in cycle + col = obj.SyncColourCycle(obj.NextSyncIdx,:); + % render rectangle in the sync region bounds in the required colour + Screen('FillRect', obj.StimWindowPtr, col, obj.SyncBounds); + % cyclically increment the next sync idx + obj.NextSyncIdx = mod(obj.NextSyncIdx, size(obj.SyncColourCycle, 1)) + 1; + end + renderTime = now(obj.Clock); + % start the 'flip' of the frame onto the screen + Screen('AsyncFlipBegin', obj.StimWindowPtr); + obj.AsyncFlipping = true; + obj.StimWindowUpdateCount = obj.StimWindowUpdateCount + 1; + obj.Data.stimWindowRenderTimes(obj.StimWindowUpdateCount) = renderTime; + obj.StimWindowInvalid = false; + end + if (obj.Clock.now - t) > 0.1 + sendSignalUpdates(obj); + t = obj.Clock.now; + end + + % q = toc; + % if q>0.005 + % fprintf(1, 'send updates took %.1fms\n', 1000*toc); + % end + drawnow; % allow other callbacks to execute + end + ensureWindowReady(obj); % complete any outstanding refresh end - end - - if isempty(obj.AlyxInstance) - warning('No Alyx token set'); - else - try - subject = dat.parseExpRef(obj.Data.expRef); - if strcmp(subject, 'default'); return; end - % Register saved files - obj.AlyxInstance.registerFile(savepaths{end}); -% obj.AlyxInstance.registerFile(savepaths{end}, 'mat',... -% {subject, expDate, seq}, 'Block', []); - % Save the session end time - if ~isempty(obj.AlyxInstance.SessionURL) - numCorrect = []; - if isfield(obj.Data, 'events') - numTrials = length(obj.Data.events.endTrialValues); - if isfield(obj.Data.events, 'feedbackValues') - numCorrect = sum(obj.Data.events.feedbackValues == 1); - end + + function checkInput(obj) + % Checks for and handles inputs during experiment + % + % checkInput() is called during the experiment loop to check for and + % handle any inputs. This function specifically checks for any + % keyboard input that occurred since the last check, and passes that + % information on to handleKeyboardInput. Subclasses should override + % this function to check for any non-keyboard inputs of interest, but + % must chain a call to this function. + [pressed, keysPressed] = KbQueueCheck(); + if pressed + if any(keysPressed(obj.QuitKey)) + % handle the quit key being pressed + if strcmp(obj.Data.endStatus, 'quit') + %quitting already in progress - a second time means fast abort + fprintf('Abort fast (quit key pressed during quitting)\n'); + obj.quit(true); + else + fprintf('Quit key pressed\n'); + obj.quit(false); + end + elseif any(keysPressed(obj.PauseKey)) + fprintf('Pause key pressed\n'); + if obj.IsPaused + resume(obj); + else + pause(obj); + end + else + % key = keysPressed(find(keysPressed~=obj.QuitKey&... + % keysPressed~=obj.PauseKey,1,'first')); + key = KbName(keysPressed); + if ~isempty(key) + post(obj.Inputs.keyboard, key(1)); + end + end + end + end + + function fireEvent(obj, eventInfo, logEvent) + %fireEvent Called when an event occurs to log and handle them + % fireEvent(EVENTINFO) logs the time of the event, and checks the list + % of experiment event handlers for any that are listening to the event + % detailed in EVENTINFO. Those that are will be activated after their + % desired delay period. EVENTINFO must be an object of class EventInfo. + + event = eventInfo.Event; + + %post comms notification with event name and time + tnow = now(obj.Clock); + msg = {'update', obj.Data.expRef, 'event', event, tnow}; + post(obj, 'status', msg); + + if nargin < 3 + % log events by default + logEvent = true; + end + + if logEvent + % Save the actual time the event completed. For events that occur + % during a trial, timestamps are saved within the trial data, otherwise + % we just save in experiment-wide data. + log(obj, [event 'Time'], tnow); + end + + % create a list of handlers for this event + if isempty(obj.EventHandlers) + % handle special case bug in matlab + % if EventHandlers is empty, the alternate case below will fail so + % we handle it here + handleEventNames = {}; + else + handleEventNames = {obj.EventHandlers.Event}; + end + + evexp = ['(^|\|)' event '($|\|)']; + match = ~strcmp(regexp(handleEventNames, evexp, 'match', 'once'), ''); + handlers = obj.EventHandlers(match); + nhandlers = length(handlers); + for i = 1:nhandlers + if islogical(handlers(i).Delay) && handlers(i).Delay == false + % delay is false, so activate immediately + due = eventInfo.Time; + immediate = true; + else + % delayed handler + due = eventInfo.Time + handlers(i).Delay.secs; + immediate = false; + end + + % if the handler has no delay, then activate it now, + % otherwise add it to our pending list + if immediate + activateEventHandler(obj, handlers(i), eventInfo, due); + else + pending = dueHandlerInfo(obj, handlers(i), eventInfo, due); + obj.Pending = [obj.Pending, pending]; % append to pending handlers + end + end + end + + function s = dueHandlerInfo(~, handler, eventInfo, dueTime) + s = struct('handler', handler,... + 'eventInfo', eventInfo,... + 'dueTime', dueTime,... + 'isActive', true); % handlers starts active + end + + function drawFrame(obj) + % Called to draw current stimulus window frame + % + % drawFrame(obj) does nothing in this class but can be overrriden + % in a subclass to draw the stimulus frame when it is invalidated + win = obj.StimWindowPtr; + layerValues = cell2mat(obj.LayersByStim.values()); + Screen('BeginOpenGL', win); + vis.draw(win, obj.Occ, layerValues, obj.TextureById); + Screen('EndOpenGL', win); + end + + function activateEventHandler(obj, handler, eventInfo, dueTime) + activate(handler, eventInfo, dueTime); + % if the handler requests the stimulus window be invalided, do so. + if handler.InvalidateStimWindow + obj.StimWindow.invalidate; + end + end + + function saveData(obj) + % save the data to the appropriate locations indicated by expRef + savepaths = dat.expFilePath(obj.Data.expRef, 'block'); + superSave(savepaths, struct('block', obj.Data)); + [subject, ~, ~] = dat.parseExpRef(obj.Data.expRef); + + % if this is a 'ChoiceWorld' experiment, let's save out for + % relevant data for basic behavioural analysis and register them to + % Alyx + if contains(lower(obj.Data.expDef), 'choiceworld') ... + && ~strcmp(subject, 'default') && isfield(obj.Data, 'events') ... + && ~strcmp(obj.Data.endStatus,'aborted') + try + fullpath = alf.block2ALF(obj.Data); + obj.AlyxInstance.registerFile(fullpath); + catch ex + warning(ex.identifier, 'Failed to register alf files: %s.', ex.message); + end + end + + if isempty(obj.AlyxInstance) + warning('No Alyx token set'); else - numTrials = 0; - numCorrect = 0; - end - sessionData = struct('end_time', obj.AlyxInstance.datestr(now), 'subject', subject); - if ~isempty(numTrials); sessionData.numberOfTrials = numTrials; end - if ~isempty(numCorrect); sessionData.numberOfCorrectTrials = numCorrect; end - obj.AlyxInstance.postData(obj.AlyxInstance.SessionURL,... - sessionData, 'put'); - else - % Retrieve session from endpoint -% subsessions = obj.AlyxInstance.getData(... -% sprintf('sessions?type=Experiment&subject=%s&number=%i', subject, seq)); - end - catch ex - warning(ex.identifier, 'Failed to register files to Alyx: %s', ex.message); + try + subject = dat.parseExpRef(obj.Data.expRef); + if strcmp(subject, 'default'); return; end + % Register saved files + obj.AlyxInstance.registerFile(savepaths{end}); + % obj.AlyxInstance.registerFile(savepaths{end}, 'mat',... + % {subject, expDate, seq}, 'Block', []); + % Save the session end time + if ~isempty(obj.AlyxInstance.SessionURL) + numCorrect = []; + if isfield(obj.Data, 'events') + numTrials = length(obj.Data.events.endTrialValues); + if isfield(obj.Data.events, 'feedbackValues') + numCorrect = sum(obj.Data.events.feedbackValues == 1); + end + else + numTrials = 0; + numCorrect = 0; + end + sessionData = struct('end_time', obj.AlyxInstance.datestr(now), 'subject', subject); + if ~isempty(numTrials); sessionData.numberOfTrials = numTrials; end + if ~isempty(numCorrect); sessionData.numberOfCorrectTrials = numCorrect; end + obj.AlyxInstance.postData(obj.AlyxInstance.SessionURL,... + sessionData, 'put'); + else + % Retrieve session from endpoint + % subsessions = obj.AlyxInstance.getData(... + % sprintf('sessions?type=Experiment&subject=%s&number=%i', subject, seq)); + end + catch ex + warning(ex.identifier, 'Failed to register files to Alyx: %s', ex.message); + end + end end - end end - end - + end \ No newline at end of file From 9bba914111732d86a258518acf0f28072aea3072 Mon Sep 17 00:00:00 2001 From: jaib1 <jaib1@mit.edu> Date: Wed, 31 Oct 2018 13:22:34 +0000 Subject: [PATCH 197/393] repoint submodules back at Jai's forks --- .gitmodules | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.gitmodules b/.gitmodules index 0022ed5b..32b91d63 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,16 +1,16 @@ [submodule "alyx-matlab"] path = alyx-matlab - url = https://github.com/cortex-lab/alyx-matlab/ + url = https://github.com/jaib1/alyx-matlab/ branch = dev [submodule "signals"] path = signals - url = https://github.com/cortex-lab/signals + url = https://github.com/jaib1/signals branch = dev [submodule "npy-matlab"] path = npy-matlab - url = https://github.com/kwikteam/npy-matlab - branch = master + url = https://github.com/jaib1/npy-matlab + branch = dev [submodule "wheelAnalysis"] path = wheelAnalysis - url = https://github.com/cortex-lab/wheelAnalysis - branch = master + url = https://github.com/jaib1/wheelAnalysis + branch = dev From 66f8d8f4448f8133cdb61a9e921048e05034edb7 Mon Sep 17 00:00:00 2001 From: jaib1 <jaib1@mit.edu> Date: Wed, 31 Oct 2018 13:33:26 +0000 Subject: [PATCH 198/393] last changes made to signals subrepo --- signals | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/signals b/signals index 13db4d1d..0596b553 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit 13db4d1d6a7e11427043b514867a3807eb1f12f7 +Subproject commit 0596b553fc7283ced49d2e403f6b681e37e00096 From 00af9896bd1723cb9671eba89e5565bd36f81d0b Mon Sep 17 00:00:00 2001 From: jaib1 <jaib1@mit.edu> Date: Wed, 31 Oct 2018 15:08:25 +0000 Subject: [PATCH 199/393] typo fixes for readme --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index fa163b12..120f73ae 100644 --- a/readme.md +++ b/readme.md @@ -1,7 +1,7 @@ ---------- # Rigbox -Rigbox is a (mostly) object-oriented MATLAB software package for designing and controlling behavioural experiments (principally, the [steering wheel setup](https://www.ucl.ac.uk/cortexlab/tools/wheel) which [we](https://www.ucl.ac.uk/cortexlab) developed to probe mouse behaviour. Rigbox requires two machines, one for stimulus presentation ('the stimulus server') and another for controlling and monitoring the experiment ('mc'). +Rigbox is a (mostly) object-oriented MATLAB software package for designing and controlling neurophysiological behavioural experiments (principally, the [steering wheel setup](https://www.ucl.ac.uk/cortexlab/tools/wheel) which [we](https://www.ucl.ac.uk/cortexlab) developed to probe mouse behaviour). Rigbox requires two machines, one for stimulus presentation ('the stimulus server') and another for controlling and monitoring the experiment ('mc'). ## Getting Started @@ -75,7 +75,7 @@ This opens a GUI that will allow you to choose a subject, edit some of the exper ## Code organization -Below is a list of the principle directories and their general purpose. +Below is a list of Rigbox's subdirectories and an overview of their respective contents. ### +dat From 814cfde112841d41f94fd888a1b918eb695532f3 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 31 Oct 2018 16:30:54 +0000 Subject: [PATCH 200/393] View all subjects window is now a better size --- +eui/AlyxPanel.m | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index 73277f52..94c848bc 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -629,7 +629,10 @@ function viewAllSubjects(obj) wr = ai.getData(ai.makeEndpoint('water-restricted-subjects')); % build a figure to show it - f = figure; % popup a new figure for this + f = figure('Name', 'All Water Restricted Subjects', 'NumberTitle', 'off'); % popup a new figure for this + p = get(f, 'Position'); + set(f, 'Position', [p(1) p(2) 295, p(4)]); + wrBox = uix.VBox('Parent', f); wrTable = uitable('Parent', wrBox,... 'FontName', 'Consolas',... From 4ec10e684f85a870f6730de99f5d489eb0dc9471 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 15 Nov 2018 12:04:48 +0000 Subject: [PATCH 201/393] Wheel signal availiable in degrees and mm --- +exp/SignalsExp.m | 5 ++- +hw/DaqRotaryEncoder.m | 71 +++++++++++++++++++++++++++++++++--------- +hw/PositionSensor.m | 3 +- 3 files changed, 63 insertions(+), 16 deletions(-) diff --git a/+exp/SignalsExp.m b/+exp/SignalsExp.m index d261de2c..8647baa6 100644 --- a/+exp/SignalsExp.m +++ b/+exp/SignalsExp.m @@ -220,6 +220,9 @@ function useRig(obj, rig) obj.DaqController = rig.daqController; obj.Wheel = rig.mouseInput; obj.Wheel.zero(); + obj.Inputs.wheelMM = obj.Inputs.wheel.map(@(x)obj.Wheel.MillimetresFactor*(x-obj.Wheel.ZeroOffset)); + obj.Inputs.wheelDeg = obj.Inputs.wheel.map(... + @(x)((x-obj.Wheel.ZeroOffset) / (obj.Wheel.EncoderResolution*4))*360); if isfield(rig, 'lickDetector') obj.LickDetector = rig.lickDetector; obj.LickDetector.zero(); @@ -719,7 +722,7 @@ function mainLoop(obj) obj.Data.stimWindowRenderTimes(obj.StimWindowUpdateCount) = renderTime; obj.StimWindowInvalid = false; end - if (obj.Clock.now - t) > 0.1 + if (obj.Clock.now - t) > 0.1 || obj.IsLooping == false sendSignalUpdates(obj); t = obj.Clock.now; end diff --git a/+hw/DaqRotaryEncoder.m b/+hw/DaqRotaryEncoder.m index c3d014fc..2d6cc123 100644 --- a/+hw/DaqRotaryEncoder.m +++ b/+hw/DaqRotaryEncoder.m @@ -36,31 +36,61 @@ % 2013-01 CB created properties - DaqSession = [] %DAQ session for input (see session-based interface docs) - DaqId = 'Dev1' %DAQ's device ID, e.g. 'Dev1' - DaqChannelId = 'ctr0' %DAQ's ID for the counter channel. e.g. 'ctr0' - %Size of DAQ counter range for detecting over- and underflows (e.g. if - %the DAQ's counter is 32-bit, this should be 2^32) + % DAQ session for input (see session-based interface docs) + DaqSession = [] + % DAQ's device ID, e.g. 'Dev1' + DaqId = 'Dev1' + % DAQ's ID for the counter channel. e.g. 'ctr0' + % Size of DAQ counter range for detecting over- and underflows (e.g. if + % the DAQ's counter is 32-bit, this should be 2^32) + DaqChannelId = 'ctr0' DaqCounterPeriod = 2^32 end + properties (SetObservable) + % Number of pulses per revolution. Found at the end of the K�BLER + % product number, e.g. 05.2400.1122.0100 has a resolution of 100 + EncoderResolution = 1024 + % Diameter of the wheel in mm + WheelDiameter = 62 + end + properties (Access = protected) - %Created when listenForAvailableData is called, allowing logging of - %positions during DAQ background acquision - DaqListener - DaqInputChannelIdx %Index into acquired input data matrices for our channel - LastDaqValue %Last value obtained from the DAQ counter - %Accumulated cycle number for position (i.e. when the DAQ's counter has - %over- or underflowed its range, this is incremented or decremented - %accordingly) + % Index into acquired input data matrices for our channel + DaqInputChannelIdx + % Last value obtained from the DAQ counter Accumulated cycle number for + % position (i.e. when the DAQ's counter has over- or underflowed its + % range, this is incremented or decremented accordingly) + LastDaqValue Cycle end + properties (Transient, Access = protected) + % Created when listenForAvailableData is called, allowing logging of + % positions during DAQ background acquision + DaqListener + PropertyListener + end + properties (Dependent) - DaqChannelIdx % index into DaqSession's channels for our data + % Index into DaqSession's channels for our data + DaqChannelIdx end methods + + function obj = DaqRotaryEncoder() + p1 = findprop(obj,'EncoderResolution'); + p2 = findprop(obj,'WheelDiameter'); + obj.PropertyListener = event.proplistener(obj,[p1, p2],'PostSet',... + @(src,~)obj.setMillimetresFactor(src)); + setMillimetresFactor(obj); + end + + function setMillimetresFactor(obj,~) + obj.MillimetresFactor = obj.WheelDiameter*pi/(obj.EncoderResolution*4); + end + function value = get.DaqChannelIdx(obj) inputs = find(strcmpi('input', io.daqSessionChannelDirections(obj.DaqSession))); value = inputs(obj.DaqInputChannelIdx); @@ -155,5 +185,18 @@ function daqListener(obj, ~, event) logSamples(obj, values, times); end end + + methods (Static) + function obj = loadobj(obj) + if isstruct(obj) % Handle error + obj = hw.DaqRotaryEncoder(); + else + p1 = findprop(obj,'EncoderResolution'); + p2 = findprop(obj,'WheelDiameter'); + obj.PropertyListener = event.proplistener(obj,[p1, p2],'PostSet',... + @(src,~)obj.setMillimetresFactor(src)); + end + end + end end diff --git a/+hw/PositionSensor.m b/+hw/PositionSensor.m index 350bbfe2..99ee3c62 100644 --- a/+hw/PositionSensor.m +++ b/+hw/PositionSensor.m @@ -1,7 +1,8 @@ classdef PositionSensor < hw.DataLogging %HW.POSITIONSENSOR Abstract class for tracking positions from a sensor % Takes care of logging positions and times every time readPosition is - % called. Has a zeroing function and a gain parameter. + % called. Has a zeroing function and a gain parameter. This class is + % intended only for linear position sensors. % % Part of Rigbox From 3d23d11e2e643e821f745cc992b8744184af3df8 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 15 Nov 2018 15:09:37 +0000 Subject: [PATCH 202/393] Moved useRig to be before expDef run --- +exp/SignalsExp.m | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/+exp/SignalsExp.m b/+exp/SignalsExp.m index 8647baa6..b96ee89e 100644 --- a/+exp/SignalsExp.m +++ b/+exp/SignalsExp.m @@ -165,11 +165,9 @@ allCondPars = net.origin('condPars'); [obj.Params, hasNext, obj.Events.repeatNum] = exp.trialConditions(... globalPars, allCondPars, advanceTrial); - obj.Events.trialNum = obj.Events.newTrial.scan(@plus, 0); % track trial number - lastTrialOver = then(~hasNext, true); - + obj.useRig(rig); % obj.Events.expStop = then(~hasNext, true); % run experiment definition if ischar(paramStruct.defFunction) @@ -201,7 +199,6 @@ obj.Data.stimWindowRenderTimes = zeros(60*60*60*2, 1); % obj.Data.stimWindowUpdateLags = zeros(60*60*60*2, 1); obj.ParamsLog = obj.Params.log(); - obj.useRig(rig); end function useRig(obj, rig) From 7d222c1ad9a5d6c1bff09cb7499584f90b870ea6 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 15 Nov 2018 16:01:25 +0000 Subject: [PATCH 203/393] Skip repeats on wheel trace and zero offset now accessable --- +exp/SignalsExp.m | 5 +++-- +hw/PositionSensor.m | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/+exp/SignalsExp.m b/+exp/SignalsExp.m index b96ee89e..e84a864f 100644 --- a/+exp/SignalsExp.m +++ b/+exp/SignalsExp.m @@ -217,9 +217,10 @@ function useRig(obj, rig) obj.DaqController = rig.daqController; obj.Wheel = rig.mouseInput; obj.Wheel.zero(); - obj.Inputs.wheelMM = obj.Inputs.wheel.map(@(x)obj.Wheel.MillimetresFactor*(x-obj.Wheel.ZeroOffset)); + obj.Inputs.wheelMM = obj.Inputs.wheel.map(@... + (x)obj.Wheel.MillimetresFactor*(x-obj.Wheel.ZeroOffset)).skipRepeats(); obj.Inputs.wheelDeg = obj.Inputs.wheel.map(... - @(x)((x-obj.Wheel.ZeroOffset) / (obj.Wheel.EncoderResolution*4))*360); + @(x)((x-obj.Wheel.ZeroOffset) / (obj.Wheel.EncoderResolution*4))*360).skipRepeats(); if isfield(rig, 'lickDetector') obj.LickDetector = rig.lickDetector; obj.LickDetector.zero(); diff --git a/+hw/PositionSensor.m b/+hw/PositionSensor.m index 99ee3c62..87aac7f1 100644 --- a/+hw/PositionSensor.m +++ b/+hw/PositionSensor.m @@ -18,7 +18,7 @@ LastPosition %Most recent position read end - properties (Access = protected) + properties (SetAccess = protected) ZeroOffset = 0 end From 385d6ba3acad6cf724bd88ecf9a543c67919009c Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 16 Nov 2018 14:27:04 +0000 Subject: [PATCH 204/393] Moved wheel out of useRig method: conflict with setting output --- +exp/SignalsExp.m | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/+exp/SignalsExp.m b/+exp/SignalsExp.m index e84a864f..8b0ded5b 100644 --- a/+exp/SignalsExp.m +++ b/+exp/SignalsExp.m @@ -153,6 +153,12 @@ obj.Events.newTrial = net.origin('newTrial'); obj.Events.expStop = net.origin('expStop'); obj.Inputs.wheel = net.origin('wheel'); + obj.Wheel = rig.mouseInput; + obj.Wheel.zero(); + obj.Inputs.wheelMM = obj.Inputs.wheel.map(@... + (x)obj.Wheel.MillimetresFactor*(x-obj.Wheel.ZeroOffset)).skipRepeats(); + obj.Inputs.wheelDeg = obj.Inputs.wheel.map(... + @(x)((x-obj.Wheel.ZeroOffset) / (obj.Wheel.EncoderResolution*4))*360).skipRepeats(); obj.Inputs.lick = net.origin('lick'); obj.Inputs.keyboard = net.origin('keyboard'); % get global parameters & conditional parameters structs @@ -167,7 +173,6 @@ globalPars, allCondPars, advanceTrial); obj.Events.trialNum = obj.Events.newTrial.scan(@plus, 0); % track trial number lastTrialOver = then(~hasNext, true); - obj.useRig(rig); % obj.Events.expStop = then(~hasNext, true); % run experiment definition if ischar(paramStruct.defFunction) @@ -199,6 +204,7 @@ obj.Data.stimWindowRenderTimes = zeros(60*60*60*2, 1); % obj.Data.stimWindowUpdateLags = zeros(60*60*60*2, 1); obj.ParamsLog = obj.Params.log(); + obj.useRig(rig); end function useRig(obj, rig) @@ -215,12 +221,6 @@ function useRig(obj, rig) warning('squeak:hw', 'No screen configuration specified. Visual locations will be wrong.'); end obj.DaqController = rig.daqController; - obj.Wheel = rig.mouseInput; - obj.Wheel.zero(); - obj.Inputs.wheelMM = obj.Inputs.wheel.map(@... - (x)obj.Wheel.MillimetresFactor*(x-obj.Wheel.ZeroOffset)).skipRepeats(); - obj.Inputs.wheelDeg = obj.Inputs.wheel.map(... - @(x)((x-obj.Wheel.ZeroOffset) / (obj.Wheel.EncoderResolution*4))*360).skipRepeats(); if isfield(rig, 'lickDetector') obj.LickDetector = rig.lickDetector; obj.LickDetector.zero(); From 81d7e8d39e146d48fc6d864b2f6bdb6ba8260b70 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 16 Nov 2018 14:56:46 +0000 Subject: [PATCH 205/393] Fix to paths; npy-matlab added properly --- addRigboxPaths.m | 2 +- alyx-matlab | 2 +- cortexlab/+git/changes.m | 1 + cortexlab/+git/update.m | 7 +++++++ npy-matlab | 2 +- signals | 2 +- 6 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 cortexlab/+git/changes.m diff --git a/addRigboxPaths.m b/addRigboxPaths.m index cd5d85ee..b705367e 100644 --- a/addRigboxPaths.m +++ b/addRigboxPaths.m @@ -110,7 +110,7 @@ function addRigboxPaths(savePaths) % NumPy binary files. Used by Rigbox to save data as .npy files with the % ALF (ALex File) naming convention. For more information please visit: % https://docs.scipy.org/doc/numpy-dev/neps/npy-format.html -addpath(fullfile(root, 'npy-matlab')); +addpath(fullfile(root, 'npy-matlab', 'npy-matlab')); % Add the Java paths for Java WebSockets used for communications between % the stimulus computer and the master computer diff --git a/alyx-matlab b/alyx-matlab index ddc6f766..5420b606 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit ddc6f766fc25e1a57cc6c61a90aa5083cbd46543 +Subproject commit 5420b6065ccca9b438827fc1419fe30566e2ed7b diff --git a/cortexlab/+git/changes.m b/cortexlab/+git/changes.m new file mode 100644 index 00000000..9232640b --- /dev/null +++ b/cortexlab/+git/changes.m @@ -0,0 +1 @@ +addRigboxPaths; \ No newline at end of file diff --git a/cortexlab/+git/update.m b/cortexlab/+git/update.m index 14814d6b..52705ca1 100644 --- a/cortexlab/+git/update.m +++ b/cortexlab/+git/update.m @@ -59,5 +59,12 @@ function update(fatalOnError, scheduled) end end +% Run any new tasks +changesPath = fullfile(root, 'cortexlab', '+git', 'changes.m'); +if exist(changesPath, 'file') + git.changes; + delete(changesPath); +end + cd(origDir) end \ No newline at end of file diff --git a/npy-matlab b/npy-matlab index a99e00f7..b7b0a4ef 160000 --- a/npy-matlab +++ b/npy-matlab @@ -1 +1 @@ -Subproject commit a99e00f78c72a7ec5f9c3074242ffaf242de9448 +Subproject commit b7b0a4ef6ba26d98a8c54e651d5444083c88311c diff --git a/signals b/signals index 51f56c0d..19fb9da3 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit 51f56c0d90ed2232eb3255a1773d45ad695adaf4 +Subproject commit 19fb9da3c4485782c81f2e57d43337da0b77940d From 8982b199b4bdd1fbaa76efc110114e931cae8c84 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 16 Nov 2018 15:35:44 +0000 Subject: [PATCH 206/393] Update from master --- signals | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/signals b/signals index 19fb9da3..071e4420 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit 19fb9da3c4485782c81f2e57d43337da0b77940d +Subproject commit 071e44207410dab429f5c962999a56ade4d050dc From b53653c9d99feb69301a6030f00fe96b06bceb81 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 16 Nov 2018 17:12:39 +0000 Subject: [PATCH 207/393] Docstring fix --- +hw/TLOutputChrono.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/+hw/TLOutputChrono.m b/+hw/TLOutputChrono.m index 1bf9c52d..bacca3fe 100644 --- a/+hw/TLOutputChrono.m +++ b/+hw/TLOutputChrono.m @@ -56,8 +56,8 @@ function init(obj, timeline) % INIT Initialize the output session % INIT(obj, timeline) is called when timeline is initialized. - % Creates the DAQ session and ensures that the clocking pulse test - % can not be read back + % Creates the DAQ session and ensures that the clocking test pulse + % can be read back % % See Also HW.TIMELINE/INIT if obj.Enable From 64aa3ca54870ac002ea5781eaa8f5aa0e5df4f62 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 16 Nov 2018 17:23:43 +0000 Subject: [PATCH 208/393] Fix log for when no future train dates --- +eui/AlyxPanel.m | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index 94c848bc..76cf0377 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -316,11 +316,13 @@ function giveFutureWater(obj) futDates = thisDate + (1:length(amt)); % datenum of all input future dates futTrnDates = futDates(amt < 0); % future training dates - dat.saveParamProfile('WeekendWater', obj.Subject, futTrnDates); - [~,days] = weekday(futTrnDates, 'long'); - delim = iff(size(days,1) < 3, ' and ', {', ', ' and '}); - obj.log('%s marked for training on %s',... - obj.Subject, strjoin(strtrim(string(days)), delim)); + if any(futTrnDates) + dat.saveParamProfile('WeekendWater', obj.Subject, futTrnDates); + [~,days] = weekday(futTrnDates, 'long'); + delim = iff(size(days,1) < 3, ' and ', {', ', ' and '}); + obj.log('%s marked for training on %s',... + obj.Subject, strjoin(strtrim(string(days)), delim)); + end futWtrDates = futDates(amt > 0); % future water giving dates amtWtrDates = amt(amt > 0); % amount of water to give on future water dates From 9b7a815c18695a93c87bda8f7278578d508e9289 Mon Sep 17 00:00:00 2001 From: Olivier Winter <olivier.winter@research.fchampalimaud.org> Date: Mon, 19 Nov 2018 16:59:14 +0000 Subject: [PATCH 209/393] Alyx changes in subject endpoint --- +dat/parseAlyxInstance.m | 6 +++--- +eui/AlyxPanel.m | 8 ++++---- +eui/MControl.m | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/+dat/parseAlyxInstance.m b/+dat/parseAlyxInstance.m index 2eb09711..9db5cf4a 100644 --- a/+dat/parseAlyxInstance.m +++ b/+dat/parseAlyxInstance.m @@ -18,9 +18,9 @@ ai = varargin{2}; % extract AlyxInstance struct if isstruct(ai) % if there is an AlyxInstance ai = orderfields(ai); % alphabetize fields - % remove water requirement remaining field - if isfield(ai, 'water_requirement_remaining') - ai = rmfield(ai, 'water_requirement_remaining'); + % remove water remaining_water field + if isfield(ai, 'remaining_water') + ai = rmfield(ai, 'remaining_water'); end fname = fieldnames(ai); % get fieldnames emp = structfun(@isempty, ai); % find empty fields diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index 73277f52..53e2c083 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -382,12 +382,12 @@ function dispWaterReq(obj, src, ~) weight_pct = '> 80%'; end % Round up water remaining to the near 0.01 - remainder = obj.round(s(idx).water_requirement_remaining, 'up'); + remainder = obj.round(s(idx).remaining_water, 'up'); % Set text set(obj.WaterRequiredText, 'ForegroundColor', colour, 'String', ... sprintf(['Subject %s requires %.2f of %.2f today\n\t '... 'Weight today: %.2f (%s) Water today: %.2f'], obj.Subject, ... - remainder, obj.round(s(idx).water_requirement_total, 'up'), weight, ... + remainder, obj.round(s(idx).expected_water, 'up'), weight, ... weight_pct, obj.round(sum([water gel]), 'down'))); % Set WaterRemaining attribute for changeWaterText callback obj.WaterRemaining = remainder; @@ -640,11 +640,11 @@ function viewAllSubjects(obj) colorgen = @(colorNum,text) ['<html><body bgcolor=#',htmlColor(colorNum),'>',text,'</body></html>']; wrdat = cellfun(@(x)colorgen(1-double(x>0)*[0 0.3 0.3],... - sprintf('%.2f',obj.round(x, 'up'))), {wr.water_requirement_remaining}, 'uni', false); + sprintf('%.2f',obj.round(x, 'up'))), {wr.remaining_water}, 'uni', false); set(wrTable, 'ColumnName', {'Name', 'Water Required', 'Remaining Requirement'}, ... 'Data', horzcat({wr.nickname}', ... - cellfun(@(x)sprintf('%.2f',obj.round(x, 'up')),{wr.water_requirement_total}', 'uni', false), ... + cellfun(@(x)sprintf('%.2f',obj.round(x, 'up')),{wr.expected_water}', 'uni', false), ... wrdat'), ... 'ColumnEditable', false(1,3)); end diff --git a/+eui/MControl.m b/+eui/MControl.m index 9e5a30ca..3ca11966 100644 --- a/+eui/MControl.m +++ b/+eui/MControl.m @@ -346,8 +346,8 @@ function rigExpStopped(obj, rig, evt) % Announce that the experiment has stopped subject = dat.parseExpRef(evt.Ref); sd = rig.AlyxInstance.getData(sprintf('subjects/%s', subject)); obj.log('Water requirement remaining for %s: %.2f (%.2f already given)', ... - subject, sd.water_requirement_remaining, ... - sd.water_requirement_total-sd.water_requirement_remaining); + subject, sd.remaining_water, ... + sd.expected_water-sd.remaining_water); catch subject = dat.parseExpRef(evt.Ref); obj.log('Warning: unable to query Alyx about %s''s water requirements', subject); From a8c895c66cd0f70431ed771693bf06441de2da93 Mon Sep 17 00:00:00 2001 From: Olivier Winter <olivier.winter@research.fchampalimaud.org> Date: Mon, 19 Nov 2018 18:01:18 +0000 Subject: [PATCH 210/393] eui.AlyxPanel: water restriction management endpoint refactoring --- +eui/AlyxPanel.m | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index 53e2c083..009d7f52 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -365,12 +365,12 @@ function dispWaterReq(obj, src, ~) else record = struct(); end - weight = getOr(record, 'weight_measured', NaN); % Get today's measured weight - water = getOr(record, 'water_given', 0); % Get total water given - gel = getOr(record, 'hydrogel_given', 0); % Get total gel given - weight_expected = getOr(record, 'weight_expected', NaN); + weight = getOr(record, 'weight', NaN); % Get today's measured weight + water = getOr(record, 'given_water_liquid', 0); % Get total water given + gel = getOr(record, 'given_water_hydrogel', 0); % Get total gel given + expected_weight = getOr(record, 'expected_weight', NaN); % Set colour based on weight percentage - weight_pct = (weight-wr.implant_weight)/(weight_expected-wr.implant_weight); + weight_pct = (weight-wr.implant_weight)/(expected_weight-wr.implant_weight); if weight_pct < 0.8 % Mouse below 80% original weight colour = [0.91, 0.41, 0.17]; % Orange weight_pct = '< 80%'; @@ -541,7 +541,7 @@ function viewSubjectHistory(obj, ax) obj.log('No weight data found for subject %s', obj.Subject); return end - expected = [records.weight_expected]; + expected = [records.expected_weight]; expected(expected==0) = nan; dates = cellfun(@(x)datenum(x), {records.date}); @@ -555,7 +555,7 @@ function viewSubjectHistory(obj, ax) ax = axes('Parent', plotBox); end - plot(ax, dates, [records.weight_measured], '.-'); + plot(ax, dates, [records.weight], '.-'); hold(ax, 'on'); plot(ax, dates, ((expected-iw)*0.7)+iw, 'r', 'LineWidth', 2.0); plot(ax, dates, ((expected-iw)*0.8)+iw, 'LineWidth', 2.0, 'Color', [244, 191, 66]/255); @@ -572,7 +572,7 @@ function viewSubjectHistory(obj, ax) if nargin==1 ax = axes('Parent', plotBox); - plot(ax, dates, ([records.weight_measured]-iw)./(expected-iw), '.-'); + plot(ax, dates, ([records.weight]-iw)./(expected-iw), '.-'); hold(ax, 'on'); plot(ax, dates, 0.7*ones(size(dates)), 'r', 'LineWidth', 2.0); plot(ax, dates, 0.8*ones(size(dates)), 'LineWidth', 2.0, 'Color', [244, 191, 66]/255); @@ -582,11 +582,11 @@ function viewSubjectHistory(obj, ax) ylabel(ax, 'weight as pct (%)'); axWater = axes('Parent',plotBox); - plot(axWater, dates, obj.round([records.water_given]+[records.hydrogel_given], 'up'), '.-'); + plot(axWater, dates, obj.round([records.given_water_liquid]+[records.given_water_hydrogel], 'up'), '.-'); hold(axWater, 'on'); - plot(axWater, dates, obj.round([records.hydrogel_given], 'down'), '.-'); - plot(axWater, dates, obj.round([records.water_given], 'down'), '.-'); - plot(axWater, dates, obj.round([records.water_expected], 'up'), 'r', 'LineWidth', 2.0); + plot(axWater, dates, obj.round([records.given_water_hydrogel], 'down'), '.-'); + plot(axWater, dates, obj.round([records.given_water_liquid], 'down'), '.-'); + plot(axWater, dates, obj.round([records.expected_water], 'up'), 'r', 'LineWidth', 2.0); box(axWater, 'off'); xlim(axWater, [min(dates) max(dates)]); set(axWater, 'XTickLabel', arrayfun(@(x)datestr(x, 'dd-mmm'), get(axWater, 'XTick'), 'uni', false)) @@ -597,22 +597,22 @@ function viewSubjectHistory(obj, ax) histTable = uitable('Parent', histbox,... 'FontName', 'Consolas',... 'RowName', []); - weightsByDate = num2cell([records.weight_measured]); + weightsByDate = num2cell([records.weight]); weightsByDate = cellfun(@(x)sprintf('%.1f', x), weightsByDate, 'uni', false); - weightsByDate(isnan([records.weight_measured])) = {[]}; - weightPctByDate = num2cell(([records.weight_measured]-iw)./(expected-iw)); + weightsByDate(isnan([records.weight])) = {[]}; + weightPctByDate = num2cell(([records.weight]-iw)./(expected-iw)); weightPctByDate = cellfun(@(x)sprintf('%.1f', x*100), weightPctByDate, 'uni', false); - weightPctByDate(isnan([records.weight_measured])) = {[]}; + weightPctByDate(isnan([records.weight])) = {[]}; dat = horzcat(... arrayfun(@(x)datestr(x), dates', 'uni', false), ... weightsByDate', ... - arrayfun(@(x)sprintf('%.1f', 0.8*(x-iw)+iw), [records.weight_expected]', 'uni', false), ... + arrayfun(@(x)sprintf('%.1f', 0.8*(x-iw)+iw), [records.expected_weight]', 'uni', false), ... weightPctByDate'); waterDat = (... - num2cell(horzcat([records.water_given]', [records.hydrogel_given]', ... - [records.water_given]'+[records.hydrogel_given]', [records.water_expected]',... - [records.water_given]'+[records.hydrogel_given]'-[records.water_expected]'))); + num2cell(horzcat([records.given_water_liquid]', [records.given_water_hydrogel]', ... + [records.given_water_liquid]'+[records.given_water_hydrogel]', [records.expected_water]',... + [records.given_water_liquid]'+[records.given_water_hydrogel]'-[records.expected_water]'))); waterDat = cellfun(@(x)sprintf('%.2f', x), waterDat, 'uni', false); dat = horzcat(dat, waterDat); @@ -705,4 +705,4 @@ function log(obj, varargin) end end end -end \ No newline at end of file +end From e960a1abad56f2ad134b18a49bbcfdea4ea87081 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 20 Nov 2018 13:44:55 +0000 Subject: [PATCH 211/393] Water type set in hardware --- +eui/ExpPanel.m | 12 +++++++----- +exp/SignalsExp.m | 24 +++++++++++++++++++----- +hw/RewardValveControl.m | 3 +++ 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/+eui/ExpPanel.m b/+eui/ExpPanel.m index e3434f14..d9fd958c 100644 --- a/+eui/ExpPanel.m +++ b/+eui/ExpPanel.m @@ -213,6 +213,7 @@ function expStopped(obj, rig, ~) % ended. This function also records to Alyx the amount of water, % if any, that the subject received during the task. % + % TODO: Move water to save data functions % See also EXPSTARTED, ALYX.POSTWATER set(obj.StatusLabel, 'String', 'Completed'); %staus to completed obj.ExpRunning = false; @@ -236,11 +237,12 @@ function expStopped(obj, rig, ~) sum([obj.Block.trial.feedbackType]==1); end if numel(amount)>1; amount = amount(1); end % Take first element (second being laser) - otherwise - infoFields = {obj.InfoFields.String}; - inc = cellfun(@(x) any(strfind(x(:)','�l')), {obj.InfoFields.String}); % Find event values ending with 'ul'. - reward = cell2mat(cellfun(@str2num,strsplit(infoFields{find(inc,1)},'�l'),'UniformOutput',0)); - amount = iff(isempty(reward),0,@()reward); + otherwise + % Done in exp.SignalsExp/saveData + %infoFields = {obj.InfoFields.String}; + %inc = cellfun(@(x) any(strfind(x(:)','�l')), {obj.InfoFields.String}); % Find event values ending with 'ul'. + %reward = cell2mat(cellfun(@str2num,strsplit(infoFields{find(inc,1)},'�l'),'UniformOutput',0)); + %amount = iff(isempty(reward),0,@()reward); end if ~any(amount); return; end % Return if no water was given try diff --git a/+exp/SignalsExp.m b/+exp/SignalsExp.m index d261de2c..b8f1b74c 100644 --- a/+exp/SignalsExp.m +++ b/+exp/SignalsExp.m @@ -892,7 +892,8 @@ function saveData(obj) % obj.AlyxInstance.registerFile(savepaths{end}, 'mat',... % {subject, expDate, seq}, 'Block', []); % Save the session end time - if ~isempty(obj.AlyxInstance.SessionURL) + url = obj.AlyxInstance.SessionURL; + if ~isempty(url) numCorrect = []; if isfield(obj.Data, 'events') numTrials = length(obj.Data.events.endTrialValues); @@ -903,19 +904,32 @@ function saveData(obj) numTrials = 0; numCorrect = 0; end + % Update Alyx session with end time, trial counts and water tye sessionData = struct('end_time', obj.AlyxInstance.datestr(now), 'subject', subject); if ~isempty(numTrials); sessionData.numberOfTrials = numTrials; end if ~isempty(numCorrect); sessionData.numberOfCorrectTrials = numCorrect; end - obj.AlyxInstance.postData(obj.AlyxInstance.SessionURL,... - sessionData, 'put'); + obj.AlyxInstance.postData(url, sessionData, 'put'); else % Retrieve session from endpoint -% subsessions = obj.AlyxInstance.getData(... -% sprintf('sessions?type=Experiment&subject=%s&number=%i', subject, seq)); + % subsessions = obj.AlyxInstance.getData(... + % sprintf('sessions?type=Experiment&subject=%s&number=%i', subject, seq)); end catch ex warning(ex.identifier, 'Failed to register files to Alyx: %s', ex.message); end + % Post water to Alyx + try + valve_controller = obj.DaqController.SignalGenerators(strcmp(obj.DaqController.ChannelNames,'rewardValve')); + type = iff(isprop(valve_controller, 'WaterType'), valve_controller.WaterType, 'Water'); + if isfield(outputs, 'rewardValues') + amount = sum(obj.Data.outputs.rewardValues)*0.001; + else + amount = 0; + end + obj.AlyxInstance.postWater(subject, amount, now, type, url); + catch ex + warning(ex.identifier, 'Failed to post water to Alyx: %s', ex.message); + end end end end diff --git a/+hw/RewardValveControl.m b/+hw/RewardValveControl.m index aa94b999..3713ed6a 100644 --- a/+hw/RewardValveControl.m +++ b/+hw/RewardValveControl.m @@ -14,6 +14,9 @@ % 'volumeMicroLitres' indicating the duration the valve was open, and the % measured volume (in ul) for that delivery. These points are interpolated % to work out how long to open the valve for arbitrary volumes. + WaterType = 'Water' + % The type of water dispenced by the rig. This is used to populate the + % water_type field in Alyx sessions. end methods From 34a4b7a13d0ad38bd341281a168a274655cf853a Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 20 Nov 2018 14:39:43 +0000 Subject: [PATCH 212/393] field name change for trials numbers --- +exp/SignalsExp.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/+exp/SignalsExp.m b/+exp/SignalsExp.m index b8f1b74c..23340cc9 100644 --- a/+exp/SignalsExp.m +++ b/+exp/SignalsExp.m @@ -906,8 +906,8 @@ function saveData(obj) end % Update Alyx session with end time, trial counts and water tye sessionData = struct('end_time', obj.AlyxInstance.datestr(now), 'subject', subject); - if ~isempty(numTrials); sessionData.numberOfTrials = numTrials; end - if ~isempty(numCorrect); sessionData.numberOfCorrectTrials = numCorrect; end + if ~isempty(numTrials); sessionData.n_trials = numTrials; end + if ~isempty(numCorrect); sessionData.n_correct_trials = numCorrect; end obj.AlyxInstance.postData(url, sessionData, 'put'); else % Retrieve session from endpoint From 86b4ce0cd928750d8f42b325bb5a81d621781997 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 20 Nov 2018 15:54:41 +0000 Subject: [PATCH 213/393] Bug fix for post water --- +exp/SignalsExp.m | 2 +- alyx-matlab | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/+exp/SignalsExp.m b/+exp/SignalsExp.m index 23340cc9..7e705c0d 100644 --- a/+exp/SignalsExp.m +++ b/+exp/SignalsExp.m @@ -921,7 +921,7 @@ function saveData(obj) try valve_controller = obj.DaqController.SignalGenerators(strcmp(obj.DaqController.ChannelNames,'rewardValve')); type = iff(isprop(valve_controller, 'WaterType'), valve_controller.WaterType, 'Water'); - if isfield(outputs, 'rewardValues') + if isfield(obj.Data.outputs, 'rewardValues') amount = sum(obj.Data.outputs.rewardValues)*0.001; else amount = 0; diff --git a/alyx-matlab b/alyx-matlab index 5420b606..9f75b53a 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit 5420b6065ccca9b438827fc1419fe30566e2ed7b +Subproject commit 9f75b53a7fbbce7f80caf674157c592e19d91db2 From 8b455028e6ff8421a0086f998d9b4e7bf709f588 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 20 Nov 2018 18:09:57 +0000 Subject: [PATCH 214/393] Ugly but functional drop-down for water type --- +eui/AlyxPanel.m | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index 76cf0377..792ff5dc 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -45,7 +45,7 @@ LoginButton % Button to log in to Alyx WeightButton % Button to submit weight to Alyx WaterEntry % Text box for entering the amout of water to give - IsHydrogel % UI checkbox indicating whether to water to be given is in gel form + WaterType % UI checkbox indicating whether to water to be given is in gel form WaterRequiredText % Handle to text UI element displaying the water required WaterRemainingText % Handle to text UI element displaying the water remaining LoginTimer % Timer to keep track of how long the user has been logged in, when this expires the user is automatically logged out @@ -150,11 +150,11 @@ 'Enable', 'off',... 'Callback', @(~,~)obj.giveFutureWater); % Check box to indicate whether water was gel or liquid - obj.IsHydrogel = uicontrol('Parent', waterbox,... - 'Style', 'checkbox', ... - 'String', 'Hydrogel?', ... + obj.WaterType = uicontrol('Parent', waterbox,... + 'Style', 'popupmenu', ... + 'String', {'Water'}, ... 'HorizontalAlignment', 'right',... - 'Value', false, ... + 'Value', 1, ... 'Enable', 'off'); % Input for submitting amount of water obj.WaterEntry = uicontrol('Parent', waterbox,... @@ -241,6 +241,10 @@ function login(obj) obj.NewExpSubject.Option = newSubs; obj.SubjectList = newSubs; + % update water type list + wt = obj.AlyxInstance.getData('water-type'); + obj.WaterType.String = {wt.name}; + notify(obj, 'Connected'); % Notify listeners of login obj.log('Logged into Alyx successfully as %s', obj.AlyxInstance.User); @@ -287,7 +291,7 @@ function giveWater(obj) % state of the 'is hydrogel' check box thisDate = now; amount = str2double(get(obj.WaterEntry, 'String')); - type = iff(get(obj.IsHydrogel, 'Value')==1, 'Hydrogel', 'Water'); + type = obj.WaterType.String{obj.WaterType.Value}; if obj.AlyxInstance.IsLoggedIn && amount~=0 && ~isnan(amount) wa = obj.AlyxInstance.postWater(obj.Subject, amount, thisDate, type); if ~isempty(wa) % returned us a created water administration object successfully From 3994da9f865b8ceebc07d53adaf69b23cfa7fba6 Mon Sep 17 00:00:00 2001 From: Olivier Winter <olivier.winter@research.fchampalimaud.org> Date: Mon, 19 Nov 2018 18:01:18 +0000 Subject: [PATCH 215/393] eui.AlyxPanel: water restriction management endpoint refactoring --- +eui/AlyxPanel.m | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index 792ff5dc..ada340ac 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -371,12 +371,12 @@ function dispWaterReq(obj, src, ~) else record = struct(); end - weight = getOr(record, 'weight_measured', NaN); % Get today's measured weight - water = getOr(record, 'water_given', 0); % Get total water given - gel = getOr(record, 'hydrogel_given', 0); % Get total gel given - weight_expected = getOr(record, 'weight_expected', NaN); + weight = getOr(record, 'weight', NaN); % Get today's measured weight + water = getOr(record, 'given_water_liquid', 0); % Get total water given + gel = getOr(record, 'given_water_hydrogel', 0); % Get total gel given + expected_weight = getOr(record, 'expected_weight', NaN); % Set colour based on weight percentage - weight_pct = (weight-wr.implant_weight)/(weight_expected-wr.implant_weight); + weight_pct = (weight-wr.implant_weight)/(expected_weight-wr.implant_weight); if weight_pct < 0.8 % Mouse below 80% original weight colour = [0.91, 0.41, 0.17]; % Orange weight_pct = '< 80%'; @@ -547,7 +547,7 @@ function viewSubjectHistory(obj, ax) obj.log('No weight data found for subject %s', obj.Subject); return end - expected = [records.weight_expected]; + expected = [records.expected_weight]; expected(expected==0) = nan; dates = cellfun(@(x)datenum(x), {records.date}); @@ -561,7 +561,7 @@ function viewSubjectHistory(obj, ax) ax = axes('Parent', plotBox); end - plot(ax, dates, [records.weight_measured], '.-'); + plot(ax, dates, [records.weight], '.-'); hold(ax, 'on'); plot(ax, dates, ((expected-iw)*0.7)+iw, 'r', 'LineWidth', 2.0); plot(ax, dates, ((expected-iw)*0.8)+iw, 'LineWidth', 2.0, 'Color', [244, 191, 66]/255); @@ -578,7 +578,7 @@ function viewSubjectHistory(obj, ax) if nargin==1 ax = axes('Parent', plotBox); - plot(ax, dates, ([records.weight_measured]-iw)./(expected-iw), '.-'); + plot(ax, dates, ([records.weight]-iw)./(expected-iw), '.-'); hold(ax, 'on'); plot(ax, dates, 0.7*ones(size(dates)), 'r', 'LineWidth', 2.0); plot(ax, dates, 0.8*ones(size(dates)), 'LineWidth', 2.0, 'Color', [244, 191, 66]/255); @@ -588,11 +588,11 @@ function viewSubjectHistory(obj, ax) ylabel(ax, 'weight as pct (%)'); axWater = axes('Parent',plotBox); - plot(axWater, dates, obj.round([records.water_given]+[records.hydrogel_given], 'up'), '.-'); + plot(axWater, dates, obj.round([records.given_water_liquid]+[records.given_water_hydrogel], 'up'), '.-'); hold(axWater, 'on'); - plot(axWater, dates, obj.round([records.hydrogel_given], 'down'), '.-'); - plot(axWater, dates, obj.round([records.water_given], 'down'), '.-'); - plot(axWater, dates, obj.round([records.water_expected], 'up'), 'r', 'LineWidth', 2.0); + plot(axWater, dates, obj.round([records.given_water_hydrogel], 'down'), '.-'); + plot(axWater, dates, obj.round([records.given_water_liquid], 'down'), '.-'); + plot(axWater, dates, obj.round([records.expected_water], 'up'), 'r', 'LineWidth', 2.0); box(axWater, 'off'); xlim(axWater, [min(dates) max(dates)]); set(axWater, 'XTickLabel', arrayfun(@(x)datestr(x, 'dd-mmm'), get(axWater, 'XTick'), 'uni', false)) @@ -603,22 +603,22 @@ function viewSubjectHistory(obj, ax) histTable = uitable('Parent', histbox,... 'FontName', 'Consolas',... 'RowName', []); - weightsByDate = num2cell([records.weight_measured]); + weightsByDate = num2cell([records.weight]); weightsByDate = cellfun(@(x)sprintf('%.1f', x), weightsByDate, 'uni', false); - weightsByDate(isnan([records.weight_measured])) = {[]}; - weightPctByDate = num2cell(([records.weight_measured]-iw)./(expected-iw)); + weightsByDate(isnan([records.weight])) = {[]}; + weightPctByDate = num2cell(([records.weight]-iw)./(expected-iw)); weightPctByDate = cellfun(@(x)sprintf('%.1f', x*100), weightPctByDate, 'uni', false); - weightPctByDate(isnan([records.weight_measured])) = {[]}; + weightPctByDate(isnan([records.weight])) = {[]}; dat = horzcat(... arrayfun(@(x)datestr(x), dates', 'uni', false), ... weightsByDate', ... - arrayfun(@(x)sprintf('%.1f', 0.8*(x-iw)+iw), [records.weight_expected]', 'uni', false), ... + arrayfun(@(x)sprintf('%.1f', 0.8*(x-iw)+iw), [records.expected_weight]', 'uni', false), ... weightPctByDate'); waterDat = (... - num2cell(horzcat([records.water_given]', [records.hydrogel_given]', ... - [records.water_given]'+[records.hydrogel_given]', [records.water_expected]',... - [records.water_given]'+[records.hydrogel_given]'-[records.water_expected]'))); + num2cell(horzcat([records.given_water_liquid]', [records.given_water_hydrogel]', ... + [records.given_water_liquid]'+[records.given_water_hydrogel]', [records.expected_water]',... + [records.given_water_liquid]'+[records.given_water_hydrogel]'-[records.expected_water]'))); waterDat = cellfun(@(x)sprintf('%.2f', x), waterDat, 'uni', false); dat = horzcat(dat, waterDat); @@ -714,4 +714,4 @@ function log(obj, varargin) end end end -end \ No newline at end of file +end From 770ef43aa7c1cb75ad2c4b50e5ae47524ca79019 Mon Sep 17 00:00:00 2001 From: Olivier Winter <olivier.winter@research.fchampalimaud.org> Date: Mon, 19 Nov 2018 16:59:14 +0000 Subject: [PATCH 216/393] Alyx changes in subject endpoint --- +dat/parseAlyxInstance.m | 6 +++--- +eui/AlyxPanel.m | 8 ++++---- +eui/MControl.m | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/+dat/parseAlyxInstance.m b/+dat/parseAlyxInstance.m index 2eb09711..9db5cf4a 100644 --- a/+dat/parseAlyxInstance.m +++ b/+dat/parseAlyxInstance.m @@ -18,9 +18,9 @@ ai = varargin{2}; % extract AlyxInstance struct if isstruct(ai) % if there is an AlyxInstance ai = orderfields(ai); % alphabetize fields - % remove water requirement remaining field - if isfield(ai, 'water_requirement_remaining') - ai = rmfield(ai, 'water_requirement_remaining'); + % remove water remaining_water field + if isfield(ai, 'remaining_water') + ai = rmfield(ai, 'remaining_water'); end fname = fieldnames(ai); % get fieldnames emp = structfun(@isempty, ai); % find empty fields diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index ada340ac..9f2e83dc 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -388,12 +388,12 @@ function dispWaterReq(obj, src, ~) weight_pct = '> 80%'; end % Round up water remaining to the near 0.01 - remainder = obj.round(s(idx).water_requirement_remaining, 'up'); + remainder = obj.round(s(idx).remaining_water, 'up'); % Set text set(obj.WaterRequiredText, 'ForegroundColor', colour, 'String', ... sprintf(['Subject %s requires %.2f of %.2f today\n\t '... 'Weight today: %.2f (%s) Water today: %.2f'], obj.Subject, ... - remainder, obj.round(s(idx).water_requirement_total, 'up'), weight, ... + remainder, obj.round(s(idx).expected_water, 'up'), weight, ... weight_pct, obj.round(sum([water gel]), 'down'))); % Set WaterRemaining attribute for changeWaterText callback obj.WaterRemaining = remainder; @@ -649,11 +649,11 @@ function viewAllSubjects(obj) colorgen = @(colorNum,text) ['<html><body bgcolor=#',htmlColor(colorNum),'>',text,'</body></html>']; wrdat = cellfun(@(x)colorgen(1-double(x>0)*[0 0.3 0.3],... - sprintf('%.2f',obj.round(x, 'up'))), {wr.water_requirement_remaining}, 'uni', false); + sprintf('%.2f',obj.round(x, 'up'))), {wr.remaining_water}, 'uni', false); set(wrTable, 'ColumnName', {'Name', 'Water Required', 'Remaining Requirement'}, ... 'Data', horzcat({wr.nickname}', ... - cellfun(@(x)sprintf('%.2f',obj.round(x, 'up')),{wr.water_requirement_total}', 'uni', false), ... + cellfun(@(x)sprintf('%.2f',obj.round(x, 'up')),{wr.expected_water}', 'uni', false), ... wrdat'), ... 'ColumnEditable', false(1,3)); end diff --git a/+eui/MControl.m b/+eui/MControl.m index 9e5a30ca..3ca11966 100644 --- a/+eui/MControl.m +++ b/+eui/MControl.m @@ -346,8 +346,8 @@ function rigExpStopped(obj, rig, evt) % Announce that the experiment has stopped subject = dat.parseExpRef(evt.Ref); sd = rig.AlyxInstance.getData(sprintf('subjects/%s', subject)); obj.log('Water requirement remaining for %s: %.2f (%.2f already given)', ... - subject, sd.water_requirement_remaining, ... - sd.water_requirement_total-sd.water_requirement_remaining); + subject, sd.remaining_water, ... + sd.expected_water-sd.remaining_water); catch subject = dat.parseExpRef(evt.Ref); obj.log('Warning: unable to query Alyx about %s''s water requirements', subject); From 3cb585195df551b98902a455c494b838994c3e20 Mon Sep 17 00:00:00 2001 From: jaib1 <jaib1@mit.edu> Date: Mon, 26 Nov 2018 15:37:40 +0000 Subject: [PATCH 217/393] Initial DaqController changes for clock output + tidying other files --- +hw/DaqController.m | 77 ++++++++++++++++++++++++++++++++------------- +hw/DaqSingleScan.m | 4 +-- +hw/Timeline.m | 13 ++++---- 3 files changed, 64 insertions(+), 30 deletions(-) diff --git a/+hw/DaqController.m b/+hw/DaqController.m index a9835d40..b2fa0891 100644 --- a/+hw/DaqController.m +++ b/+hw/DaqController.m @@ -53,6 +53,7 @@ properties (Transient) DaqSession % should be a DAQ session containing at least one analogue output channel DigitalDaqSession % a DAQ session containing only digital output channels + ClockDaqSession % a DAQ session for implementing output to a clock channel end properties (Dependent) @@ -67,32 +68,41 @@ methods function createDaqChannels(obj) - if isempty(obj.DaqSession) + if isempty(obj.DaqSession)&&any(obj.AnalogueChannelsIdx) obj.DaqSession = daq.createSession('ni'); obj.DaqSession.Rate = obj.SampleRate; end if isempty(obj.DigitalDaqSession)&&any(~obj.AnalogueChannelsIdx) obj.DigitalDaqSession = daq.createSession('ni'); end + if isempty(obj.ClockDaqSession)&&any(strncmp('ctr',(obj.DaqChannelIds),3)) + obj.ClockDaqSession = daq.createSession('ni'); + end n = obj.NumChannels; if n > 0 - for ii = 1:n + for i = 1:n if iscell(obj.DaqIds) - daqid = obj.DaqIds{ii}; + daqid = obj.DaqIds{i}; else daqid = obj.DaqIds; end - if obj.AnalogueChannelsIdx(ii) % is channal analogue? + % is channel analogue? + if strncmp('ao',(obj.DaqChannelIds{i}),2) obj.DaqSession.addAnalogOutputChannel(... - daqid, obj.DaqChannelIds{ii}, 'Voltage'); + daqid, obj.DaqChannelIds{i}, 'Voltage'); + % is channel clock output? + elseif strncmp('ctr',(obj.DaqChannelIds{i}),3) + obj.ClockDaqSession.addCounterOutputChannel(... + daqid, obj.DaqChannelIds{i}, 'PulseGeneration'); else % assume digital, always output only obj.DigitalDaqSession.addDigitalChannel(... - daqid, obj.DaqChannelIds{ii}, 'OutputOnly'); + daqid, obj.DaqChannelIds{i}, 'OutputOnly'); end end v = [obj.SignalGenerators.DefaultValue]; obj.DaqSession.outputSingleScan(v(obj.AnalogueChannelsIdx)); - if any(~obj.AnalogueChannelsIdx) + if any(~obj.AnalogueChannelsIdx) && ... + ~( any(strncmp('ctr',(obj.DaqChannelIds),3)) ) obj.DigitalDaqSession.outputSingleScan(v(~obj.AnalogueChannelsIdx)); end obj.CurrValue = v; @@ -105,12 +115,14 @@ function command(obj, varargin) % Sends command signals to each channel % % command(channels, values) - % sends command signals to each channel carrying each value. - % 'channels' is a cell array of strings with each channel name, and - % value is + % sends command signal to a channel with the corresponding value + % (i.e. there is a channel-value pair for each command signal) + % 'channels' is a cell array of strings with each channel name, and + % 'value' is a cell array of values? % % command(values) - % sends command signals to all channels carrying each value + % for length of values, sends command signals to the corresponding + % ordered channels % % [CHANNEL,INDEX] = addAnalogInputChannel(...) % addAnalogInputChannel optionally returns CHANNEL, which is an @@ -143,13 +155,13 @@ function command(obj, varargin) gen = obj.SignalGenerators(1:n); rate = obj.DaqSession.Rate; waveforms = cell(1, n); - for ii = 1:n + for i = 1:n if iscell(values) - v = values{ii}; + v = values{i}; else - v = values(:,ii); + v = values(:,i); end - waveforms{ii} = gen(ii).waveform(rate, v); + waveforms{i} = gen(i).waveform(rate, v); end if obj.DaqSession.IsRunning % if a daq operation is in progress, stop it, and set its output @@ -158,6 +170,7 @@ function command(obj, varargin) end channelNames = obj.ChannelNames(1:n); analogueChannelsIdx = obj.AnalogueChannelsIdx(1:n); + % for all analogue channel outputs if any(analogueChannelsIdx)&&any(any(values(:,analogueChannelsIdx)~=0)) queue(obj, channelNames(analogueChannelsIdx), waveforms(analogueChannelsIdx)); if foreground @@ -167,18 +180,38 @@ function command(obj, varargin) end readyWait(obj); obj.DaqSession.release; - elseif any(~analogueChannelsIdx) - waveforms = waveforms(~analogueChannelsIdx); - for n = 1:length(waveforms) - digitalValues = waveforms{n}; - for m = 1:length(digitalValues) - obj.DigitalDaqSession.outputSingleScan(digitalValues(m)); - end +% elseif any(~analogueChannelsIdx) %why is this an elseif? +% waveforms = waveforms(~analogueChannelsIdx); + else % for all digital or clock outputs + maxLnWaveform = max(cellfun(@length, waveforms)); + % pad shorter waveforms + for i = 1:length(waveforms) + waveforms{i}(end:maxLnWaveform) = waveforms{i}(end); + end + waveformsMtx = vec2mat(cell2mat(waveforms), maxLnWaveform); + if iscolumn(waveformsMtx), waveformsMtx = waveformsMtx'; end + % output first rows of waveformsMtx (values for each channel) (to + % account for waveforms of different lengths) + for n = 1:size(waveformsMtx,1) + % for clock output channels with a valid value to output + if strncmp('ctr',(obj.DaqChannelIds{n}),3) && waveformsMtx(n,1)>0 + obj.ClockDaqSession.dt = length(waveformsMtx(n,1)) / obj.SampleRate; + obj.ClockDaqSession.F = 1/obj.ClockDaqSession.dt; + obj.ClockDaqSession.Duty = 1; + startBackground(obj.ClockDaqSession); + else %for digital output channels + obj.DigitalDaqSession.outputSingleScan(waveformsMtx(n,:)); end + end end end end + function clearSessions(obj) + obj.DaqSession = []; + obj.DigitalDaqSession = []; + end + function v = get.NumChannels(obj) v = numel(obj.DaqChannelIds); end diff --git a/+hw/DaqSingleScan.m b/+hw/DaqSingleScan.m index 2bb53282..b37fd102 100644 --- a/+hw/DaqSingleScan.m +++ b/+hw/DaqSingleScan.m @@ -17,11 +17,11 @@ obj.Scale = scale; end - function samples = waveform(obj, v) + function samples = waveform(obj, varargin) % just take the first value (if multiple were provided) and output % it, scaled, as a single number. This will result in the analog % output channel switching to that value and staying there. - samples = v(1)*obj.Scale; + samples = varargin{end}*obj.Scale; end end diff --git a/+hw/Timeline.m b/+hw/Timeline.m index 2e8a3b16..020a96c2 100644 --- a/+hw/Timeline.m +++ b/+hw/Timeline.m @@ -1,5 +1,5 @@ classdef Timeline < handle -% HW.TIMELINE Returns an object that generate and aquires clocking pulses +% HW.TIMELINE Returns an object that generates and aquires clocking pulses % Timeline (tl) manages the aquisition and generation of experimental % timing data using an NI data aquisition device. The main timing signal % is called 'chrono' and consists of a digital squarewave that flips each @@ -45,7 +45,7 @@ % %Add the rotary encoder % timeline.addInput('rotaryEncoder', 'ctr0', 'Position'); % %For a lick detector -% timeline.addInput('lickDetector', 'ctr2', 'EdgeCount'); +% timeline.addInput('lickDetector', 'ctr1', 'EdgeCount'); % %We want use camera frame acquisition trigger by default % timeline.UseOutputs{end+1} = 'clock'; % %Save your hardware.mat file @@ -132,7 +132,7 @@ function start(obj, expRef, ai) % START Starts timeline data acquisition % START(obj, ref, AlyxInstance) starts all DAQ sessions and adds - % the relevent output and input channels. + % the relevant output and input channels. % % See Also HW.TLOUTPUT/START @@ -192,8 +192,8 @@ function start(obj, expRef, ai) function record(obj, name, event, t) % Records an event in Timeline % TL.RECORD(name, event, [time]) records an event in the Timeline - % struct in fields prefixed with 'name', with data in 'event'. Optionally - % specify 'time', otherwise the time of call will be used (relative to + % object in fields prefixed with 'name', with data in 'event'. Optionally + % specify time 't', otherwise the time of call will be used (relative to % Timeline acquisition). if nargin < 4; t = time(obj); end % default to time now (using Timeline clock) initLength = 100; % default initial length of event data arrays @@ -604,7 +604,8 @@ function livePlot(obj, data) % TL.LIVEPLOT(source, event) plots the data aquired by the % DAQ while the PlotLive property is true. if isempty(obj.Axes) - f = figure('Units', 'Normalized', 'Position', [0 0 1 1]); % create a figure for plotting aquired data + %f = figure('Units', 'Normalized', 'Position', [0 0 1 1]); % create a figure for plotting aquired data + f = figure('Units', 'Normalized'); obj.Axes = gca; % store a handle to the axes if isprop(obj, 'FigurePosition') && ~isempty(obj.FigurePosition) set(f, 'Position', obj.FigurePosition); % set the figure position From a94ab94c79ca8bca2145a39c808b9c7d4d4662fd Mon Sep 17 00:00:00 2001 From: jaib1 <jaib1@mit.edu> Date: Tue, 27 Nov 2018 14:24:23 +0000 Subject: [PATCH 218/393] working RewardAsClock in DaqController --- +hw/DaqController.m | 103 +++++++++++++++++++++++++++++--------------- 1 file changed, 68 insertions(+), 35 deletions(-) diff --git a/+hw/DaqController.m b/+hw/DaqController.m index b2fa0891..524047e4 100644 --- a/+hw/DaqController.m +++ b/+hw/DaqController.m @@ -15,7 +15,7 @@ % %Define the channel ID to output on % daqController.DaqChannelIds = {'ai0'}; % %As it is an analogue output, set the AnalogueChannelsIdx to true - % daqController.AnalogueChannelIdx(1) = true; + % daqController.AnalogueChannelsIdx(1) = true; % %Add a signal generator that will return the correct samples for % %delivering a reward of a specified volume % daqController.SignalGenerators(1) = hw.RewardValveControl; @@ -37,8 +37,11 @@ % See also HW.CONTROLSIGNALGENERATOR, HW.DAQROTARYENCODER % 2013 CB created % 2017-07 MW added digital output support + % 2018-11 JB added output reward as clock channel + % 2018-11 JB added simultaneous analog/digital and digital/digital output properties + ChannelNames = {} % name to refer to each channel %Signal generator for each channel. Each should be an object of class %hw.ControlSignalGenerator, for generating command waveforms. @@ -48,31 +51,39 @@ SampleRate = 1000 % output sample rate ("scans/sec") of the daq device % 1000 is also the default of the ni daq devices themselves, so if % you don't change this, it doesn't actually do anything. + end properties (Transient) + DaqSession % should be a DAQ session containing at least one analogue output channel DigitalDaqSession % a DAQ session containing only digital output channels ClockDaqSession % a DAQ session for implementing output to a clock channel + end properties (Dependent) + Value %The current voltage on each DAQ channel NumChannels %Number of channels controlled AnalogueChannelsIdx %Logical array of analogue channel IDs + end properties (Access = private, Transient) + CurrValue + end methods + function createDaqChannels(obj) - if isempty(obj.DaqSession)&&any(obj.AnalogueChannelsIdx) + if isempty(obj.DaqSession)&&any(strncmp('ao',(obj.DaqChannelIds),2)) obj.DaqSession = daq.createSession('ni'); obj.DaqSession.Rate = obj.SampleRate; end - if isempty(obj.DigitalDaqSession)&&any(~obj.AnalogueChannelsIdx) + if isempty(obj.DigitalDaqSession)&&any(strncmp('port',(obj.DaqChannelIds),4)) obj.DigitalDaqSession = daq.createSession('ni'); end if isempty(obj.ClockDaqSession)&&any(strncmp('ctr',(obj.DaqChannelIds),3)) @@ -94,17 +105,21 @@ function createDaqChannels(obj) elseif strncmp('ctr',(obj.DaqChannelIds{i}),3) obj.ClockDaqSession.addCounterOutputChannel(... daqid, obj.DaqChannelIds{i}, 'PulseGeneration'); - else % assume digital, always output only + else % assume digital, always 'OutputOnly' obj.DigitalDaqSession.addDigitalChannel(... daqid, obj.DaqChannelIds{i}, 'OutputOnly'); end end + + % what are these lines doing? why outputSingleScan? why a/d separate? + v = [obj.SignalGenerators.DefaultValue]; - obj.DaqSession.outputSingleScan(v(obj.AnalogueChannelsIdx)); - if any(~obj.AnalogueChannelsIdx) && ... - ~( any(strncmp('ctr',(obj.DaqChannelIds),3)) ) - obj.DigitalDaqSession.outputSingleScan(v(~obj.AnalogueChannelsIdx)); - end +% obj.DaqSession.outputSingleScan(v(obj.AnalogueChannelsIdx)); +% % digital (non-clock) channel only +% nonClockDigis = strncmp('port',(obj.DaqChannelIds),4); +% if any(nonClockDigis) +% obj.DigitalDaqSession.outputSingleScan(v(nonClockDigis)); +% end obj.CurrValue = v; else obj.CurrValue = []; @@ -171,7 +186,7 @@ function command(obj, varargin) channelNames = obj.ChannelNames(1:n); analogueChannelsIdx = obj.AnalogueChannelsIdx(1:n); % for all analogue channel outputs - if any(analogueChannelsIdx)&&any(any(values(:,analogueChannelsIdx)~=0)) + if any(analogueChannelsIdx)&&any(values(:,analogueChannelsIdx)~=0) queue(obj, channelNames(analogueChannelsIdx), waveforms(analogueChannelsIdx)); if foreground startForeground(obj.DaqSession); @@ -180,27 +195,31 @@ function command(obj, varargin) end readyWait(obj); obj.DaqSession.release; -% elseif any(~analogueChannelsIdx) %why is this an elseif? -% waveforms = waveforms(~analogueChannelsIdx); - else % for all digital or clock outputs + end + % for all digital or clock outputs (why does this have to be an else?) + if any(~analogueChannelsIdx)&&any(values(:,~analogueChannelsIdx)~=0) maxLnWaveform = max(cellfun(@length, waveforms)); % pad shorter waveforms for i = 1:length(waveforms) waveforms{i}(end:maxLnWaveform) = waveforms{i}(end); end - waveformsMtx = vec2mat(cell2mat(waveforms), maxLnWaveform); - if iscolumn(waveformsMtx), waveformsMtx = waveformsMtx'; end - % output first rows of waveformsMtx (values for each channel) (to - % account for waveforms of different lengths) - for n = 1:size(waveformsMtx,1) - % for clock output channels with a valid value to output - if strncmp('ctr',(obj.DaqChannelIds{n}),3) && waveformsMtx(n,1)>0 - obj.ClockDaqSession.dt = length(waveformsMtx(n,1)) / obj.SampleRate; - obj.ClockDaqSession.F = 1/obj.ClockDaqSession.dt; - obj.ClockDaqSession.Duty = 1; - startBackground(obj.ClockDaqSession); - else %for digital output channels - obj.DigitalDaqSession.outputSingleScan(waveformsMtx(n,:)); + waveformsMtx = cell2mat(waveforms); + %if iscolumn(waveformsMtx), waveformsMtx = waveformsMtx'; end + % output columns of waveformsMtx (values for each channel) + for n = 1:size(waveformsMtx,2) + %if we have some value to output + if any(waveformsMtx(:,n)) + % for clock output channels with a valid value to output + if strncmp('ctr',(obj.DaqChannelIds{n}),3) + obj.ClockDaqSession.DurationInSeconds = length(waveformsMtx) / obj.SampleRate; + %Duty Cycle must be b/w 0-1, so set to 'n' and scale frequency by 1/n + obj.ClockDaqSession.Channels.DutyCycle = 0.99; + obj.ClockDaqSession.Channels.Frequency = 1/obj.ClockDaqSession.DurationInSeconds/0.99; + startBackground(obj.ClockDaqSession); + %for digital output channels + elseif strncmp('port',(obj.DaqChannelIds{n}),4) + obj.DigitalDaqSession.outputSingleScan(waveformsMtx(:,n)); + end end end end @@ -210,6 +229,7 @@ function command(obj, varargin) function clearSessions(obj) obj.DaqSession = []; obj.DigitalDaqSession = []; + obj.ClockDaqSession = []; end function v = get.NumChannels(obj) @@ -226,7 +246,9 @@ function clearSessions(obj) function set.Value(obj, v) readyWait(obj); - obj.DaqSession.outputSingleScan(v(obj.AnalogueChannelsIdx)); + if any (obj.AnalogueChannelsIdx) + obj.DaqSession.outputSingleScan(v(obj.AnalogueChannelsIdx)); + end if any(~obj.AnalogueChannelsIdx) obj.DigitalDaqSession.outputSingleScan(v(~obj.AnalogueChannelsIdx)); end @@ -234,20 +256,26 @@ function clearSessions(obj) end function reset(obj) - stop(obj.DaqSession); + if ~isempty(obj.DaqSession) + stop(obj.DaqSession); + end if ~isempty(obj.DigitalDaqSession) stop(obj.DigitalDaqSession); end v = [obj.SignalGenerators.DefaultValue]; - outputSingleScan(obj.DaqSession, v(obj.AnalogueChannelsIdx)); + if any(obj.AnalogueChannelsIdx) + outputSingleScan(obj.DaqSession, v(obj.AnalogueChannelsIdx)); + end if any(~obj.AnalogueChannelsIdx) outputSingleScan(obj.DigitalDaqSession, v(~obj.AnalogueChannelsIdx)); end obj.CurrValue = v; end + end methods (Access = protected) + function queue(obj, names, waveforms) names = ensureCell(names); waveforms = ensureCell(waveforms); @@ -256,12 +284,13 @@ function queue(obj, names, waveforms) len = cellfun(@numel, waveforms); defaultValues = [obj.SignalGenerators.DefaultValue]; samples = repmat(defaultValues(obj.AnalogueChannelsIdx), max(len), 1); - for ii = 1:numel(waveforms) - cidx = strcmp(names{ii}, obj.ChannelNames); - assert(sum(cidx) == 1, 'Channel name mismatch'); - samples(1:len(ii),cidx) = waveforms{ii}; + for i = 1:numel(waveforms) + % cidx = strcmp(names{i}, obj.ChannelNames); + % assert(sum(cidx) == 1, 'Channel name mismatch'); + % samples(1:len(i),cidx) = waveforms{i}; + samples(1:len(i),i) = waveforms{i}; end - readyWait(obj); + %readyWait(obj); % plot(samples,'-x'), xlim([-1 300]) obj.DaqSession.queueOutputData(samples); % samplelen = size(samples,1)/1000 @@ -269,13 +298,17 @@ function queue(obj, names, waveforms) end function readyWait(obj) - if obj.DaqSession.IsRunning + if ~isempty(obj.DaqSession)&&obj.DaqSession.IsRunning obj.DaqSession.wait(); end if ~isempty(obj.DigitalDaqSession)&&obj.DigitalDaqSession.IsRunning obj.DigitalDaqSession.wait(); end + if ~isempty(obj.ClockDaqSession)&&obj.ClockDaqSession.IsRunning + obj.ClockDaqSession.wait(); + end end + end end From 7526eee6f17e90dab70f34b7025c4762bcd07c9a Mon Sep 17 00:00:00 2001 From: jaib1 <jaib1@mit.edu> Date: Tue, 27 Nov 2018 15:51:32 +0000 Subject: [PATCH 219/393] manual changes to resolve remote conflicts --- .gitmodules | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.gitmodules b/.gitmodules index 32b91d63..77c1349b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,16 +1,16 @@ [submodule "alyx-matlab"] path = alyx-matlab - url = https://github.com/jaib1/alyx-matlab/ + url = https://github.com/cortex-lab/alyx-matlab/ branch = dev [submodule "signals"] path = signals - url = https://github.com/jaib1/signals + url = https://github.com/cortex-lab/signals branch = dev [submodule "npy-matlab"] path = npy-matlab - url = https://github.com/jaib1/npy-matlab - branch = dev + url = https://github.com/cortex-lab/npy-matlab + branch = master [submodule "wheelAnalysis"] path = wheelAnalysis - url = https://github.com/jaib1/wheelAnalysis - branch = dev + url = https://github.com/cortex-lab/wheelAnalysis + branch = master From 53273c6d25bcab0a84b2b3873cfbefa642ece759 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 27 Nov 2018 19:01:53 +0000 Subject: [PATCH 220/393] Session water posted by rig instead of mc. Allows rig specific water type. --- +eui/ExpPanel.m | 58 ++++++++++++++++----------------- +exp/Experiment.m | 27 ---------------- cortexlab/+exp/ChoiceWorld.m | 63 +++++++++++++++++++++++++++++++++++- cortexlab/+git/update.m | 12 ------- 4 files changed, 91 insertions(+), 69 deletions(-) diff --git a/+eui/ExpPanel.m b/+eui/ExpPanel.m index d9fd958c..6827dbdc 100644 --- a/+eui/ExpPanel.m +++ b/+eui/ExpPanel.m @@ -222,35 +222,35 @@ function expStopped(obj, rig, ~) obj.Listeners = []; obj.Root.TitleColor = [1 0.3 0.22]; % red title area %post water to Alyx - ai = rig.AlyxInstance; - subject = obj.SubjectRef; - if ~isempty(ai)&&~strcmp(subject,'default') - switch class(obj) - case 'eui.ChoiceExpPanel' - if ~isfield(obj.Block.trial,'feedbackType'); return; end % No completed trials - if any(strcmp(obj.Parameters.TrialSpecificNames,'rewardVolume')) % Reward is trial specific - condition = [obj.Block.trial.condition]; - reward = [condition.rewardVolume]; - amount = sum(reward(:,[obj.Block.trial.feedbackType]==1), 2); - else % Global reward x positive feedback - amount = obj.Parameters.Struct.rewardVolume(1)*... - sum([obj.Block.trial.feedbackType]==1); - end - if numel(amount)>1; amount = amount(1); end % Take first element (second being laser) - otherwise - % Done in exp.SignalsExp/saveData - %infoFields = {obj.InfoFields.String}; - %inc = cellfun(@(x) any(strfind(x(:)','�l')), {obj.InfoFields.String}); % Find event values ending with 'ul'. - %reward = cell2mat(cellfun(@str2num,strsplit(infoFields{find(inc,1)},'�l'),'UniformOutput',0)); - %amount = iff(isempty(reward),0,@()reward); - end - if ~any(amount); return; end % Return if no water was given - try - ai.postWater(subject, amount*0.001, now, 'Water', ai.SessionURL); - catch - warning('Failed to post the %.2fml %s recieved during the experiment to Alyx', amount*0.001, subject); - end - end +% ai = rig.AlyxInstance; +% subject = obj.SubjectRef; +% if ~isempty(ai)&&~strcmp(subject,'default') +% switch class(obj) +% case 'eui.ChoiceExpPanel' +% if ~isfield(obj.Block.trial,'feedbackType'); return; end % No completed trials +% if any(strcmp(obj.Parameters.TrialSpecificNames,'rewardVolume')) % Reward is trial specific +% condition = [obj.Block.trial.condition]; +% reward = [condition.rewardVolume]; +% amount = sum(reward(:,[obj.Block.trial.feedbackType]==1), 2); +% else % Global reward x positive feedback +% amount = obj.Parameters.Struct.rewardVolume(1)*... +% sum([obj.Block.trial.feedbackType]==1); +% end +% if numel(amount)>1; amount = amount(1); end % Take first element (second being laser) +% otherwise +% % Done in exp.SignalsExp/saveData +% %infoFields = {obj.InfoFields.String}; +% %inc = cellfun(@(x) any(strfind(x(:)','�l')), {obj.InfoFields.String}); % Find event values ending with 'ul'. +% %reward = cell2mat(cellfun(@str2num,strsplit(infoFields{find(inc,1)},'�l'),'UniformOutput',0)); +% %amount = iff(isempty(reward),0,@()reward); +% end +% if ~any(amount); return; end % Return if no water was given +% try +% ai.postWater(subject, amount*0.001, now, 'Water', ai.SessionURL); +% catch +% warning('Failed to post the %.2fml %s recieved during the experiment to Alyx', amount*0.001, subject); +% end +% end end function expUpdate(obj, rig, evt) diff --git a/+exp/Experiment.m b/+exp/Experiment.m index b236aa93..5f411ac9 100644 --- a/+exp/Experiment.m +++ b/+exp/Experiment.m @@ -775,33 +775,6 @@ function saveData(obj) % save the data to the appropriate locations indicated by expRef savepaths = dat.expFilePath(obj.Data.expRef, 'block'); superSave(savepaths, struct('block', obj.Data)); - - if ~obj.AlyxInstance.IsLoggedIn - warning('No Alyx token set'); - else - try - subject = dat.parseExpRef(obj.Data.expRef); - if strcmp(subject, 'default'); return; end - % Register saved files - obj.AlyxInstance.registerFile(savepaths{end}); - % Save the session end time - if ~isempty(obj.AlyxInstance.SessionURL) - numTrials = obj.Data.numCompletedTrials; - if isfield(obj.Data, 'trial')&&isfield(obj.Data.trial, 'feedbackType') - numCorrect = sum([obj.Data.trial.feedbackType] == 1); - else - numCorrect = 0; - end - sessionData = struct('end_time', obj.AlyxInstance.datestr(now), ... - 'subject', subject, 'numberOfTrials', numTrials, 'numberOfCorrectTrials', numCorrect); - obj.AlyxInstance.postData(obj.AlyxInstance.SessionURL, sessionData, 'put'); - else - % Infer from date session and retrieve using expFilePath - end - catch ex - warning(ex.identifier, 'Failed to register files to Alyx: %s', ex.message); - end - end end end diff --git a/cortexlab/+exp/ChoiceWorld.m b/cortexlab/+exp/ChoiceWorld.m index 4c8d1edb..f55f0bb2 100644 --- a/cortexlab/+exp/ChoiceWorld.m +++ b/cortexlab/+exp/ChoiceWorld.m @@ -172,7 +172,68 @@ function drawFrame(obj) % obj.Data.trial(obj.TrialNum).stimFrame(n).time = obj.Clock.now; % obj.Data.trial(obj.TrialNum).stimFrame(n).targetBounds = round(bounds); end - end + end + + function saveData(obj) + saveData@exp.Experiment(obj); + if ~obj.AlyxInstance.IsLoggedIn + warning('No Alyx token set'); + else + try + subject = dat.parseExpRef(obj.Data.expRef); + if strcmp(subject, 'default'); return; end + % Register saved files + savepaths = dat.expFilePath(obj.Data.expRef, 'block'); + obj.AlyxInstance.registerFile(savepaths{end}); + % Save the session end time + if ~isempty(obj.AlyxInstance.SessionURL) + numTrials = obj.Data.numCompletedTrials; + if isfield(obj.Data, 'trial')&&isfield(obj.Data.trial, 'feedbackType') + numCorrect = sum([obj.Data.trial.feedbackType] == 1); + else + numCorrect = 0; + end + sessionData = struct('end_time', obj.AlyxInstance.datestr(now), ... + 'subject', subject, 'n_trials', numTrials, 'n_correct_trials', numCorrect); + obj.AlyxInstance.postData(obj.AlyxInstance.SessionURL, sessionData, 'put'); + else + % Infer from date session and retrieve using expFilePath + end + catch ex + warning(ex.identifier, 'Failed to register files to Alyx: %s', ex.message); + end + try + if ~isfield(obj.Data,'trial') && ~isfield(obj.Data.trial,'feedbackType') + return % No completed trials + end + % Reward volume + if ~any(strcmp(fieldnames(obj.Data.parameters),'rewardVolume')) % Reward is trial specific + condition = [obj.Data.trial.condition]; + reward = [condition.rewardVolume]; + amount = sum(reward(:,[obj.Data.trial.feedbackType]==1), 2); + else % Global reward x positive feedback + amount = obj.Data.parameters.rewardVolume(1)*... + sum([obj.Data.trial.feedbackType]==1); + end + % Reward on stimulus + if ~any(strcmp(fieldnames(obj.Data.parameters),'rewardOnStimulus')) % Reward is trial specific + condition = [obj.Data.trial.condition]; + stimReward = sum([condition.rewardOnStimulus],2); + amount = amount(1) + stimReward; + else % Global reward x positive feedback + amount = amount(1) + obj.Data.parameters.rewardOnStimulus(1); + end + if numel(amount)>1; amount = amount(1); end % Take first element (second being laser) + if ~any(amount); return; end % Return if no water was given + controller = obj.RewardController.SignalGenerators(strcmp(obj.RewardController.ChannelNames,'rewardValve')); + type = iff(isprop(controller, 'WaterType'), controller.WaterType, 'Water'); + obj.AlyxInstance.postWater(subject, amount*0.001, now, type, obj.AlyxInstance.SessionURL); + catch ex + warning(ex.identifier, 'Failed to post water to Alyx: %s', ex.message); + end + end + end + end end diff --git a/cortexlab/+git/update.m b/cortexlab/+git/update.m index 52705ca1..d45d1f79 100644 --- a/cortexlab/+git/update.m +++ b/cortexlab/+git/update.m @@ -46,18 +46,6 @@ function update(fatalOnError, scheduled) end end % TODO: check if submodules are empty and use init flag -cmdstr = strjoin({gitexepath, 'submodule update --remote --merge'}); -status = system(cmdstr, '-echo'); -if status ~= 0 - if fatalOnError - cd(origDir) - error('gitUpdate:submodule:updateFailed', ... - 'Failed to pull latest changes for submodules:, %s', cmdout) - else - warning('gitUpdate:submodule:updateFailed', ... - 'Failed to pull latest changes for submodules:, %s', cmdout) - end -end % Run any new tasks changesPath = fullfile(root, 'cortexlab', '+git', 'changes.m'); From f6a1ff5bcedc4fd7d8fe34e224cfe917a67a64ba Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 27 Nov 2018 19:37:13 +0000 Subject: [PATCH 221/393] Simplified ChoiceWorld water post --- cortexlab/+exp/ChoiceWorld.m | 22 +++------------------- cortexlab/+git/changes.m | 1 - 2 files changed, 3 insertions(+), 20 deletions(-) delete mode 100644 cortexlab/+git/changes.m diff --git a/cortexlab/+exp/ChoiceWorld.m b/cortexlab/+exp/ChoiceWorld.m index f55f0bb2..607d6d06 100644 --- a/cortexlab/+exp/ChoiceWorld.m +++ b/cortexlab/+exp/ChoiceWorld.m @@ -203,27 +203,11 @@ function saveData(obj) warning(ex.identifier, 'Failed to register files to Alyx: %s', ex.message); end try - if ~isfield(obj.Data,'trial') && ~isfield(obj.Data.trial,'feedbackType') + if ~isfield(obj.Data,'rewardDeliveredSizes') || ... + strcmp(obj.Data.endStatus, 'aborted') return % No completed trials end - % Reward volume - if ~any(strcmp(fieldnames(obj.Data.parameters),'rewardVolume')) % Reward is trial specific - condition = [obj.Data.trial.condition]; - reward = [condition.rewardVolume]; - amount = sum(reward(:,[obj.Data.trial.feedbackType]==1), 2); - else % Global reward x positive feedback - amount = obj.Data.parameters.rewardVolume(1)*... - sum([obj.Data.trial.feedbackType]==1); - end - % Reward on stimulus - if ~any(strcmp(fieldnames(obj.Data.parameters),'rewardOnStimulus')) % Reward is trial specific - condition = [obj.Data.trial.condition]; - stimReward = sum([condition.rewardOnStimulus],2); - amount = amount(1) + stimReward; - else % Global reward x positive feedback - amount = amount(1) + obj.Data.parameters.rewardOnStimulus(1); - end - if numel(amount)>1; amount = amount(1); end % Take first element (second being laser) + amount = sum(obj.Data.rewardDeliveredSizes(:,1)); % Take first element (second being laser) if ~any(amount); return; end % Return if no water was given controller = obj.RewardController.SignalGenerators(strcmp(obj.RewardController.ChannelNames,'rewardValve')); type = iff(isprop(controller, 'WaterType'), controller.WaterType, 'Water'); diff --git a/cortexlab/+git/changes.m b/cortexlab/+git/changes.m deleted file mode 100644 index 9232640b..00000000 --- a/cortexlab/+git/changes.m +++ /dev/null @@ -1 +0,0 @@ -addRigboxPaths; \ No newline at end of file From 779a9f817726d1c7e37ba5eb9cc66478cf893b20 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 27 Nov 2018 19:57:48 +0000 Subject: [PATCH 222/393] Scheduled update day set in paths file --- +dat/paths.m | 1 + +srv/expServer.m | 3 ++- mc.m | 5 +++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/+dat/paths.m b/+dat/paths.m index 37f96514..aff32ea7 100644 --- a/+dat/paths.m +++ b/+dat/paths.m @@ -28,6 +28,7 @@ p.databaseURL = 'https://alyx.cortexlab.net'; % p.databaseURL = 'https://dev.alyx.internationalbrainlab.org/'; p.gitExe = 'C:\Program Files\Git\cmd\git.exe'; +p.updateSchedule = 2; % Day on which to update code (2 = Monday) % Under the new system of having data grouped by mouse % rather than data type, all experimental data are saved here. diff --git a/+srv/expServer.m b/+srv/expServer.m index be5b3081..05e9c591 100644 --- a/+srv/expServer.m +++ b/+srv/expServer.m @@ -20,7 +20,8 @@ function expServer(useTimelineOverride, bgColour) %% Initialisation % Pull latest changes from remote -git.update(true, 2); % Update ever Monday +schedule = getOr(dat.paths, 'updateSchedule', 2); +git.update(true, schedule); % random seed random number generator rng('shuffle'); % communicator for receiving commands from clients diff --git a/mc.m b/mc.m index 071f4656..e81013d8 100644 --- a/mc.m +++ b/mc.m @@ -5,8 +5,9 @@ % 2013-06 CB created -% Pull latest changes from remote -git.update(true); +% Pull latest changes from remote every Monday +schedule = getOr(dat.paths, 'updateSchedule', 2); +git.update(true, schedule); f = figure('Name', 'MC',... 'MenuBar', 'none',... 'Toolbar', 'none',... From 318cae02c1c3c6abfb5ff9caa5cd034d6205edd1 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 28 Nov 2018 20:01:42 +0000 Subject: [PATCH 223/393] Launch Webpage for session fix --- +eui/AlyxPanel.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index 9f2e83dc..bdf360a2 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -473,7 +473,7 @@ function launchSessionURL(obj) % If the date of this latest base session is not the same date % as today, then create a new one for today - if isempty(sessions) || ~strcmp(sessions{end}.start_time(1:10), thisDate(1:10)) + if isempty(sessions) || ~strcmp(sessions(end).start_time(1:10), thisDate(1:10)) % Ask user whether he/she wants to create new session % Construct a questdlg with three options choice = questdlg('Would you like to create a new base session?', ... From a60e93af3eafaf923bba33b8ce92af16b8858660 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 30 Nov 2018 15:14:55 +0000 Subject: [PATCH 224/393] Small delay for correct behaviour of reward on stim --- cortexlab/+exp/configureChoiceExperiment.m | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cortexlab/+exp/configureChoiceExperiment.m b/cortexlab/+exp/configureChoiceExperiment.m index c37225f9..714f081b 100644 --- a/cortexlab/+exp/configureChoiceExperiment.m +++ b/cortexlab/+exp/configureChoiceExperiment.m @@ -117,6 +117,8 @@ % or 'onsetToneSoundPlayed' 'stimulusCueStarted' stimRewardHandler = exp.EventHandler('stimulusCueStarted'); stimRewardHandler.addAction(exp.DeliverReward('rewardOnStimulus')); + % Small delay to allow time for screen flip before the samples output + stimRewardHandler.Delay = exp.FixedTime(0.05); experiment.addEventHandler(stimRewardHandler); terminationHandler = exp.EventHandler('responseMade'); terminationHandler.addCallback(@(inf,t)reset(inf.Experiment.RewardController)); From b3a0f2f9987d608815a78cb64eab6a87c397e4bf Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 30 Nov 2018 16:06:14 +0000 Subject: [PATCH 225/393] Update tp submodules --- alyx-matlab | 2 +- signals | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/alyx-matlab b/alyx-matlab index 1e193a9d..9f75b53a 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit 1e193a9d21d59ac67900a41ef8181c7504d90b00 +Subproject commit 9f75b53a7fbbce7f80caf674157c592e19d91db2 diff --git a/signals b/signals index 8c480d8b..071e4420 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit 8c480d8b002ef8ae24c82b138ce4fbeea0d961cb +Subproject commit 071e44207410dab429f5c962999a56ade4d050dc From 0908e3353f806c770d1df7b3d2001469a4f14949 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Mon, 3 Dec 2018 16:06:16 +0000 Subject: [PATCH 226/393] Removed star and end dates in water-restriction GET for view subject method --- +eui/AlyxPanel.m | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index bdf360a2..15025b61 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -538,8 +538,7 @@ function viewSubjectHistory(obj, ax) % If not logged in or 'default' is selected, return if ~obj.AlyxInstance.IsLoggedIn||strcmp(obj.Subject, 'default'); return; end % collect the data for the table - endpnt = sprintf('water-requirement/%s?start_date=2016-01-01&end_date=%s', obj.Subject, datestr(now, 'yyyy-mm-dd')); - wr = obj.AlyxInstance.getData(endpnt); + wr = obj.AlyxInstance.getData(['water-requirement/', obj.Subject]); iw = iff(isempty(wr.implant_weight), 0, wr.implant_weight); records = catStructs(wr.records, nan); % no weighings found From c5cccc143f2ae63b62ca131409dc2652f48f9e26 Mon Sep 17 00:00:00 2001 From: jaib1 <jaib1@mit.edu> Date: Tue, 4 Dec 2018 14:57:48 +0000 Subject: [PATCH 227/393] commit for updated submodules --- alyx-matlab | 2 +- npy-matlab | 2 +- signals | 2 +- wheelAnalysis | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/alyx-matlab b/alyx-matlab index 3e88d1e9..9f75b53a 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit 3e88d1e9c61a9b04eb99c49f269a64db843cb017 +Subproject commit 9f75b53a7fbbce7f80caf674157c592e19d91db2 diff --git a/npy-matlab b/npy-matlab index a7c4900b..b7b0a4ef 160000 --- a/npy-matlab +++ b/npy-matlab @@ -1 +1 @@ -Subproject commit a7c4900b62757e1b657f2cc983a5df3282abd674 +Subproject commit b7b0a4ef6ba26d98a8c54e651d5444083c88311c diff --git a/signals b/signals index 4e64108c..3bfc5dfc 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit 4e64108ca6a8874edcb7a3e316df78004e6551ef +Subproject commit 3bfc5dfcdc3bbabbb24f93e25900a4f689161564 diff --git a/wheelAnalysis b/wheelAnalysis index 16979786..05f90203 160000 --- a/wheelAnalysis +++ b/wheelAnalysis @@ -1 +1 @@ -Subproject commit 169797868da3fe93e1581cb4d581cdc4f4d9cd34 +Subproject commit 05f902033bc834c98624d5634be3cf91b737f250 From 6f667d1872c69196e5dab25e4051fe0d5c2e23ed Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 5 Dec 2018 14:35:58 +0000 Subject: [PATCH 228/393] Fix'd weight plot; no longer ploting unweighed days --- +eui/AlyxPanel.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index 15025b61..eeb477be 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -547,7 +547,7 @@ function viewSubjectHistory(obj, ax) return end expected = [records.expected_weight]; - expected(expected==0) = nan; + expected(expected==0|cellfun('isempty',{records.weighing_at})) = nan; dates = cellfun(@(x)datenum(x), {records.date}); % build the figure to show it From 1462af7f939cd66f20f147bb1a6923afa0a69763 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 5 Dec 2018 14:40:17 +0000 Subject: [PATCH 229/393] Typo fix in signals repo --- signals | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/signals b/signals index 8c480d8b..0dc60c11 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit 8c480d8b002ef8ae24c82b138ce4fbeea0d961cb +Subproject commit 0dc60c11e20a75a3c63da9f80956fb1bb938f801 From 066ec03f97d851adfa411dcefef2f50ce65f060a Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 5 Dec 2018 17:53:24 +0000 Subject: [PATCH 230/393] Updates to modules and update fun --- alyx-matlab | 2 +- cortexlab/+git/update.m | 12 ++++++++---- npy-matlab | 2 +- signals | 2 +- wheelAnalysis | 2 +- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/alyx-matlab b/alyx-matlab index 3e88d1e9..9f75b53a 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit 3e88d1e9c61a9b04eb99c49f269a64db843cb017 +Subproject commit 9f75b53a7fbbce7f80caf674157c592e19d91db2 diff --git a/cortexlab/+git/update.m b/cortexlab/+git/update.m index d45d1f79..f084e274 100644 --- a/cortexlab/+git/update.m +++ b/cortexlab/+git/update.m @@ -11,7 +11,8 @@ function update(fatalOnError, scheduled) root = fileparts(which('addRigboxPaths')); lastFetch = getOr(dir(fullfile(root, '.git', 'FETCH_HEAD')), 'datenum'); -if scheduled && weekday(now) ~= scheduled && now - lastFetch < 7 +if (scheduled && weekday(now) ~= scheduled && now - lastFetch < 7) || ... + (~scheduled && now - lastFetch < 1/24) return end disp('Updating code...') @@ -35,8 +36,12 @@ function update(fatalOnError, scheduled) % return % end -cmdstr = strjoin({gitexepath, 'pull'}); -[status, cmdout] = system(cmdstr); +%%% Check that the submodules are initialized +cmdstr = strjoin({gitexepath, 'submodule', 'update', '--init'}); +[status, cmdout] = system(cmdstr, '-echo'); + +cmdstr = strjoin({gitexepath, 'pull --recurse-submodules'}); +[status, cmdout] = system(cmdstr, '-echo'); if status ~= 0 if fatalOnError cd(origDir) @@ -45,7 +50,6 @@ function update(fatalOnError, scheduled) warning('gitUpdate:pull:pullFailed', 'Failed to pull latest changes:, %s', cmdout) end end -% TODO: check if submodules are empty and use init flag % Run any new tasks changesPath = fullfile(root, 'cortexlab', '+git', 'changes.m'); diff --git a/npy-matlab b/npy-matlab index a7c4900b..b7b0a4ef 160000 --- a/npy-matlab +++ b/npy-matlab @@ -1 +1 @@ -Subproject commit a7c4900b62757e1b657f2cc983a5df3282abd674 +Subproject commit b7b0a4ef6ba26d98a8c54e651d5444083c88311c diff --git a/signals b/signals index 4e64108c..c33a2791 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit 4e64108ca6a8874edcb7a3e316df78004e6551ef +Subproject commit c33a2791b7d346f1be97433679fadc5467eedfdb diff --git a/wheelAnalysis b/wheelAnalysis index 16979786..05f90203 160000 --- a/wheelAnalysis +++ b/wheelAnalysis @@ -1 +1 @@ -Subproject commit 169797868da3fe93e1581cb4d581cdc4f4d9cd34 +Subproject commit 05f902033bc834c98624d5634be3cf91b737f250 From ddc5045d6bd1dd86dab61b5b5cd64faf0c479a3c Mon Sep 17 00:00:00 2001 From: jaib1 <jaib1@mit.edu> Date: Thu, 6 Dec 2018 19:18:02 +0000 Subject: [PATCH 231/393] stash and pull strategy changes to git.update --- cortexlab/+git/update.m | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/cortexlab/+git/update.m b/cortexlab/+git/update.m index f084e274..ee2fee72 100644 --- a/cortexlab/+git/update.m +++ b/cortexlab/+git/update.m @@ -17,14 +17,15 @@ function update(fatalOnError, scheduled) end disp('Updating code...') -% Get the path to the Git exe +% Get the path to the Git exe (can we assume this is unnecessary)? gitexepath = getOr(dat.paths, 'gitExe'); if isempty(gitexepath) - [~,gitexepath] = system('where git'); + [~,gitexepath] = system('where git'); % this doesn't always work end gitexepath = ['"', strtrim(gitexepath), '"']; -% Temporarily change directory into Rigbox +% Temporarily change directory into Rigbox -> isn't it safe to assume we're +% already in Rigbox? (since we're running this file, presumably we are?) origDir = pwd; cd(root) @@ -36,12 +37,17 @@ function update(fatalOnError, scheduled) % return % end -%%% Check that the submodules are initialized -cmdstr = strjoin({gitexepath, 'submodule', 'update', '--init'}); -[status, cmdout] = system(cmdstr, '-echo'); +% Check that the submodules are initialized +cmdstrInit = [gitexepath, ' ', 'submodule update --init']; +[status, cmdout] = system(cmdstrInit, '-echo'); -cmdstr = strjoin({gitexepath, 'pull --recurse-submodules'}); -[status, cmdout] = system(cmdstr, '-echo'); +% Stash any WIP changes +cmdstrStash = [gitexepath ' ' 'stash push -m "stash working changes before scheduled git update"']; +[status, cmdout] = system(cmdstrStash, '-echo'); + +%Pull submodules using "theirs" merge strategy +cmdstrPull = [gitexepath, ' ', 'pull --recurse-submodules --strategy-option=theirs']; +[status, cmdout] = system(cmdstrPull, '-echo'); if status ~= 0 if fatalOnError cd(origDir) From 2a3b473ffcdf3009d47d80f73548f05347621364 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 7 Dec 2018 12:45:07 +0000 Subject: [PATCH 232/393] hw-info ext bugfix; new commits to alyx-matlab; added test paths --- +dat/expFilePath.m | 2 +- alyx-matlab | 2 +- tests/+dat/paths.m | 66 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 tests/+dat/paths.m diff --git a/+dat/expFilePath.m b/+dat/expFilePath.m index b9053047..89133929 100644 --- a/+dat/expFilePath.m +++ b/+dat/expFilePath.m @@ -53,7 +53,7 @@ case 'block' % MAT-file with info about each set of trials suff = '_Block.mat'; case 'hw-info' % MAT-file with info about the hardware used for an experiment - suff = '_hardwareInfo.mat'; + suff = '_hardwareInfo.json'; case '2p-raw' % TIFF with 2-photon raw fluorescence movies suff = '_2P.tif'; case 'calcium-preview' diff --git a/alyx-matlab b/alyx-matlab index 9f75b53a..6a4f2863 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit 9f75b53a7fbbce7f80caf674157c592e19d91db2 +Subproject commit 6a4f2863281801627090a69693665a62f10e5aae diff --git a/tests/+dat/paths.m b/tests/+dat/paths.m new file mode 100644 index 00000000..95e39049 --- /dev/null +++ b/tests/+dat/paths.m @@ -0,0 +1,66 @@ +function p = paths(rig) +%DAT.PATHS Returns struct containing important paths for testing +% p = DAT.PATHS([RIG]) +% TODO: +% - Clean up expDefinitions directory +% Part of Rigbox + +% 2013-03 CB created + +thishost = 'dummyRig'; + +if nargin < 1 || isempty(rig) + rig = thishost; +end + +%% defaults +% path containing rigbox config folders +p.rigbox = fileparts(which('addRigboxPaths')); +% Repository for local copy of everything generated on this rig +p.localRepository = 'C:\LocalExpData'; +p.localAlyxQueue = fullfile(p.rigbox, 'tests', 'data', 'alyx'); +p.databaseURL = 'https://alyx-dev.cortexlab.net'; +% p.databaseURL = 'https://dev.alyx.internationalbrainlab.org/'; +p.gitExe = 'C:\Program Files\Git\cmd\git.exe'; + +% Under the new system of having data grouped by mouse +% rather than data type, all experimental data are saved here. +p.mainRepository = fullfile(p.rigbox, 'tests', 'data', 'Subjects'); + +% directory for organisation-wide configuration files, for now these should +% all remain on zserver +p.globalConfig = fullfile(p.rigbox, 'tests', 'data', 'config'); +% directory for rig-specific configuration files +p.rigConfig = fullfile(p.globalConfig, rig); +% repository for all experiment definitions +p.expDefinitions = fullfile(p.rigbox, 'tests', 'data', 'expdefs'); + +% repository for working analyses that are not meant to be stored +% permanently +p.workingAnalysisRepository = fullfile(p.rigbox, 'tests', 'data'); + +% for tape backups, first files go here: +p.tapeStagingRepository = fullfile(p.rigbox, 'tests', 'staging'); + +% then they go here: +p.tapeArchiveRepository = fullfile(p.rigbox, 'tests', 'toarchive'); + + +%% load rig-specific overrides from config file, if any +customPathsFile = fullfile(p.rigConfig, 'paths.mat'); +if file.exists(customPathsFile) + customPaths = loadVar(customPathsFile, 'paths'); + if isfield(customPaths, 'centralRepository') + % 'centralRepository' is deprecated, remove field, if any + customPaths = rmfield(customPaths, 'centralRepository'); + end + if isfield(customPaths, 'expInfoRepository') + % 'expInfo' is deprecated, change to 'main' + p.mainRepository = customPaths.expInfoRepository; + customPaths = rmfield(customPaths, 'expInfoRepository'); + end + % merge paths structures, with precedence on the loaded custom paths + p = mergeStructs(customPaths, p); +end + +end From 3f764de7e92c821fdb9104cdc1a070a2edb2dcce Mon Sep 17 00:00:00 2001 From: Jai Bhagat <jaib1@mit.edu> Date: Fri, 7 Dec 2018 14:39:33 +0000 Subject: [PATCH 233/393] Removed questioning directory comments --- cortexlab/+git/update.m | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cortexlab/+git/update.m b/cortexlab/+git/update.m index ee2fee72..cd5f0cb9 100644 --- a/cortexlab/+git/update.m +++ b/cortexlab/+git/update.m @@ -17,15 +17,14 @@ function update(fatalOnError, scheduled) end disp('Updating code...') -% Get the path to the Git exe (can we assume this is unnecessary)? +% Get the path to the Git exe gitexepath = getOr(dat.paths, 'gitExe'); if isempty(gitexepath) [~,gitexepath] = system('where git'); % this doesn't always work end gitexepath = ['"', strtrim(gitexepath), '"']; -% Temporarily change directory into Rigbox -> isn't it safe to assume we're -% already in Rigbox? (since we're running this file, presumably we are?) +% Temporarily change directory into Rigbox to git pull origDir = pwd; cd(root) @@ -65,4 +64,4 @@ function update(fatalOnError, scheduled) end cd(origDir) -end \ No newline at end of file +end From 6e8e9de9a2f8ef144715d067e1633b2c95cf034e Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 7 Dec 2018 17:56:43 +0000 Subject: [PATCH 234/393] Give different water types in the future. Today's recorded weigth now displayed --- +eui/AlyxPanel.m | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index eeb477be..db481a67 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -309,8 +309,10 @@ function giveFutureWater(obj) % paramProfiles file. This may be used to notify weekend staff % of the experimentor's intent to train on that date. thisDate = now; - prompt = sprintf(['Enter space-separated numbers \n'... - '[tomorrow, day after that, day after that.. etc] \n',... + waterType = obj.WaterType.String{obj.WaterType.Value}; + prompt = sprintf(['To post future ', strrep(lower(waterType), '%', '%%'), ', ',... + 'enter space-separated numbers, i.e. \n',... + '[tomorrow, day after that, day after that.. etc] \n\n',... 'Enter "0" to skip a day\nEnter "-1" to indicate training for that day\n']); amtStr = inputdlg(prompt,'Future Amounts', [1 50]); if isempty(amtStr)||~obj.AlyxInstance.IsLoggedIn @@ -332,7 +334,7 @@ function giveFutureWater(obj) amtWtrDates = amt(amt > 0); % amount of water to give on future water dates for d = 1:length(futWtrDates) - obj.AlyxInstance.postWater(obj.Subject, amtWtrDates(d), futWtrDates(d), 'Water'); + obj.AlyxInstance.postWater(obj.Subject, amtWtrDates(d), futWtrDates(d), waterType); [~,day] = weekday(futWtrDates(d), 'long'); obj.log('Water administration of %.2f for %s posted successfully to alyx for %s %s',... amtWtrDates(d), obj.Subject, day, datestr(futWtrDates(d), 'dd mmm yyyy')); @@ -371,7 +373,7 @@ function dispWaterReq(obj, src, ~) else record = struct(); end - weight = getOr(record, 'weight', NaN); % Get today's measured weight + weight = iff(isempty(record.weighing_at), NaN, record.weight); % Get today's measured weight water = getOr(record, 'given_water_liquid', 0); % Get total water given gel = getOr(record, 'given_water_hydrogel', 0); % Get total gel given expected_weight = getOr(record, 'expected_weight', NaN); From 5d31c5acba003d1d52d22bae12a83499076875fd Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 7 Dec 2018 19:17:12 +0000 Subject: [PATCH 235/393] Update to alyx-matlab submodule --- alyx-matlab | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alyx-matlab b/alyx-matlab index 6a4f2863..87cd195c 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit 6a4f2863281801627090a69693665a62f10e5aae +Subproject commit 87cd195c4745d74b56e01357255a34984675c89e From 05f723e667427fdc1e67eb670b4eb3ede5575987 Mon Sep 17 00:00:00 2001 From: Jai Bhagat <jaib1@mit.edu> Date: Fri, 7 Dec 2018 22:02:40 +0000 Subject: [PATCH 236/393] Update readme.md --- readme.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/readme.md b/readme.md index 120f73ae..6f6e5470 100644 --- a/readme.md +++ b/readme.md @@ -10,8 +10,8 @@ The following is a brief description of how to install Rigbox on your experiment ## Prerequisites Rigbox has the following software dependencies: -* Windows Operating System (7 or later) -* MATLAB (2016a or later) +* Windows Operating System (7 or later, 64-bit) +* MATLAB (2016b or later) * The following MathWorks MATLAB toolboxes: * Data Acquisition Toolbox * Signal Processing Toolbox @@ -117,7 +117,7 @@ The 'StimulusControl' class is used by the mc computer to manage the stimulus se ### cb-tools/burgbox -Burgbox contains many simple helper functions that are used by the main packages. Within this directory are additional packages: +"Burgbox" contains many simple helper functions that are used by the main packages. Within this directory are additional packages: * +bui --- Classes for managing graphics objects such as axes * +aud --- Functions for interacting with PsychoPortAudio @@ -131,7 +131,11 @@ Burgbox contains many simple helper functions that are used by the main packages ### cortexlab -The cortexlab directory is intended for functions and classes that are rig or cortexlab specific, for instance code that allows compatibility with other stimulus presentation packages used by cortexlab (e.g. MPEP) +The "cortexlab" directory is intended for functions and classes that are rig or cortexlab specific, for instance code that allows compatibility with other stimulus presentation packages used by cortexlab (e.g. MPEP) + +### tests + +The "tests" directory contains code for running unit tests within Rigbox. ### submodules From ad6571b27a368ea7649005c0982d6307e1b9e112 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Mon, 10 Dec 2018 14:18:41 +0000 Subject: [PATCH 237/393] Update to signals --- signals | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/signals b/signals index c33a2791..5dd71e46 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit c33a2791b7d346f1be97433679fadc5467eedfdb +Subproject commit 5dd71e4618ef7738a01f7ae6ab8ce79a3505ed63 From 8978915f389f23c935e2efbb20a7d193695548fe Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Mon, 10 Dec 2018 15:22:47 +0000 Subject: [PATCH 238/393] Update to signals --- signals | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/signals b/signals index 5dd71e46..83a1cc17 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit 5dd71e4618ef7738a01f7ae6ab8ce79a3505ed63 +Subproject commit 83a1cc17e47b98b2aa0a285dd9fe959c2253d5b3 From 89ce3fbb5ad1c0107049221f9a9ddd19820a79cb Mon Sep 17 00:00:00 2001 From: Jai Bhagat <jaib1@mit.edu> Date: Mon, 10 Dec 2018 18:20:53 +0000 Subject: [PATCH 239/393] Update git.update --- cortexlab/+git/update.m | 46 ++++++++++++++--------------------------- 1 file changed, 16 insertions(+), 30 deletions(-) diff --git a/cortexlab/+git/update.m b/cortexlab/+git/update.m index cd5f0cb9..5aa86c58 100644 --- a/cortexlab/+git/update.m +++ b/cortexlab/+git/update.m @@ -1,4 +1,4 @@ -function update(fatalOnError, scheduled) +function update(scheduled) % GIT.UPDATE Pull latest Rigbox code % Pulls the latest code from the remote repository. If scheduled is a % value in the range [1 7] corresponding to the days of the week, the @@ -6,8 +6,7 @@ function update(fatalOnError, scheduled) % a week ago. % TODO Find quicker way to check for changes % See also -if nargin == 0; fatalOnError = true; end -if nargin < 2; scheduled = 0; end +if nargin < 1; scheduled = 0; end root = fileparts(which('addRigboxPaths')); lastFetch = getOr(dir(fullfile(root, '.git', 'FETCH_HEAD')), 'datenum'); @@ -28,32 +27,20 @@ function update(fatalOnError, scheduled) origDir = pwd; cd(root) -% Check if there are changes before pulling -% cmdstr = strjoin({gitexepath, 'fetch'}); -% system(cmdstr, '-echo'); -% if isempty(cmdout) -% cd(origDir) -% return -% end - -% Check that the submodules are initialized -cmdstrInit = [gitexepath, ' ', 'submodule update --init']; -[status, cmdout] = system(cmdstrInit, '-echo'); - -% Stash any WIP changes -cmdstrStash = [gitexepath ' ' 'stash push -m "stash working changes before scheduled git update"']; -[status, cmdout] = system(cmdstrStash, '-echo'); - -%Pull submodules using "theirs" merge strategy -cmdstrPull = [gitexepath, ' ', 'pull --recurse-submodules --strategy-option=theirs']; -[status, cmdout] = system(cmdstrPull, '-echo'); -if status ~= 0 - if fatalOnError - cd(origDir) - error('gitUpdate:pull:pullFailed', 'Failed to pull latest changes:, %s', cmdout) - else - warning('gitUpdate:pull:pullFailed', 'Failed to pull latest changes:, %s', cmdout) - end +cmdstrStash = [gitexepath, ' stash push -m "stash Rigbox working changes before scheduled git update"']; +cmdstrStashSubs = [gitexepath, ' submodule foreach "git stash push"']; +cmdstrInit = [gitexepath, ' submodule update --init']; +cmdstrPull = [gitexepath, ' pull --recurse-submodules --strategy-option=theirs']; + +% Stash any WIP, check submodules are initialized, pull +try + [status, cmdout] = system(cmdstrStash, '-echo'); + [status, cmdout] = system(cmdstrStashSubs, '-echo'); + [status, cmdout] = system(cmdstrInit, '-echo'); + [status, cmdout] = system(cmdstrPull, '-echo'); +catch ex + cd(origDir) + error('gitUpdate:pull:pullFailed', 'Failed to pull latest changes:, %s', cmdout) end % Run any new tasks @@ -62,6 +49,5 @@ function update(fatalOnError, scheduled) git.changes; delete(changesPath); end - cd(origDir) end From 329bd47d66b75934b0e8502546c9559285efc6c8 Mon Sep 17 00:00:00 2001 From: Jai Bhagat <jaib1@mit.edu> Date: Mon, 10 Dec 2018 18:21:38 +0000 Subject: [PATCH 240/393] corresponding 'git.update' update to 'mc' --- mc.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mc.m b/mc.m index 071f4656..8297142f 100644 --- a/mc.m +++ b/mc.m @@ -6,7 +6,7 @@ % 2013-06 CB created % Pull latest changes from remote -git.update(true); +git.update; f = figure('Name', 'MC',... 'MenuBar', 'none',... 'Toolbar', 'none',... From 5facbea0047b13001dfaa94437b1a300ebbba112 Mon Sep 17 00:00:00 2001 From: Jai Bhagat <jaib1@mit.edu> Date: Mon, 10 Dec 2018 18:22:25 +0000 Subject: [PATCH 241/393] corresponding 'git.update' update to 'srv.expServer' --- +srv/expServer.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/+srv/expServer.m b/+srv/expServer.m index 586f03b3..4e8d2bcc 100644 --- a/+srv/expServer.m +++ b/+srv/expServer.m @@ -20,7 +20,7 @@ function expServer(useTimelineOverride, bgColour) %% Initialisation % Pull latest changes from remote -git.update(true); +git.update; % random seed random number generator rng('shuffle'); % communicator for receiving commands from clients From 71c4ade2106a1c56d68be7208954dabcdcaa2ce8 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 27 Nov 2018 19:57:48 +0000 Subject: [PATCH 242/393] Scheduled update day taken from paths file --- +dat/paths.m | 5 +++-- +srv/expServer.m | 2 +- cortexlab/+git/update.m | 2 +- mc.m | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/+dat/paths.m b/+dat/paths.m index 37f96514..52c1145c 100644 --- a/+dat/paths.m +++ b/+dat/paths.m @@ -25,9 +25,10 @@ % Repository for local copy of everything generated on this rig p.localRepository = 'C:\LocalExpData'; p.localAlyxQueue = 'C:\localAlyxQueue'; -p.databaseURL = 'https://alyx.cortexlab.net'; -% p.databaseURL = 'https://dev.alyx.internationalbrainlab.org/'; +p.databaseURL = 'https://alyx.cortexlab.net'; % 'https://dev.alyx.internationalbrainlab.org/'; p.gitExe = 'C:\Program Files\Git\cmd\git.exe'; +% Day on which to update code (0 = Everyday, 1 = Sunday, etc.) +p.updateSchedule = 0; % Under the new system of having data grouped by mouse % rather than data type, all experimental data are saved here. diff --git a/+srv/expServer.m b/+srv/expServer.m index 4e8d2bcc..235fd286 100644 --- a/+srv/expServer.m +++ b/+srv/expServer.m @@ -20,7 +20,7 @@ function expServer(useTimelineOverride, bgColour) %% Initialisation % Pull latest changes from remote -git.update; +git.update(); % random seed random number generator rng('shuffle'); % communicator for receiving commands from clients diff --git a/cortexlab/+git/update.m b/cortexlab/+git/update.m index 5aa86c58..86cb25e9 100644 --- a/cortexlab/+git/update.m +++ b/cortexlab/+git/update.m @@ -6,7 +6,7 @@ function update(scheduled) % a week ago. % TODO Find quicker way to check for changes % See also -if nargin < 1; scheduled = 0; end +if nargin < 1; scheduled = getOr(dat.paths, 'updateSchedule', 0); end root = fileparts(which('addRigboxPaths')); lastFetch = getOr(dir(fullfile(root, '.git', 'FETCH_HEAD')), 'datenum'); diff --git a/mc.m b/mc.m index 8297142f..76379578 100644 --- a/mc.m +++ b/mc.m @@ -6,7 +6,7 @@ % 2013-06 CB created % Pull latest changes from remote -git.update; +git.update(); f = figure('Name', 'MC',... 'MenuBar', 'none',... 'Toolbar', 'none',... From 44090664d7aabbbf15f9014ca9bf685a9fb6ac32 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 14 Dec 2018 16:28:20 +0000 Subject: [PATCH 243/393] Update to submodules --- alyx-matlab | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alyx-matlab b/alyx-matlab index 87cd195c..8eeb2f5f 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit 87cd195c4745d74b56e01357255a34984675c89e +Subproject commit 8eeb2f5f9336b701ced0bb9a9c4ce579e4623105 From d23f6fb5b3b90ccac56c056ae03f7ef03f5d9e57 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 14 Dec 2018 18:50:19 +0000 Subject: [PATCH 244/393] Fixed bug with hw-info saved as '.jjson' file --- +dat/expFilePath.m | 2 +- +eui/AlyxPanel.m | 2 +- +srv/expServer.m | 6 +++--- alyx-matlab | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/+dat/expFilePath.m b/+dat/expFilePath.m index 89133929..79736854 100644 --- a/+dat/expFilePath.m +++ b/+dat/expFilePath.m @@ -52,7 +52,7 @@ switch lower(type) case 'block' % MAT-file with info about each set of trials suff = '_Block.mat'; - case 'hw-info' % MAT-file with info about the hardware used for an experiment + case 'hw-info' % JSON-file with info about the hardware used for an experiment suff = '_hardwareInfo.json'; case '2p-raw' % TIFF with 2-photon raw fluorescence movies suff = '_2P.tif'; diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index db481a67..458293e0 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -711,7 +711,7 @@ function log(obj, varargin) case 'down' A = ceil(a*c)/c; otherwise - A = round(a*c)/c; + A = round(a, sigFigures, 'significant'); end end end diff --git a/+srv/expServer.m b/+srv/expServer.m index 586f03b3..501e170f 100644 --- a/+srv/expServer.m +++ b/+srv/expServer.m @@ -260,13 +260,13 @@ function handleMessage(id, data, host) rig.stimWindow.flip(); % clear the screen after % save a copy of the hardware in JSON - name = dat.expFilePath(expRef, 'hw-info', 'master'); - fid = fopen([name(1:end-3) 'json'], 'w'); + hwInfo = dat.expFilePath(expRef, 'hw-info', 'master'); + fid = fopen(hwInfo, 'w'); fprintf(fid, '%s', obj2json(rig)); fclose(fid); if ~strcmp(dat.parseExpRef(expRef), 'default') try - Alyx.registerFile([name(1:end-3) 'json']); + Alyx.registerFile(hwInfo); catch ex warning(ex.identifier, 'Failed to register hardware info: %s', ex.message); end diff --git a/alyx-matlab b/alyx-matlab index 87cd195c..11038d22 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit 87cd195c4745d74b56e01357255a34984675c89e +Subproject commit 11038d22c42f96c0408027c7bbe3f0939f79028a From 8135874475deb241bb3db49613ff9e8990d58732 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 14 Dec 2018 19:01:07 +0000 Subject: [PATCH 245/393] Reverting changes; .mat hw-nfo still used by tlvs. Using strrep to deal with ext in expServer --- +dat/expFilePath.m | 4 ++-- +srv/expServer.m | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/+dat/expFilePath.m b/+dat/expFilePath.m index 79736854..b9053047 100644 --- a/+dat/expFilePath.m +++ b/+dat/expFilePath.m @@ -52,8 +52,8 @@ switch lower(type) case 'block' % MAT-file with info about each set of trials suff = '_Block.mat'; - case 'hw-info' % JSON-file with info about the hardware used for an experiment - suff = '_hardwareInfo.json'; + case 'hw-info' % MAT-file with info about the hardware used for an experiment + suff = '_hardwareInfo.mat'; case '2p-raw' % TIFF with 2-photon raw fluorescence movies suff = '_2P.tif'; case 'calcium-preview' diff --git a/+srv/expServer.m b/+srv/expServer.m index dff9a879..4748aa69 100644 --- a/+srv/expServer.m +++ b/+srv/expServer.m @@ -260,7 +260,7 @@ function handleMessage(id, data, host) rig.stimWindow.flip(); % clear the screen after % save a copy of the hardware in JSON - hwInfo = dat.expFilePath(expRef, 'hw-info', 'master'); + hwInfo = strrep(dat.expFilePath(expRef, 'hw-info', 'master'), '.mat', '.json'); fid = fopen(hwInfo, 'w'); fprintf(fid, '%s', obj2json(rig)); fclose(fid); From c9d7a00b4c30fc1663057563cd45b86098d21857 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Mon, 17 Dec 2018 14:24:31 +0000 Subject: [PATCH 246/393] Wheel assignment back in useRig --- +exp/SignalsExp.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/+exp/SignalsExp.m b/+exp/SignalsExp.m index bcf93e6d..eae1c04e 100644 --- a/+exp/SignalsExp.m +++ b/+exp/SignalsExp.m @@ -153,8 +153,6 @@ obj.Events.newTrial = net.origin('newTrial'); obj.Events.expStop = net.origin('expStop'); obj.Inputs.wheel = net.origin('wheel'); - obj.Wheel = rig.mouseInput; - obj.Wheel.zero(); obj.Inputs.wheelMM = obj.Inputs.wheel.map(@... (x)obj.Wheel.MillimetresFactor*(x-obj.Wheel.ZeroOffset)).skipRepeats(); obj.Inputs.wheelDeg = obj.Inputs.wheel.map(... @@ -221,6 +219,8 @@ function useRig(obj, rig) warning('squeak:hw', 'No screen configuration specified. Visual locations will be wrong.'); end obj.DaqController = rig.daqController; + obj.Wheel = rig.mouseInput; + obj.Wheel.zero(); if isfield(rig, 'lickDetector') obj.LickDetector = rig.lickDetector; obj.LickDetector.zero(); From 77a3c2c113f77b6fc544ce348b394084916a104d Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 18 Dec 2018 14:09:49 +0000 Subject: [PATCH 247/393] Rolled back DaqController due to issues with DigitalTTL output --- +hw/DaqController.m | 140 ++++++++++++-------------------------------- 1 file changed, 37 insertions(+), 103 deletions(-) diff --git a/+hw/DaqController.m b/+hw/DaqController.m index 524047e4..a9835d40 100644 --- a/+hw/DaqController.m +++ b/+hw/DaqController.m @@ -15,7 +15,7 @@ % %Define the channel ID to output on % daqController.DaqChannelIds = {'ai0'}; % %As it is an analogue output, set the AnalogueChannelsIdx to true - % daqController.AnalogueChannelsIdx(1) = true; + % daqController.AnalogueChannelIdx(1) = true; % %Add a signal generator that will return the correct samples for % %delivering a reward of a specified volume % daqController.SignalGenerators(1) = hw.RewardValveControl; @@ -37,11 +37,8 @@ % See also HW.CONTROLSIGNALGENERATOR, HW.DAQROTARYENCODER % 2013 CB created % 2017-07 MW added digital output support - % 2018-11 JB added output reward as clock channel - % 2018-11 JB added simultaneous analog/digital and digital/digital output properties - ChannelNames = {} % name to refer to each channel %Signal generator for each channel. Each should be an object of class %hw.ControlSignalGenerator, for generating command waveforms. @@ -51,75 +48,53 @@ SampleRate = 1000 % output sample rate ("scans/sec") of the daq device % 1000 is also the default of the ni daq devices themselves, so if % you don't change this, it doesn't actually do anything. - end properties (Transient) - DaqSession % should be a DAQ session containing at least one analogue output channel DigitalDaqSession % a DAQ session containing only digital output channels - ClockDaqSession % a DAQ session for implementing output to a clock channel - end properties (Dependent) - Value %The current voltage on each DAQ channel NumChannels %Number of channels controlled AnalogueChannelsIdx %Logical array of analogue channel IDs - end properties (Access = private, Transient) - CurrValue - end methods - function createDaqChannels(obj) - if isempty(obj.DaqSession)&&any(strncmp('ao',(obj.DaqChannelIds),2)) + if isempty(obj.DaqSession) obj.DaqSession = daq.createSession('ni'); obj.DaqSession.Rate = obj.SampleRate; end - if isempty(obj.DigitalDaqSession)&&any(strncmp('port',(obj.DaqChannelIds),4)) + if isempty(obj.DigitalDaqSession)&&any(~obj.AnalogueChannelsIdx) obj.DigitalDaqSession = daq.createSession('ni'); end - if isempty(obj.ClockDaqSession)&&any(strncmp('ctr',(obj.DaqChannelIds),3)) - obj.ClockDaqSession = daq.createSession('ni'); - end n = obj.NumChannels; if n > 0 - for i = 1:n + for ii = 1:n if iscell(obj.DaqIds) - daqid = obj.DaqIds{i}; + daqid = obj.DaqIds{ii}; else daqid = obj.DaqIds; end - % is channel analogue? - if strncmp('ao',(obj.DaqChannelIds{i}),2) + if obj.AnalogueChannelsIdx(ii) % is channal analogue? obj.DaqSession.addAnalogOutputChannel(... - daqid, obj.DaqChannelIds{i}, 'Voltage'); - % is channel clock output? - elseif strncmp('ctr',(obj.DaqChannelIds{i}),3) - obj.ClockDaqSession.addCounterOutputChannel(... - daqid, obj.DaqChannelIds{i}, 'PulseGeneration'); - else % assume digital, always 'OutputOnly' + daqid, obj.DaqChannelIds{ii}, 'Voltage'); + else % assume digital, always output only obj.DigitalDaqSession.addDigitalChannel(... - daqid, obj.DaqChannelIds{i}, 'OutputOnly'); + daqid, obj.DaqChannelIds{ii}, 'OutputOnly'); end end - - % what are these lines doing? why outputSingleScan? why a/d separate? - v = [obj.SignalGenerators.DefaultValue]; -% obj.DaqSession.outputSingleScan(v(obj.AnalogueChannelsIdx)); -% % digital (non-clock) channel only -% nonClockDigis = strncmp('port',(obj.DaqChannelIds),4); -% if any(nonClockDigis) -% obj.DigitalDaqSession.outputSingleScan(v(nonClockDigis)); -% end + obj.DaqSession.outputSingleScan(v(obj.AnalogueChannelsIdx)); + if any(~obj.AnalogueChannelsIdx) + obj.DigitalDaqSession.outputSingleScan(v(~obj.AnalogueChannelsIdx)); + end obj.CurrValue = v; else obj.CurrValue = []; @@ -130,14 +105,12 @@ function command(obj, varargin) % Sends command signals to each channel % % command(channels, values) - % sends command signal to a channel with the corresponding value - % (i.e. there is a channel-value pair for each command signal) - % 'channels' is a cell array of strings with each channel name, and - % 'value' is a cell array of values? + % sends command signals to each channel carrying each value. + % 'channels' is a cell array of strings with each channel name, and + % value is % % command(values) - % for length of values, sends command signals to the corresponding - % ordered channels + % sends command signals to all channels carrying each value % % [CHANNEL,INDEX] = addAnalogInputChannel(...) % addAnalogInputChannel optionally returns CHANNEL, which is an @@ -170,13 +143,13 @@ function command(obj, varargin) gen = obj.SignalGenerators(1:n); rate = obj.DaqSession.Rate; waveforms = cell(1, n); - for i = 1:n + for ii = 1:n if iscell(values) - v = values{i}; + v = values{ii}; else - v = values(:,i); + v = values(:,ii); end - waveforms{i} = gen(i).waveform(rate, v); + waveforms{ii} = gen(ii).waveform(rate, v); end if obj.DaqSession.IsRunning % if a daq operation is in progress, stop it, and set its output @@ -185,8 +158,7 @@ function command(obj, varargin) end channelNames = obj.ChannelNames(1:n); analogueChannelsIdx = obj.AnalogueChannelsIdx(1:n); - % for all analogue channel outputs - if any(analogueChannelsIdx)&&any(values(:,analogueChannelsIdx)~=0) + if any(analogueChannelsIdx)&&any(any(values(:,analogueChannelsIdx)~=0)) queue(obj, channelNames(analogueChannelsIdx), waveforms(analogueChannelsIdx)); if foreground startForeground(obj.DaqSession); @@ -195,43 +167,18 @@ function command(obj, varargin) end readyWait(obj); obj.DaqSession.release; - end - % for all digital or clock outputs (why does this have to be an else?) - if any(~analogueChannelsIdx)&&any(values(:,~analogueChannelsIdx)~=0) - maxLnWaveform = max(cellfun(@length, waveforms)); - % pad shorter waveforms - for i = 1:length(waveforms) - waveforms{i}(end:maxLnWaveform) = waveforms{i}(end); - end - waveformsMtx = cell2mat(waveforms); - %if iscolumn(waveformsMtx), waveformsMtx = waveformsMtx'; end - % output columns of waveformsMtx (values for each channel) - for n = 1:size(waveformsMtx,2) - %if we have some value to output - if any(waveformsMtx(:,n)) - % for clock output channels with a valid value to output - if strncmp('ctr',(obj.DaqChannelIds{n}),3) - obj.ClockDaqSession.DurationInSeconds = length(waveformsMtx) / obj.SampleRate; - %Duty Cycle must be b/w 0-1, so set to 'n' and scale frequency by 1/n - obj.ClockDaqSession.Channels.DutyCycle = 0.99; - obj.ClockDaqSession.Channels.Frequency = 1/obj.ClockDaqSession.DurationInSeconds/0.99; - startBackground(obj.ClockDaqSession); - %for digital output channels - elseif strncmp('port',(obj.DaqChannelIds{n}),4) - obj.DigitalDaqSession.outputSingleScan(waveformsMtx(:,n)); + elseif any(~analogueChannelsIdx) + waveforms = waveforms(~analogueChannelsIdx); + for n = 1:length(waveforms) + digitalValues = waveforms{n}; + for m = 1:length(digitalValues) + obj.DigitalDaqSession.outputSingleScan(digitalValues(m)); end end - end end end end - function clearSessions(obj) - obj.DaqSession = []; - obj.DigitalDaqSession = []; - obj.ClockDaqSession = []; - end - function v = get.NumChannels(obj) v = numel(obj.DaqChannelIds); end @@ -246,9 +193,7 @@ function clearSessions(obj) function set.Value(obj, v) readyWait(obj); - if any (obj.AnalogueChannelsIdx) - obj.DaqSession.outputSingleScan(v(obj.AnalogueChannelsIdx)); - end + obj.DaqSession.outputSingleScan(v(obj.AnalogueChannelsIdx)); if any(~obj.AnalogueChannelsIdx) obj.DigitalDaqSession.outputSingleScan(v(~obj.AnalogueChannelsIdx)); end @@ -256,26 +201,20 @@ function clearSessions(obj) end function reset(obj) - if ~isempty(obj.DaqSession) - stop(obj.DaqSession); - end + stop(obj.DaqSession); if ~isempty(obj.DigitalDaqSession) stop(obj.DigitalDaqSession); end v = [obj.SignalGenerators.DefaultValue]; - if any(obj.AnalogueChannelsIdx) - outputSingleScan(obj.DaqSession, v(obj.AnalogueChannelsIdx)); - end + outputSingleScan(obj.DaqSession, v(obj.AnalogueChannelsIdx)); if any(~obj.AnalogueChannelsIdx) outputSingleScan(obj.DigitalDaqSession, v(~obj.AnalogueChannelsIdx)); end obj.CurrValue = v; end - end methods (Access = protected) - function queue(obj, names, waveforms) names = ensureCell(names); waveforms = ensureCell(waveforms); @@ -284,13 +223,12 @@ function queue(obj, names, waveforms) len = cellfun(@numel, waveforms); defaultValues = [obj.SignalGenerators.DefaultValue]; samples = repmat(defaultValues(obj.AnalogueChannelsIdx), max(len), 1); - for i = 1:numel(waveforms) - % cidx = strcmp(names{i}, obj.ChannelNames); - % assert(sum(cidx) == 1, 'Channel name mismatch'); - % samples(1:len(i),cidx) = waveforms{i}; - samples(1:len(i),i) = waveforms{i}; + for ii = 1:numel(waveforms) + cidx = strcmp(names{ii}, obj.ChannelNames); + assert(sum(cidx) == 1, 'Channel name mismatch'); + samples(1:len(ii),cidx) = waveforms{ii}; end - %readyWait(obj); + readyWait(obj); % plot(samples,'-x'), xlim([-1 300]) obj.DaqSession.queueOutputData(samples); % samplelen = size(samples,1)/1000 @@ -298,17 +236,13 @@ function queue(obj, names, waveforms) end function readyWait(obj) - if ~isempty(obj.DaqSession)&&obj.DaqSession.IsRunning + if obj.DaqSession.IsRunning obj.DaqSession.wait(); end if ~isempty(obj.DigitalDaqSession)&&obj.DigitalDaqSession.IsRunning obj.DigitalDaqSession.wait(); end - if ~isempty(obj.ClockDaqSession)&&obj.ClockDaqSession.IsRunning - obj.ClockDaqSession.wait(); - end end - end end From 0e5e4004e1daecdfefc2486994016899249a1990 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 18 Dec 2018 18:22:41 +0000 Subject: [PATCH 248/393] Logs no longer subtract reference time: already done by obj.Clock.now method --- +exp/SignalsExp.m | 8 ++++---- signals | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/+exp/SignalsExp.m b/+exp/SignalsExp.m index eae1c04e..2eb9d192 100644 --- a/+exp/SignalsExp.m +++ b/+exp/SignalsExp.m @@ -600,17 +600,17 @@ function cleanup(obj) % collate the logs %events - obj.Data.events = logs(obj.Events, obj.Clock.ReferenceTime); + obj.Data.events = logs(obj.Events); %params parsLog = obj.ParamsLog.Node.CurrValue; obj.Data.paramsValues = [parsLog.value]; obj.Data.paramsTimes = [parsLog.time]; %inputs - obj.Data.inputs = logs(obj.Inputs, obj.Clock.ReferenceTime); + obj.Data.inputs = logs(obj.Inputs); %outputs - obj.Data.outputs = logs(obj.Outputs, obj.Clock.ReferenceTime); + obj.Data.outputs = logs(obj.Outputs); %audio -% obj.Data.audio = logs(audio, clockZeroTime); +% obj.Data.audio = logs(audio); % MATLAB time stamp for ending the experiment obj.Data.endDateTime = stopdatetime; diff --git a/signals b/signals index 83a1cc17..1c9127de 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit 83a1cc17e47b98b2aa0a285dd9fe959c2253d5b3 +Subproject commit 1c9127de66f3edc4c2b0063019b3e9045929a21c From b55ea8e46cecd03d87e7b75fcd9a9c286a1c19a8 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 18 Dec 2018 19:41:27 +0000 Subject: [PATCH 249/393] Releasing hardware and ports properly after experiment end --- +srv/expServer.m | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/+srv/expServer.m b/+srv/expServer.m index 4748aa69..36ab8078 100644 --- a/+srv/expServer.m +++ b/+srv/expServer.m @@ -44,7 +44,20 @@ function expServer(useTimelineOverride, bgColour) KbQueueStart(); % get rig hardware -rig = hw.devices; +try + rig = hw.devices; +catch ME + fun.applyForce({ + @() communicator.close(),... + @() delete(listener),... + @KbQueueRelease,... + @() Screen('CloseAll'),... + @() PsychPortAudio('Close'),... + @() Priority(0),... %set back to normal priority level + @() PsychPortAudio('Verbosity', oldPpaVerbosity)... + }); + rethrow(ME) +end cleanup = onCleanup(@() fun.applyForce({ @() communicator.close(),... @@ -255,6 +268,7 @@ function handleMessage(id, data, host) communicator.EventMode = false; % back to pull message mode aborted = strcmp(experiment.Data.endStatus, 'aborted'); % clear the active experiment var + experiment.delete() experiment = []; rig.stimWindow.BackgroundColour = bgColour; rig.stimWindow.flip(); % clear the screen after From e5ed26f0cc6876b27203944e0ed79b235a8d85db Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 19 Dec 2018 16:16:10 +0000 Subject: [PATCH 250/393] Removed fieldOrDefault; same functionality as getOr --- +eui/MControl.m | 2 +- cb-tools/burgbox/+img/ImageArray.m | 4 ++-- cb-tools/burgbox/fieldOrDefault.m | 25 ------------------------- signals | 2 +- 4 files changed, 4 insertions(+), 29 deletions(-) delete mode 100644 cb-tools/burgbox/fieldOrDefault.m diff --git a/+eui/MControl.m b/+eui/MControl.m index 3ca11966..5b25075d 100644 --- a/+eui/MControl.m +++ b/+eui/MControl.m @@ -88,7 +88,7 @@ addlistener(obj.AlyxPanel, 'Disconnected', @obj.expSubjectChanged); try if isfield(rig, 'scale') && ~isempty(rig.scale) - obj.WeighingScale = fieldOrDefault(rig, 'scale'); + obj.WeighingScale = getOr(rig, 'scale'); init(obj.WeighingScale); % Add listners for new reading, both for the log tab and also for % the weigh button in the Alyx Panel. diff --git a/cb-tools/burgbox/+img/ImageArray.m b/cb-tools/burgbox/+img/ImageArray.m index 8c0ae1e1..71cb986d 100644 --- a/cb-tools/burgbox/+img/ImageArray.m +++ b/cb-tools/burgbox/+img/ImageArray.m @@ -130,8 +130,8 @@ else %TODO: generate it end - obj.BaseGenerator = fieldOrDefault(s, 'baseGenerator', []); - obj.Transforms = fieldOrDefault(s, 'transforms', []); + obj.BaseGenerator = getOr(s, 'baseGenerator', []); + obj.Transforms = getOr(s, 'transforms', []); end end diff --git a/cb-tools/burgbox/fieldOrDefault.m b/cb-tools/burgbox/fieldOrDefault.m deleted file mode 100644 index f349ba37..00000000 --- a/cb-tools/burgbox/fieldOrDefault.m +++ /dev/null @@ -1,25 +0,0 @@ -function value = fieldOrDefault(v, name, default) -%FIELDORDEFAULT Returns value of a field or a default if non-existent -% V = FIELDORDEFAULT(s, name, [default]) returns the value of the field -% 'name' in 's', or if no such field exists, returns 'default'. If no -% default is passed, [] is used. -% -% This works on structures or class objects (in which case it is the -% named property). -% -% Part of Burgbox - -% 2013-02 CB created - -if nargin < 3 - default = []; -end - -if ~isempty(v) && isfield(v, name) - value = v.(name); -else - value = default; -end - -end - diff --git a/signals b/signals index 1c9127de..dd82909b 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit 1c9127de66f3edc4c2b0063019b3e9045929a21c +Subproject commit dd82909b1f101d94c577890059eb9bba25a9857d From 3b6a32270963c9dc92cc79858dbd3ab2f5d23e58 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 19 Dec 2018 16:27:33 +0000 Subject: [PATCH 251/393] Added ext input to expFilePath --- +dat/expFilePath.m | 84 ++++++++++++++++++++++++++++++---------------- +hw/Timeline.m | 10 +++--- +srv/expServer.m | 3 +- 3 files changed, 62 insertions(+), 35 deletions(-) diff --git a/+dat/expFilePath.m b/+dat/expFilePath.m index b9053047..37f9add7 100644 --- a/+dat/expFilePath.m +++ b/+dat/expFilePath.m @@ -7,88 +7,117 @@ % e.g. to get the paths for an experiments 2 photon TIFF movie: % DAT.EXPFILEPATH('mouse1', datenum(2013, 01, 01), 1, '2p-raw'); % -% [full, filename] = expFilePath(ref, type, [reposlocation]) +% [full, filename] = expFilePath(ref, type, [reposlocation, ext]) % -% [full, filename] = expFilePath(subject, date, seq, type, [reposlocation]) +% [full, filename] = expFilePath(subject, date, seq, type, [reposlocation, ext]) % % Options for reposlocation are: 'local' or 'master' % Many options for type, e.g. 'block', '2p-raw', 'eyetracking', etc +% If ext is specified, the path returned has the extention ext, otherwise +% the default for that type is used. % % Part of Rigbox % 2013-03 CB created -if nargin == 3 || nargin == 5 - % repos argument was passed, save the value and remove from varargin - location = varargin(end); - varargin(end) = []; -elseif nargin < 2 - error('Not enough arguments supplied.'); -else - % repos argument not passed - location = {}; +assert(length(varargin) > 1, 'Error: Not enough arguments supplied.') + +parsed = catStructs(regexp(varargin{1}, dat.expRefRegExp, 'names')); +if isempty(parsed) % Subject, not ref + if nargin > 4 + location = varargin{5}; + varargin(5) = []; + else + location = {}; + end + typeIdx = 4; +else % Ref, not subject + typeIdx = 2; + if nargin > 2 + location = varargin{3}; + varargin(3) = []; + else + location = {}; + end end % tabulate the args to get complete rows [varargin{1:end}, singleArgs] = tabulateArgs(varargin{:}); -% last argument is the file type -fileType = varargin{end}; +fileType = varargin{typeIdx}; +extention = iff(any(numel(varargin) == [3,5]), varargin{end},... + cell(1,length(varargin{1}))); +if any(numel(varargin) == [3,5]); varargin(end) = []; end + % convert file types to file suffixes -[repos, suffix, dateLevel] = mapToCell(@typeInfo, fileType); +[repos, suffix, dateLevel] = mapToCell(@typeInfo, fileType, extention); reposArgs = cat(2, {repos}, location); % and the rest are for the experiment reference [expPath, expRef] = dat.expPath(varargin{1:end - 1}, reposArgs{:}); - function [repos, suff, dateLevel] = typeInfo(type) + function [repos, suff, dateLevel] = typeInfo(type, newExt) % whether this repository is at the date level or otherwise deeper at the sequence % level (default). FIXME: Date level doesn't work, perhaps this should % be modified to work with deeper sequences also? E.g. % default\2018-05-04\1\2 dateLevel = false; repos = 'main'; + ext = '.mat'; switch lower(type) case 'block' % MAT-file with info about each set of trials - suff = '_Block.mat'; + suff = '_Block'; case 'hw-info' % MAT-file with info about the hardware used for an experiment - suff = '_hardwareInfo.mat'; + suff = '_hardwareInfo'; case '2p-raw' % TIFF with 2-photon raw fluorescence movies suff = '_2P.tif'; + ext = '.tif'; case 'calcium-preview' - suff = '_2P_CalciumPreview.tif'; + suff = '_2P_CalciumPreview'; + ext = '.tif'; case 'calcium-reg' suff = '_2P_CalciumReg'; + ext = ''; case 'calcium-regframe' - suff = '_2P_CalciumRegFrame.tif'; + suff = '_2P_CalciumRegFrame'; + ext = '.tif'; case 'timeline' % MAT-file with acquired timing information - suff = '_Timeline.mat'; + suff = '_Timeline'; case 'calcium-roi' - suff = '_ROI.mat'; + suff = '_ROI'; case 'calcium-fc' % minimally filtered fractional change frames suff = '_2P_CalciumFC'; + ext = ''; case 'calcium-ffc' % ROI filtered fractional change frames suff = '_2P_CalciumFFC'; + ext = ''; case 'calcium-widefield-svd' suff = '_SVD'; + ext = ''; case 'eyetracking' suff = '_eye'; + ext = ''; case 'parameters' % MAT-file with parameters used for experiment - suff = '_parameters.mat'; + suff = '_parameters'; case 'lasermanip' - suff = '_laserManip.mat'; + suff = '_laserManip'; case 'img-info' - suff = '_imgInfo.mat'; + suff = '_imgInfo'; case 'tmaze' - suff = '_TMaze.mat'; + suff = '_TMaze'; case 'expdeffun' - suff = '_expDef.m'; + suff = '_expDef'; + ext = '.m'; case 'svdspatialcomps' dateLevel = true; otherwise error('"%s" is not a valid file type', type); end + % Append extention to suffix + ext = iff(isempty(newExt)&&~ischar(newExt), ext, newExt); + suff = iff((isempty(ext)&&ischar(ext))||(~isempty(ext)&&ext(1)=='.'),... + [suff, ext], [suff, '.', ext]); end % generate a filename for each experiment @@ -103,5 +132,4 @@ filename = filename{1}; end -end - +end \ No newline at end of file diff --git a/+hw/Timeline.m b/+hw/Timeline.m index 020a96c2..59cee5ba 100644 --- a/+hw/Timeline.m +++ b/+hw/Timeline.m @@ -57,6 +57,7 @@ % - In future could implement option to only write to disk to avoid % memory limitations when aquiring a lot of data % - Delete local binary files once timeline has successfully saved to zserver? +% - save par file in json instead % % See also HW.TIMELINECLOCK, HW.TLOUTPUT % @@ -83,7 +84,7 @@ AquiredDataType = 'double' % default data type for the acquired data array (i.e. Data.rawDAQData) UseTimeline = false % used by expServer. If true, timeline is started by default (otherwise can be toggled with the t key) LivePlot = false % if true the data are plotted as the data are aquired - FigureScale = []; % figure position in normalized units, default is [0 0 1 1] (full screen) + FigureScale = [0 0 1 1]; % figure position in normalized units, default is full screen WriteBufferToDisk = false % if true the data buffer is written to disk as they're aquired NB: in the future this will happen by default end @@ -155,9 +156,9 @@ function start(obj, expRef, ai) %find the local path to save the data to file during aquisition if obj.WriteBufferToDisk fprintf(1, 'opening binary file for writing\n'); - localPath = dat.expFilePath(expRef, 'timeline', 'local'); % get the local exp data path + localPath = dat.expFilePath(expRef, 'timeline', 'local', 'dat'); % get the local exp data path if ~dat.expExists(expRef); mkdir(fileparts(localPath)); end % if the folder doesn't exist, create it - obj.DataFID = fopen([localPath(1:end-4) '.dat'], 'w'); % open a binary data file + obj.DataFID = fopen(localPath, 'w'); % open a binary data file % save params now so if things crash later you at least have this record of the data type and size so you can load the dat parfid = fopen([localPath(1:end-4) '.par'], 'w'); % open a parameter file fprintf(parfid, 'type = %s\n', obj.AquiredDataType); % record the data type @@ -604,7 +605,6 @@ function livePlot(obj, data) % TL.LIVEPLOT(source, event) plots the data aquired by the % DAQ while the PlotLive property is true. if isempty(obj.Axes) - %f = figure('Units', 'Normalized', 'Position', [0 0 1 1]); % create a figure for plotting aquired data f = figure('Units', 'Normalized'); obj.Axes = gca; % store a handle to the axes if isprop(obj, 'FigurePosition') && ~isempty(obj.FigurePosition) @@ -665,4 +665,4 @@ function livePlot(obj, data) end end end -end \ No newline at end of file +end diff --git a/+srv/expServer.m b/+srv/expServer.m index 4748aa69..79878279 100644 --- a/+srv/expServer.m +++ b/+srv/expServer.m @@ -260,8 +260,7 @@ function handleMessage(id, data, host) rig.stimWindow.flip(); % clear the screen after % save a copy of the hardware in JSON - hwInfo = strrep(dat.expFilePath(expRef, 'hw-info', 'master'), '.mat', '.json'); - fid = fopen(hwInfo, 'w'); + fid = fopen(dat.expFilePath(expRef, 'hw-info', 'master', 'json'), 'w'); fprintf(fid, '%s', obj2json(rig)); fclose(fid); if ~strcmp(dat.parseExpRef(expRef), 'default') From 3169f66a0bec0e1a72d7b67d5dd1f10485ad5231 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 19 Dec 2018 18:46:05 +0000 Subject: [PATCH 252/393] bug fix: variable required transpose --- +dat/expFilePath.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/+dat/expFilePath.m b/+dat/expFilePath.m index 37f9add7..20310348 100644 --- a/+dat/expFilePath.m +++ b/+dat/expFilePath.m @@ -50,7 +50,7 @@ if any(numel(varargin) == [3,5]); varargin(end) = []; end % convert file types to file suffixes -[repos, suffix, dateLevel] = mapToCell(@typeInfo, fileType, extention); +[repos, suffix, dateLevel] = mapToCell(@typeInfo, fileType(:), extention(:)); reposArgs = cat(2, {repos}, location); From fcd6dcb0fc03cadcd65d29503a20c666e92c0ffc Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 9 Jan 2019 15:12:09 +0000 Subject: [PATCH 253/393] Checks for local folder rather than remote when creating new dir for buffer --- +hw/Timeline.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/+hw/Timeline.m b/+hw/Timeline.m index 59cee5ba..c97756c2 100644 --- a/+hw/Timeline.m +++ b/+hw/Timeline.m @@ -157,7 +157,7 @@ function start(obj, expRef, ai) if obj.WriteBufferToDisk fprintf(1, 'opening binary file for writing\n'); localPath = dat.expFilePath(expRef, 'timeline', 'local', 'dat'); % get the local exp data path - if ~dat.expExists(expRef); mkdir(fileparts(localPath)); end % if the folder doesn't exist, create it + if ~exist(fileparts(localPath),'dir'); mkdir(fileparts(localPath)); end % if the folder doesn't exist, create it obj.DataFID = fopen(localPath, 'w'); % open a binary data file % save params now so if things crash later you at least have this record of the data type and size so you can load the dat parfid = fopen([localPath(1:end-4) '.par'], 'w'); % open a parameter file From ddd772bf8336ec711ef225091e7b05af1a2e2a8e Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 10 Jan 2019 21:51:22 +0200 Subject: [PATCH 254/393] Auto cleanup of WeekendWater profile; changes to comments --- +eui/AlyxPanel.m | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index 458293e0..8ed2241a 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -142,14 +142,14 @@ 'String', 'Manual weighing', ... 'Enable', 'off',... 'Callback', @(~,~)obj.recordWeight); - % Button to launch dialog for submitting gel administrations + % Button to launch dialog for submitting water administrations % for future dates uicontrol('Parent', waterbox,... 'Style', 'pushbutton', ... 'String', 'Give water in future', ... 'Enable', 'off',... 'Callback', @(~,~)obj.giveFutureWater); - % Check box to indicate whether water was gel or liquid + % Dropdown to indicate water type (sucrose, gel, etc.) obj.WaterType = uicontrol('Parent', waterbox,... 'Style', 'popupmenu', ... 'String', {'Water'}, ... @@ -328,6 +328,11 @@ function giveFutureWater(obj) delim = iff(size(days,1) < 3, ' and ', {', ', ' and '}); obj.log('%s marked for training on %s',... obj.Subject, strjoin(strtrim(string(days)), delim)); + else % If no training dates given, delete from structure + try + dat.delParamProfile('WeekendWater', obj.Subject); + catch % Subject field may not exist is never marked for training + end end futWtrDates = futDates(amt > 0); % future water giving dates From 24ce0fde5c354dbfb21769059cf0bae953b9ea29 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 11 Jan 2019 13:13:19 +0000 Subject: [PATCH 255/393] Clearer plots and tables in view subject history --- +eui/AlyxPanel.m | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index 8ed2241a..f8b6da14 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -456,11 +456,11 @@ function recordWeight(obj, weight, subject) try w = postWeight(ai, weight, subject); obj.log('Alyx weight posting succeeded: %.2f for %s', w.weight, w.subject); - catch + catch ex if ~ai.IsLoggedIn % if not logged in, save the weight for later obj.log('Warning: Weight not posted to Alyx; will be posted upon login.'); else - obj.log('Warning: Alyx weight posting failed!'); + obj.log('Warning: Alyx weight posting failed! %s', ex.message); end end % Update weight and refresh login timer @@ -554,7 +554,7 @@ function viewSubjectHistory(obj, ax) return end expected = [records.expected_weight]; - expected(expected==0|cellfun('isempty',{records.weighing_at})) = nan; + expected(expected==0|isnan([records.weighing_at])) = nan; dates = cellfun(@(x)datenum(x), {records.date}); % build the figure to show it @@ -567,13 +567,18 @@ function viewSubjectHistory(obj, ax) ax = axes('Parent', plotBox); end - plot(ax, dates, [records.weight], '.-'); + plot(ax, dates, [records.weighing_at], '.-'); hold(ax, 'on'); plot(ax, dates, ((expected-iw)*0.7)+iw, 'r', 'LineWidth', 2.0); plot(ax, dates, ((expected-iw)*0.8)+iw, 'LineWidth', 2.0, 'Color', [244, 191, 66]/255); box(ax, 'off'); % Change the plot x axis limits - if numel(dates) > 1; xlim(ax, [min(dates) max(dates)]); end + maxDate = max(dates([records.is_water_restricted]|~isnan([records.weighing_at]))); + if numel(dates) > 1 && ~isempty(maxDate) + xlim(ax, [min(dates) maxDate]) + else + maxDate = now; + end if nargin == 1 set(ax, 'XTickLabel', arrayfun(@(x)datestr(x, 'dd-mmm'), get(ax, 'XTick'), 'uni', false)) else @@ -584,23 +589,23 @@ function viewSubjectHistory(obj, ax) if nargin==1 ax = axes('Parent', plotBox); - plot(ax, dates, ([records.weight]-iw)./(expected-iw), '.-'); + plot(ax, dates, ([records.weighing_at]-iw)./(expected-iw), '.-'); hold(ax, 'on'); plot(ax, dates, 0.7*ones(size(dates)), 'r', 'LineWidth', 2.0); plot(ax, dates, 0.8*ones(size(dates)), 'LineWidth', 2.0, 'Color', [244, 191, 66]/255); box(ax, 'off'); - xlim(ax, [min(dates) max(dates)]); + xlim(ax, [min(dates) maxDate]); set(ax, 'XTickLabel', arrayfun(@(x)datestr(x, 'dd-mmm'), get(ax, 'XTick'), 'uni', false)) ylabel(ax, 'weight as pct (%)'); axWater = axes('Parent',plotBox); - plot(axWater, dates, obj.round([records.given_water_liquid]+[records.given_water_hydrogel], 'up'), '.-'); + plot(axWater, dates, obj.round([records.given_water_total], 'up'), '.-'); hold(axWater, 'on'); plot(axWater, dates, obj.round([records.given_water_hydrogel], 'down'), '.-'); plot(axWater, dates, obj.round([records.given_water_liquid], 'down'), '.-'); plot(axWater, dates, obj.round([records.expected_water], 'up'), 'r', 'LineWidth', 2.0); box(axWater, 'off'); - xlim(axWater, [min(dates) max(dates)]); + xlim(axWater, [min(dates) maxDate]); set(axWater, 'XTickLabel', arrayfun(@(x)datestr(x, 'dd-mmm'), get(axWater, 'XTick'), 'uni', false)) ylabel(axWater, 'water/hydrogel (mL)'); @@ -609,23 +614,24 @@ function viewSubjectHistory(obj, ax) histTable = uitable('Parent', histbox,... 'FontName', 'Consolas',... 'RowName', []); - weightsByDate = num2cell([records.weight]); + weightsByDate = num2cell([records.weighing_at]); weightsByDate = cellfun(@(x)sprintf('%.1f', x), weightsByDate, 'uni', false); - weightsByDate(isnan([records.weight])) = {[]}; - weightPctByDate = num2cell(([records.weight]-iw)./(expected-iw)); + weightsByDate(isnan([records.weighing_at])) = {[]}; + weightPctByDate = num2cell(([records.weighing_at]-iw)./(expected-iw)); weightPctByDate = cellfun(@(x)sprintf('%.1f', x*100), weightPctByDate, 'uni', false); - weightPctByDate(isnan([records.weight])) = {[]}; + weightPctByDate(isnan([records.weighing_at])|~[records.is_water_restricted]) = {[]}; dat = horzcat(... arrayfun(@(x)datestr(x), dates', 'uni', false), ... weightsByDate', ... - arrayfun(@(x)sprintf('%.1f', 0.8*(x-iw)+iw), [records.expected_weight]', 'uni', false), ... + arrayfun(@(x)iff(isnan(x), [], @()sprintf('%.1f', 0.8*(x-iw)+iw)), expected', 'uni', false), ... weightPctByDate'); waterDat = (... num2cell(horzcat([records.given_water_liquid]', [records.given_water_hydrogel]', ... - [records.given_water_liquid]'+[records.given_water_hydrogel]', [records.expected_water]',... - [records.given_water_liquid]'+[records.given_water_hydrogel]'-[records.expected_water]'))); + [records.given_water_total]', [records.expected_water]',... + [records.given_water_total]'-[records.expected_water]'))); waterDat = cellfun(@(x)sprintf('%.2f', x), waterDat, 'uni', false); + waterDat(~[records.is_water_restricted],[1,3]) = {'ad lib'}; dat = horzcat(dat, waterDat); set(histTable, 'ColumnName', {'date', 'meas. weight', '80% weight', 'weight pct', 'water', 'hydrogel', 'total', 'min water', 'excess'}, ... From b64f90bd4eccfd08ebd917d757c54922cea10457 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 11 Jan 2019 13:13:51 +0000 Subject: [PATCH 256/393] Update to submodule; can't post zero weights --- alyx-matlab | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alyx-matlab b/alyx-matlab index 11038d22..df90cdae 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit 11038d22c42f96c0408027c7bbe3f0939f79028a +Subproject commit df90cdae00175b43aa769a3d24fc6f8b87828e03 From 77febac0f851b3a9ebc88f9cbac7e578bbfe006c Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 11 Jan 2019 13:13:19 +0000 Subject: [PATCH 257/393] Clearer plots and tables in view subject history --- +eui/AlyxPanel.m | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index 9f2e83dc..5fa174f7 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -449,11 +449,11 @@ function recordWeight(obj, weight, subject) try w = postWeight(ai, weight, subject); obj.log('Alyx weight posting succeeded: %.2f for %s', w.weight, w.subject); - catch + catch ex if ~ai.IsLoggedIn % if not logged in, save the weight for later obj.log('Warning: Weight not posted to Alyx; will be posted upon login.'); else - obj.log('Warning: Alyx weight posting failed!'); + obj.log('Warning: Alyx weight posting failed! %s', ex.message); end end % Update weight and refresh login timer @@ -548,7 +548,7 @@ function viewSubjectHistory(obj, ax) return end expected = [records.expected_weight]; - expected(expected==0) = nan; + expected(expected==0|isnan([records.weighing_at])) = nan; dates = cellfun(@(x)datenum(x), {records.date}); % build the figure to show it @@ -561,13 +561,18 @@ function viewSubjectHistory(obj, ax) ax = axes('Parent', plotBox); end - plot(ax, dates, [records.weight], '.-'); + plot(ax, dates, [records.weighing_at], '.-'); hold(ax, 'on'); plot(ax, dates, ((expected-iw)*0.7)+iw, 'r', 'LineWidth', 2.0); plot(ax, dates, ((expected-iw)*0.8)+iw, 'LineWidth', 2.0, 'Color', [244, 191, 66]/255); box(ax, 'off'); % Change the plot x axis limits - if numel(dates) > 1; xlim(ax, [min(dates) max(dates)]); end + maxDate = max(dates([records.is_water_restricted]|~isnan([records.weighing_at]))); + if numel(dates) > 1 && ~isempty(maxDate) + xlim(ax, [min(dates) maxDate]) + else + maxDate = now; + end if nargin == 1 set(ax, 'XTickLabel', arrayfun(@(x)datestr(x, 'dd-mmm'), get(ax, 'XTick'), 'uni', false)) else @@ -578,23 +583,23 @@ function viewSubjectHistory(obj, ax) if nargin==1 ax = axes('Parent', plotBox); - plot(ax, dates, ([records.weight]-iw)./(expected-iw), '.-'); + plot(ax, dates, ([records.weighing_at]-iw)./(expected-iw), '.-'); hold(ax, 'on'); plot(ax, dates, 0.7*ones(size(dates)), 'r', 'LineWidth', 2.0); plot(ax, dates, 0.8*ones(size(dates)), 'LineWidth', 2.0, 'Color', [244, 191, 66]/255); box(ax, 'off'); - xlim(ax, [min(dates) max(dates)]); + xlim(ax, [min(dates) maxDate]); set(ax, 'XTickLabel', arrayfun(@(x)datestr(x, 'dd-mmm'), get(ax, 'XTick'), 'uni', false)) ylabel(ax, 'weight as pct (%)'); axWater = axes('Parent',plotBox); - plot(axWater, dates, obj.round([records.given_water_liquid]+[records.given_water_hydrogel], 'up'), '.-'); + plot(axWater, dates, obj.round([records.given_water_total], 'up'), '.-'); hold(axWater, 'on'); plot(axWater, dates, obj.round([records.given_water_hydrogel], 'down'), '.-'); plot(axWater, dates, obj.round([records.given_water_liquid], 'down'), '.-'); plot(axWater, dates, obj.round([records.expected_water], 'up'), 'r', 'LineWidth', 2.0); box(axWater, 'off'); - xlim(axWater, [min(dates) max(dates)]); + xlim(axWater, [min(dates) maxDate]); set(axWater, 'XTickLabel', arrayfun(@(x)datestr(x, 'dd-mmm'), get(axWater, 'XTick'), 'uni', false)) ylabel(axWater, 'water/hydrogel (mL)'); @@ -603,23 +608,24 @@ function viewSubjectHistory(obj, ax) histTable = uitable('Parent', histbox,... 'FontName', 'Consolas',... 'RowName', []); - weightsByDate = num2cell([records.weight]); + weightsByDate = num2cell([records.weighing_at]); weightsByDate = cellfun(@(x)sprintf('%.1f', x), weightsByDate, 'uni', false); - weightsByDate(isnan([records.weight])) = {[]}; - weightPctByDate = num2cell(([records.weight]-iw)./(expected-iw)); + weightsByDate(isnan([records.weighing_at])) = {[]}; + weightPctByDate = num2cell(([records.weighing_at]-iw)./(expected-iw)); weightPctByDate = cellfun(@(x)sprintf('%.1f', x*100), weightPctByDate, 'uni', false); - weightPctByDate(isnan([records.weight])) = {[]}; + weightPctByDate(isnan([records.weighing_at])|~[records.is_water_restricted]) = {[]}; dat = horzcat(... arrayfun(@(x)datestr(x), dates', 'uni', false), ... weightsByDate', ... - arrayfun(@(x)sprintf('%.1f', 0.8*(x-iw)+iw), [records.expected_weight]', 'uni', false), ... + arrayfun(@(x)iff(isnan(x), [], @()sprintf('%.1f', 0.8*(x-iw)+iw)), expected', 'uni', false), ... weightPctByDate'); waterDat = (... num2cell(horzcat([records.given_water_liquid]', [records.given_water_hydrogel]', ... - [records.given_water_liquid]'+[records.given_water_hydrogel]', [records.expected_water]',... - [records.given_water_liquid]'+[records.given_water_hydrogel]'-[records.expected_water]'))); + [records.given_water_total]', [records.expected_water]',... + [records.given_water_total]'-[records.expected_water]'))); waterDat = cellfun(@(x)sprintf('%.2f', x), waterDat, 'uni', false); + waterDat(~[records.is_water_restricted],[1,3]) = {'ad lib'}; dat = horzcat(dat, waterDat); set(histTable, 'ColumnName', {'date', 'meas. weight', '80% weight', 'weight pct', 'water', 'hydrogel', 'total', 'min water', 'excess'}, ... From c7e758a3b316448935bc69a7bf7d15b0630f6835 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 11 Jan 2019 15:18:44 +0000 Subject: [PATCH 258/393] Update to submodules: added params to subsession JSON field --- alyx-matlab | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alyx-matlab b/alyx-matlab index df90cdae..6fc933b9 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit df90cdae00175b43aa769a3d24fc6f8b87828e03 +Subproject commit 6fc933b99bec09689a83b024284e8023d2c5793d From c193c609b199455316c839ec2e828395f130cb4b Mon Sep 17 00:00:00 2001 From: ArminLak <arminlak@gmail.com> Date: Tue, 15 Jan 2019 14:07:18 +0000 Subject: [PATCH 259/393] updated submodule signals --- signals | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/signals b/signals index dd82909b..897da00e 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit dd82909b1f101d94c577890059eb9bba25a9857d +Subproject commit 897da00ee217874db2f271b9dbad42886eb4ff7d From 56dd8b954888e2343ca938da3b72dadb222bb165 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Sun, 20 Jan 2019 18:04:03 +0200 Subject: [PATCH 260/393] Moved Signals vis functions from cortexlab directory --- cortexlab/+vis/checker4.m | 126 ---------------------------------- cortexlab/+vis/checker5.m | 126 ---------------------------------- cortexlab/+vis/checker6.m | 126 ---------------------------------- cortexlab/+vis/checkerLeft.m | 126 ---------------------------------- cortexlab/+vis/checkerRight.m | 126 ---------------------------------- signals | 2 +- 6 files changed, 1 insertion(+), 631 deletions(-) delete mode 100644 cortexlab/+vis/checker4.m delete mode 100644 cortexlab/+vis/checker5.m delete mode 100644 cortexlab/+vis/checker6.m delete mode 100644 cortexlab/+vis/checkerLeft.m delete mode 100644 cortexlab/+vis/checkerRight.m diff --git a/cortexlab/+vis/checker4.m b/cortexlab/+vis/checker4.m deleted file mode 100644 index be837609..00000000 --- a/cortexlab/+vis/checker4.m +++ /dev/null @@ -1,126 +0,0 @@ -function elem = checker3(t) -%vis.checker A grid of rectangles -% Detailed explanation goes here - -elem = t.Node.Net.subscriptableOrigin('checker'); - -%% make initial layers to be used as templates -maskTemplate = vis.emptyLayer(); -maskTemplate.isPeriodic = false; -maskTemplate.interpolation = 'nearest'; -maskTemplate.show = true; -maskTemplate.colourMask = [false false false true]; - -maskTemplate.textureId = 'checkerMaskPixel'; -[maskTemplate.rgba, maskTemplate.rgbaSize] = vis.rgba(0, 0); -maskTemplate.blending = '1-source'; % allows us to lay down our zero alpha value - -stencilTemplate = maskTemplate; -stencilTemplate.textureId = 'checkerStencilPixel'; -[stencilTemplate.rgba, stencilTemplate.rgbaSize] = vis.rgba(1, 1); -stencilTemplate.blending = 'none'; - -% pattern layer uses the alpha values laid down by mask layers -patternLayer = vis.emptyLayer(); -patternLayer.textureId = sprintf('~checker%i', randi(2^32)); -patternLayer.isPeriodic = false; -patternLayer.interpolation = 'nearest'; -patternLayer.blending = 'destination'; % use the alpha mask gets laid down before this - -%% construct signals used to assemble layers -% N rows by cols signal is derived from the size of the pattern array but -% we skip repeats so that pattern changes don't update the mask layers -% unless the size has acutally changed -nRowsByCols = elem.pattern.flatten().map(@size).skipRepeats(); -aziRange = elem.azimuthRange.flatten(); -altRange = elem.altitudeRange.flatten(); -sizeFrac = elem.rectSizeFrac.flatten(); -% signal containing the masking layers -gridMaskLayers = mapn(nRowsByCols, aziRange, altRange, sizeFrac, ... - maskTemplate, stencilTemplate, @gridMask); -% signal contain the checker layer -checkerLayer = scan(elem.pattern.flatten(), @updatePattern,... - elem.colour.flatten(), @updateColour,... - elem.azimuthRange.flatten(), @updateAzi,... - elem.altitudeRange.flatten(), @updateAlt,... - elem.show.flatten(), @updateShow,... - patternLayer); % initial value -%% set default attribute values -elem.layers = [gridMaskLayers checkerLayer]; -elem.azimuthRange = [-132 132]; -elem.altitudeRange = [-36 36]; -elem.rectSizeFrac = [1 1]; % horizontal and vertical size of each rectangle -elem.pattern = [ - 1 -1 1 -1 - -1 0 0 0 - 1 0 0 0 - -1 1 -1 1]; - elem.show = true; -end - -%% helper functions -function layer = updatePattern(layer, pattern) -% map pattern from -1 -> 1 range to 0->255, cast to 8 bit integers, then -% convert to RGBA texture format. -[layer.rgba, layer.rgbaSize] = vis.rgbaFromUint8(uint8(127.5*(1 + pattern)), 1); -end - -function layer = updateColour(layer, colour) -layer.maxColour = [colour 1]; -end - -function layer = updateAzi(layer, aziRange) -layer.size(1) = abs(diff(aziRange)); -layer.texOffset(1) = mean(aziRange); -end - -function layer = updateAlt(layer, altRange) -layer.size(2) = abs(diff(altRange)); -layer.texOffset(2) = mean(altRange); -end - -function layer = updateShow(layer, show) -layer.show = show; -end - -function layers = gridMask(nRowsByCols, aziRange, altRange, sizeFrac, mask, stencil) -gridDims = [abs(diff(aziRange)) abs(diff(altRange))]; -cellSize = gridDims./flip(nRowsByCols); -nCols = nRowsByCols(2) + 1; -nRows = nRowsByCols(1) + 1; -midAzi = mean(aziRange); -midAlt = mean(altRange); -%% base layer to imprint area the checker can draw on (by applying an alpha mask) -stencil.texOffset = [midAzi midAlt]; -stencil.size = gridDims; -if any(sizeFrac < 1) - %% layers for lines making up mask grid - masks out margins around each square - % make layers for vertical lines - if nCols > 1 - azi = linspace(aziRange(1), aziRange(2), nCols); - else - azi = midAzi; - end - collayers = repmat(mask, 1, nCols); - for vi = 1:nCols - collayers(vi).texOffset = [azi(vi) midAlt]; - end - [collayers.size] = deal([(1 - sizeFrac(1))*cellSize(1) gridDims(2)]); - % make layers for horizontal lines - if nRows > 1 - alt = linspace(altRange(1), altRange(2), nRows); - else - alt = midAlt; - end - rowlayers = repmat(mask, 1, nRows); - for hi = 1:nRows - rowlayers(hi).texOffset = [midAzi alt(hi)]; - end - [rowlayers.size] = deal([gridDims(1) (1 - sizeFrac(2))*cellSize(2)]); - %% combine the layers and return - layers = [stencil collayers rowlayers]; -else % no mask grid needed as each cell is full size - layers = stencil; -end - -end \ No newline at end of file diff --git a/cortexlab/+vis/checker5.m b/cortexlab/+vis/checker5.m deleted file mode 100644 index be37a1f4..00000000 --- a/cortexlab/+vis/checker5.m +++ /dev/null @@ -1,126 +0,0 @@ -function elem = checker5(t) -%vis.checker A grid of rectangles -% Detailed explanation goes here - -elem = t.Node.Net.subscriptableOrigin('checker'); - -%% make initial layers to be used as templates -maskTemplate = vis.emptyLayer(); -maskTemplate.isPeriodic = false; -maskTemplate.interpolation = 'nearest'; -maskTemplate.show = true; -maskTemplate.colourMask = [false false false true]; - -maskTemplate.textureId = 'checkerMaskPixel'; -[maskTemplate.rgba, maskTemplate.rgbaSize] = vis.rgba(0, 0); -maskTemplate.blending = '1-source'; % allows us to lay down our zero alpha value - -stencilTemplate = maskTemplate; -stencilTemplate.textureId = 'checkerStencilPixel'; -[stencilTemplate.rgba, stencilTemplate.rgbaSize] = vis.rgba(1, 1); -stencilTemplate.blending = 'none'; - -% pattern layer uses the alpha values laid down by mask layers -patternLayer = vis.emptyLayer(); -patternLayer.textureId = sprintf('~checker%i', randi(2^32)); -patternLayer.isPeriodic = false; -patternLayer.interpolation = 'nearest'; -patternLayer.blending = 'destination'; % use the alpha mask gets laid down before this - -%% construct signals used to assemble layers -% N rows by cols signal is derived from the size of the pattern array but -% we skip repeats so that pattern changes don't update the mask layers -% unless the size has acutally changed -nRowsByCols = elem.pattern.flatten().map(@size).skipRepeats(); -aziRange = elem.azimuthRange.flatten(); -altRange = elem.altitudeRange.flatten(); -sizeFrac = elem.rectSizeFrac.flatten(); -% signal containing the masking layers -gridMaskLayers = mapn(nRowsByCols, aziRange, altRange, sizeFrac, ... - maskTemplate, stencilTemplate, @gridMask); -% signal contain the checker layer -checkerLayer = scan(elem.pattern.flatten(), @updatePattern,... - elem.colour.flatten(), @updateColour,... - elem.azimuthRange.flatten(), @updateAzi,... - elem.altitudeRange.flatten(), @updateAlt,... - elem.show.flatten(), @updateShow,... - patternLayer); % initial value -%% set default attribute values -elem.layers = [gridMaskLayers checkerLayer]; -elem.azimuthRange = [-132 132]; -elem.altitudeRange = [-36 36]; -elem.rectSizeFrac = [1 1]; % horizontal and vertical size of each rectangle -elem.pattern = [ - 1 -1 1 -1 - -1 0 0 0 - 1 0 0 0 - -1 1 -1 1]; - elem.show = true; -end - -%% helper functions -function layer = updatePattern(layer, pattern) -% map pattern from -1 -> 1 range to 0->255, cast to 8 bit integers, then -% convert to RGBA texture format. -[layer.rgba, layer.rgbaSize] = vis.rgbaFromUint8_PC(uint8(127.5*(1 + pattern)), 1); -end - -function layer = updateColour(layer, colour) -layer.maxColour = [colour 1]; -end - -function layer = updateAzi(layer, aziRange) -layer.size(1) = abs(diff(aziRange)); -layer.texOffset(1) = mean(aziRange); -end - -function layer = updateAlt(layer, altRange) -layer.size(2) = abs(diff(altRange)); -layer.texOffset(2) = mean(altRange); -end - -function layer = updateShow(layer, show) -layer.show = show; -end - -function layers = gridMask(nRowsByCols, aziRange, altRange, sizeFrac, mask, stencil) -gridDims = [abs(diff(aziRange)) abs(diff(altRange))]; -cellSize = gridDims./fliplr(nRowsByCols); -nCols = nRowsByCols(2) + 1; -nRows = nRowsByCols(1) + 1; -midAzi = mean(aziRange); -midAlt = mean(altRange); -%% base layer to imprint area the checker can draw on (by applying an alpha mask) -stencil.texOffset = [midAzi midAlt]; -stencil.size = gridDims; -if any(sizeFrac < 1) - %% layers for lines making up mask grid - masks out margins around each square - % make layers for vertical lines - if nCols > 1 - azi = linspace(aziRange(1), aziRange(2), nCols); - else - azi = midAzi; - end - collayers = repmat(mask, 1, nCols); - for vi = 1:nCols - collayers(vi).texOffset = [azi(vi) midAlt]; - end - [collayers.size] = deal([(1 - sizeFrac(1))*cellSize(1) gridDims(2)]); - % make layers for horizontal lines - if nRows > 1 - alt = linspace(altRange(1), altRange(2), nRows); - else - alt = midAlt; - end - rowlayers = repmat(mask, 1, nRows); - for hi = 1:nRows - rowlayers(hi).texOffset = [midAzi alt(hi)]; - end - [rowlayers.size] = deal([gridDims(1) (1 - sizeFrac(2))*cellSize(2)]); - %% combine the layers and return - layers = [stencil collayers rowlayers]; -else % no mask grid needed as each cell is full size - layers = stencil; -end - -end \ No newline at end of file diff --git a/cortexlab/+vis/checker6.m b/cortexlab/+vis/checker6.m deleted file mode 100644 index 1733d8f7..00000000 --- a/cortexlab/+vis/checker6.m +++ /dev/null @@ -1,126 +0,0 @@ -function elem = checker6(t) -%vis.checker A grid of rectangles -% Detailed explanation goes here - -elem = t.Node.Net.subscriptableOrigin('checker'); - -%% make initial layers to be used as templates -maskTemplate = vis.emptyLayer(); -maskTemplate.isPeriodic = false; -maskTemplate.interpolation = 'nearest'; -maskTemplate.show = true; -maskTemplate.colourMask = [false false false true]; - -maskTemplate.textureId = 'checkerMaskPixel'; -[maskTemplate.rgba, maskTemplate.rgbaSize] = vis.rgba(0, 0); -maskTemplate.blending = '1-source'; % allows us to lay down our zero alpha value - -stencilTemplate = maskTemplate; -stencilTemplate.textureId = 'checkerStencilPixel'; -[stencilTemplate.rgba, stencilTemplate.rgbaSize] = vis.rgba(1, 1); -stencilTemplate.blending = 'none'; - -% pattern layer uses the alpha values laid down by mask layers -patternLayer = vis.emptyLayer(); -patternLayer.textureId = sprintf('~checker%i', randi(2^32)); -patternLayer.isPeriodic = false; -patternLayer.interpolation = 'nearest'; -patternLayer.blending = 'destination'; % use the alpha mask gets laid down before this - -%% construct signals used to assemble layers -% N rows by cols signal is derived from the size of the pattern array but -% we skip repeats so that pattern changes don't update the mask layers -% unless the size has acutally changed -nRowsByCols = elem.pattern.flatten().map(@size).skipRepeats(); -aziRange = elem.azimuthRange.flatten(); -altRange = elem.altitudeRange.flatten(); -sizeFrac = elem.rectSizeFrac.flatten(); -% signal containing the masking layers -gridMaskLayers = mapn(nRowsByCols, aziRange, altRange, sizeFrac, ... - maskTemplate, stencilTemplate, @gridMask); -% signal contain the checker layer -checkerLayer = scan(elem.pattern.flatten(), @updatePattern,... - elem.colour.flatten(), @updateColour,... - elem.azimuthRange.flatten(), @updateAzi,... - elem.altitudeRange.flatten(), @updateAlt,... - elem.show.flatten(), @updateShow,... - patternLayer); % initial value -%% set default attribute values -elem.layers = [gridMaskLayers checkerLayer]; -elem.azimuthRange = [-135 135]; -elem.altitudeRange = [-37.5 37.5]; -elem.rectSizeFrac = [1 1]; % horizontal and vertical size of each rectangle -elem.pattern = [ - 1 -1 1 -1 - -1 0 0 0 - 1 0 0 0 - -1 1 -1 1]; - elem.show = true; -end - -%% helper functions -function layer = updatePattern(layer, pattern) -% map pattern from -1 -> 1 range to 0->255, cast to 8 bit integers, then -% convert to RGBA texture format. -[layer.rgba, layer.rgbaSize] = vis.rgbaFromUint8(uint8(127.5*(1 + pattern)), 1); -end - -function layer = updateColour(layer, colour) -layer.maxColour = [colour 1]; -end - -function layer = updateAzi(layer, aziRange) -layer.size(1) = abs(diff(aziRange)); -layer.texOffset(1) = mean(aziRange); -end - -function layer = updateAlt(layer, altRange) -layer.size(2) = abs(diff(altRange)); -layer.texOffset(2) = mean(altRange); -end - -function layer = updateShow(layer, show) -layer.show = show; -end - -function layers = gridMask(nRowsByCols, aziRange, altRange, sizeFrac, mask, stencil) -gridDims = [abs(diff(aziRange)) abs(diff(altRange))]; -cellSize = gridDims./flip(nRowsByCols); -nCols = nRowsByCols(2) + 1; -nRows = nRowsByCols(1) + 1; -midAzi = mean(aziRange); -midAlt = mean(altRange); -%% base layer to imprint area the checker can draw on (by applying an alpha mask) -stencil.texOffset = [midAzi midAlt]; -stencil.size = gridDims; -if any(sizeFrac < 1) - %% layers for lines making up mask grid - masks out margins around each square - % make layers for vertical lines - if nCols > 1 - azi = linspace(aziRange(1), aziRange(2), nCols); - else - azi = midAzi; - end - collayers = repmat(mask, 1, nCols); - for vi = 1:nCols - collayers(vi).texOffset = [azi(vi) midAlt]; - end - [collayers.size] = deal([(1 - sizeFrac(1))*cellSize(1) gridDims(2)]); - % make layers for horizontal lines - if nRows > 1 - alt = linspace(altRange(1), altRange(2), nRows); - else - alt = midAlt; - end - rowlayers = repmat(mask, 1, nRows); - for hi = 1:nRows - rowlayers(hi).texOffset = [midAzi alt(hi)]; - end - [rowlayers.size] = deal([gridDims(1) (1 - sizeFrac(2))*cellSize(2)]); - %% combine the layers and return - layers = [stencil collayers rowlayers]; -else % no mask grid needed as each cell is full size - layers = stencil; -end - -end \ No newline at end of file diff --git a/cortexlab/+vis/checkerLeft.m b/cortexlab/+vis/checkerLeft.m deleted file mode 100644 index cf1122d7..00000000 --- a/cortexlab/+vis/checkerLeft.m +++ /dev/null @@ -1,126 +0,0 @@ -function elem = checkerLeft(t) -%vis.checker A grid of rectangles -% Detailed explanation goes here - -elem = t.Node.Net.subscriptableOrigin('checker'); - -%% make initial layers to be used as templates -maskTemplate = vis.emptyLayer(); -maskTemplate.isPeriodic = false; -maskTemplate.interpolation = 'nearest'; -maskTemplate.show = true; -maskTemplate.colourMask = [false false false true]; - -maskTemplate.textureId = 'checkerMaskPixel'; -[maskTemplate.rgba, maskTemplate.rgbaSize] = vis.rgba(0, 0); -maskTemplate.blending = '1-source'; % allows us to lay down our zero alpha value - -stencilTemplate = maskTemplate; -stencilTemplate.textureId = 'checkerStencilPixel'; -[stencilTemplate.rgba, stencilTemplate.rgbaSize] = vis.rgba(1, 1); -stencilTemplate.blending = 'none'; - -% pattern layer uses the alpha values laid down by mask layers -patternLayer = vis.emptyLayer(); -patternLayer.textureId = sprintf('~checker%i', randi(2^32)); -patternLayer.isPeriodic = false; -patternLayer.interpolation = 'nearest'; -patternLayer.blending = 'destination'; % use the alpha mask gets laid down before this - -%% construct signals used to assemble layers -% N rows by cols signal is derived from the size of the pattern array but -% we skip repeats so that pattern changes don't update the mask layers -% unless the size has acutally changed -nRowsByCols = elem.pattern.flatten().map(@size).skipRepeats(); -aziRange = elem.azimuthRange.flatten(); -altRange = elem.altitudeRange.flatten(); -sizeFrac = elem.rectSizeFrac.flatten(); -% signal containing the masking layers -gridMaskLayers = mapn(nRowsByCols, aziRange, altRange, sizeFrac, ... - maskTemplate, stencilTemplate, @gridMask); -% signal contain the checker layer -checkerLayer = scan(elem.pattern.flatten(), @updatePattern,... - elem.colour.flatten(), @updateColour,... - elem.azimuthRange.flatten(), @updateAzi,... - elem.altitudeRange.flatten(), @updateAlt,... - elem.show.flatten(), @updateShow,... - patternLayer); % initial value -%% set default attribute values -elem.layers = [gridMaskLayers checkerLayer]; -elem.azimuthRange = [-135 0]; -elem.altitudeRange = [-37.5 37.5]; -elem.rectSizeFrac = [1 1]; % horizontal and vertical size of each rectangle -elem.pattern = [ - 1 -1 1 -1 - -1 0 0 0 - 1 0 0 0 - -1 1 -1 1]; - elem.show = true; -end - -%% helper functions -function layer = updatePattern(layer, pattern) -% map pattern from -1 -> 1 range to 0->255, cast to 8 bit integers, then -% convert to RGBA texture format. -[layer.rgba, layer.rgbaSize] = vis.rgbaFromUint8(uint8(127.5*(1 + pattern)), 1); -end - -function layer = updateColour(layer, colour) -layer.maxColour = [colour 1]; -end - -function layer = updateAzi(layer, aziRange) -layer.size(1) = abs(diff(aziRange)); -layer.texOffset(1) = mean(aziRange); -end - -function layer = updateAlt(layer, altRange) -layer.size(2) = abs(diff(altRange)); -layer.texOffset(2) = mean(altRange); -end - -function layer = updateShow(layer, show) -layer.show = show; -end - -function layers = gridMask(nRowsByCols, aziRange, altRange, sizeFrac, mask, stencil) -gridDims = [abs(diff(aziRange)) abs(diff(altRange))]; -cellSize = gridDims./flip(nRowsByCols); -nCols = nRowsByCols(2) + 1; -nRows = nRowsByCols(1) + 1; -midAzi = mean(aziRange); -midAlt = mean(altRange); -%% base layer to imprint area the checker can draw on (by applying an alpha mask) -stencil.texOffset = [midAzi midAlt]; -stencil.size = gridDims; -if any(sizeFrac < 1) - %% layers for lines making up mask grid - masks out margins around each square - % make layers for vertical lines - if nCols > 1 - azi = linspace(aziRange(1), aziRange(2), nCols); - else - azi = midAzi; - end - collayers = repmat(mask, 1, nCols); - for vi = 1:nCols - collayers(vi).texOffset = [azi(vi) midAlt]; - end - [collayers.size] = deal([(1 - sizeFrac(1))*cellSize(1) gridDims(2)]); - % make layers for horizontal lines - if nRows > 1 - alt = linspace(altRange(1), altRange(2), nRows); - else - alt = midAlt; - end - rowlayers = repmat(mask, 1, nRows); - for hi = 1:nRows - rowlayers(hi).texOffset = [midAzi alt(hi)]; - end - [rowlayers.size] = deal([gridDims(1) (1 - sizeFrac(2))*cellSize(2)]); - %% combine the layers and return - layers = [stencil collayers rowlayers]; -else % no mask grid needed as each cell is full size - layers = stencil; -end - -end \ No newline at end of file diff --git a/cortexlab/+vis/checkerRight.m b/cortexlab/+vis/checkerRight.m deleted file mode 100644 index 78b03545..00000000 --- a/cortexlab/+vis/checkerRight.m +++ /dev/null @@ -1,126 +0,0 @@ -function elem = checkerRight(t) -%vis.checker A grid of rectangles -% Detailed explanation goes here - -elem = t.Node.Net.subscriptableOrigin('checker'); - -%% make initial layers to be used as templates -maskTemplate = vis.emptyLayer(); -maskTemplate.isPeriodic = false; -maskTemplate.interpolation = 'nearest'; -maskTemplate.show = true; -maskTemplate.colourMask = [false false false true]; - -maskTemplate.textureId = 'checkerMaskPixel'; -[maskTemplate.rgba, maskTemplate.rgbaSize] = vis.rgba(0, 0); -maskTemplate.blending = '1-source'; % allows us to lay down our zero alpha value - -stencilTemplate = maskTemplate; -stencilTemplate.textureId = 'checkerStencilPixel'; -[stencilTemplate.rgba, stencilTemplate.rgbaSize] = vis.rgba(1, 1); -stencilTemplate.blending = 'none'; - -% pattern layer uses the alpha values laid down by mask layers -patternLayer = vis.emptyLayer(); -patternLayer.textureId = sprintf('~checker%i', randi(2^32)); -patternLayer.isPeriodic = false; -patternLayer.interpolation = 'nearest'; -patternLayer.blending = 'destination'; % use the alpha mask gets laid down before this - -%% construct signals used to assemble layers -% N rows by cols signal is derived from the size of the pattern array but -% we skip repeats so that pattern changes don't update the mask layers -% unless the size has acutally changed -nRowsByCols = elem.pattern.flatten().map(@size).skipRepeats(); -aziRange = elem.azimuthRange.flatten(); -altRange = elem.altitudeRange.flatten(); -sizeFrac = elem.rectSizeFrac.flatten(); -% signal containing the masking layers -gridMaskLayers = mapn(nRowsByCols, aziRange, altRange, sizeFrac, ... - maskTemplate, stencilTemplate, @gridMask); -% signal contain the checker layer -checkerLayer = scan(elem.pattern.flatten(), @updatePattern,... - elem.colour.flatten(), @updateColour,... - elem.azimuthRange.flatten(), @updateAzi,... - elem.altitudeRange.flatten(), @updateAlt,... - elem.show.flatten(), @updateShow,... - patternLayer); % initial value -%% set default attribute values -elem.layers = [gridMaskLayers checkerLayer]; -elem.azimuthRange = [0 135]; -elem.altitudeRange = [-37.5 37.5]; -elem.rectSizeFrac = [1 1]; % horizontal and vertical size of each rectangle -elem.pattern = [ - 1 -1 1 -1 - -1 0 0 0 - 1 0 0 0 - -1 1 -1 1]; - elem.show = true; -end - -%% helper functions -function layer = updatePattern(layer, pattern) -% map pattern from -1 -> 1 range to 0->255, cast to 8 bit integers, then -% convert to RGBA texture format. -[layer.rgba, layer.rgbaSize] = vis.rgbaFromUint8(uint8(127.5*(1 + pattern)), 1); -end - -function layer = updateColour(layer, colour) -layer.maxColour = [colour 1]; -end - -function layer = updateAzi(layer, aziRange) -layer.size(1) = abs(diff(aziRange)); -layer.texOffset(1) = mean(aziRange); -end - -function layer = updateAlt(layer, altRange) -layer.size(2) = abs(diff(altRange)); -layer.texOffset(2) = mean(altRange); -end - -function layer = updateShow(layer, show) -layer.show = show; -end - -function layers = gridMask(nRowsByCols, aziRange, altRange, sizeFrac, mask, stencil) -gridDims = [abs(diff(aziRange)) abs(diff(altRange))]; -cellSize = gridDims./flip(nRowsByCols); -nCols = nRowsByCols(2) + 1; -nRows = nRowsByCols(1) + 1; -midAzi = mean(aziRange); -midAlt = mean(altRange); -%% base layer to imprint area the checker can draw on (by applying an alpha mask) -stencil.texOffset = [midAzi midAlt]; -stencil.size = gridDims; -if any(sizeFrac < 1) - %% layers for lines making up mask grid - masks out margins around each square - % make layers for vertical lines - if nCols > 1 - azi = linspace(aziRange(1), aziRange(2), nCols); - else - azi = midAzi; - end - collayers = repmat(mask, 1, nCols); - for vi = 1:nCols - collayers(vi).texOffset = [azi(vi) midAlt]; - end - [collayers.size] = deal([(1 - sizeFrac(1))*cellSize(1) gridDims(2)]); - % make layers for horizontal lines - if nRows > 1 - alt = linspace(altRange(1), altRange(2), nRows); - else - alt = midAlt; - end - rowlayers = repmat(mask, 1, nRows); - for hi = 1:nRows - rowlayers(hi).texOffset = [midAzi alt(hi)]; - end - [rowlayers.size] = deal([gridDims(1) (1 - sizeFrac(2))*cellSize(2)]); - %% combine the layers and return - layers = [stencil collayers rowlayers]; -else % no mask grid needed as each cell is full size - layers = stencil; -end - -end \ No newline at end of file diff --git a/signals b/signals index 897da00e..924de6a5 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit 897da00ee217874db2f271b9dbad42886eb4ff7d +Subproject commit 924de6a5b850e1f797a5ad6b24e64644fb5b1f00 From 770499b4e9479c0cedb058bff2142f8c3b86e4d0 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 23 Jan 2019 01:34:36 +0200 Subject: [PATCH 261/393] cellFlat now works with arrays of Signals objects --- cb-tools/burgbox/cellflat.m | 2 +- signals | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cb-tools/burgbox/cellflat.m b/cb-tools/burgbox/cellflat.m index 532b011f..37a5252d 100644 --- a/cb-tools/burgbox/cellflat.m +++ b/cb-tools/burgbox/cellflat.m @@ -18,7 +18,7 @@ if isempty(elem) elem = {elem}; end - flat = [flat; elem]; + flat = [flat; ensureCell(elem)]; end end diff --git a/signals b/signals index 924de6a5..ebea3f63 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit 924de6a5b850e1f797a5ad6b24e64644fb5b1f00 +Subproject commit ebea3f63a892edf25fc8b4d81156a219283c4a4c From 72acc9009c325197e5e20b6247488d24d7bca20f Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 24 Jan 2019 18:30:19 +0200 Subject: [PATCH 262/393] Comments still saved loaclly upon failure to post to Alyx --- +dat/updateLogEntry.m | 6 +++++- signals | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/+dat/updateLogEntry.m b/+dat/updateLogEntry.m index 0882ba2a..47678185 100644 --- a/+dat/updateLogEntry.m +++ b/+dat/updateLogEntry.m @@ -13,7 +13,11 @@ function updateLogEntry(subject, id, newEntry) if isfield(newEntry, 'AlyxInstance') % Update session narrative on Alyx if ~isempty(newEntry.comments) && ~strcmp(subject, 'default') - newEntry.comments = newEntry.AlyxInstance.updateNarrative(newEntry.comments); + try + newEntry.comments = newEntry.AlyxInstance.updateNarrative(newEntry.comments); + catch + warning('Alyx:updateNarrative:UploadFailed', 'Failed to update Alyx session narrative'); + end end newEntry = rmfield(newEntry, 'AlyxInstance'); end diff --git a/signals b/signals index ebea3f63..e05d9537 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit ebea3f63a892edf25fc8b4d81156a219283c4a4c +Subproject commit e05d95376fdc1d36b2dac8e55ed9484c3e6edded From 9ad2c06d3d6a02f7624c23fa781dc83b158024b2 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 24 Jan 2019 18:48:15 +0200 Subject: [PATCH 263/393] Updates to session done with PATCH --- +exp/SignalsExp.m | 4 ++-- alyx-matlab | 2 +- cortexlab/+exp/ChoiceWorld.m | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/+exp/SignalsExp.m b/+exp/SignalsExp.m index 2eb9d192..f616ea70 100644 --- a/+exp/SignalsExp.m +++ b/+exp/SignalsExp.m @@ -906,10 +906,10 @@ function saveData(obj) numCorrect = 0; end % Update Alyx session with end time, trial counts and water tye - sessionData = struct('end_time', obj.AlyxInstance.datestr(now), 'subject', subject); + sessionData = struct('end_time', obj.AlyxInstance.datestr(now)); if ~isempty(numTrials); sessionData.n_trials = numTrials; end if ~isempty(numCorrect); sessionData.n_correct_trials = numCorrect; end - obj.AlyxInstance.postData(url, sessionData, 'put'); + obj.AlyxInstance.postData(url, sessionData, 'patch'); else % Retrieve session from endpoint % subsessions = obj.AlyxInstance.getData(... diff --git a/alyx-matlab b/alyx-matlab index 6fc933b9..dd2ab36d 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit 6fc933b99bec09689a83b024284e8023d2c5793d +Subproject commit dd2ab36de59843edc94c15956967731d81173b74 diff --git a/cortexlab/+exp/ChoiceWorld.m b/cortexlab/+exp/ChoiceWorld.m index 607d6d06..60ad59ec 100644 --- a/cortexlab/+exp/ChoiceWorld.m +++ b/cortexlab/+exp/ChoiceWorld.m @@ -194,8 +194,8 @@ function saveData(obj) numCorrect = 0; end sessionData = struct('end_time', obj.AlyxInstance.datestr(now), ... - 'subject', subject, 'n_trials', numTrials, 'n_correct_trials', numCorrect); - obj.AlyxInstance.postData(obj.AlyxInstance.SessionURL, sessionData, 'put'); + 'n_trials', numTrials, 'n_correct_trials', numCorrect); + obj.AlyxInstance.postData(obj.AlyxInstance.SessionURL, sessionData, 'patch'); else % Infer from date session and retrieve using expFilePath end From eb4cb395a7c58d637e793e88795c4238fb0a5f58 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 24 Jan 2019 18:48:15 +0200 Subject: [PATCH 264/393] Updates to session done with PATCH --- +exp/SignalsExp.m | 4 ++-- alyx-matlab | 2 +- cortexlab/+exp/ChoiceWorld.m | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/+exp/SignalsExp.m b/+exp/SignalsExp.m index 2eb9d192..f616ea70 100644 --- a/+exp/SignalsExp.m +++ b/+exp/SignalsExp.m @@ -906,10 +906,10 @@ function saveData(obj) numCorrect = 0; end % Update Alyx session with end time, trial counts and water tye - sessionData = struct('end_time', obj.AlyxInstance.datestr(now), 'subject', subject); + sessionData = struct('end_time', obj.AlyxInstance.datestr(now)); if ~isempty(numTrials); sessionData.n_trials = numTrials; end if ~isempty(numCorrect); sessionData.n_correct_trials = numCorrect; end - obj.AlyxInstance.postData(url, sessionData, 'put'); + obj.AlyxInstance.postData(url, sessionData, 'patch'); else % Retrieve session from endpoint % subsessions = obj.AlyxInstance.getData(... diff --git a/alyx-matlab b/alyx-matlab index 7a860739..1f29e306 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit 7a860739766fa1a08fd3cb084c7a0b2ec3a7ac37 +Subproject commit 1f29e306747a39cc76d99bb3ac51175309ecbda4 diff --git a/cortexlab/+exp/ChoiceWorld.m b/cortexlab/+exp/ChoiceWorld.m index 607d6d06..60ad59ec 100644 --- a/cortexlab/+exp/ChoiceWorld.m +++ b/cortexlab/+exp/ChoiceWorld.m @@ -194,8 +194,8 @@ function saveData(obj) numCorrect = 0; end sessionData = struct('end_time', obj.AlyxInstance.datestr(now), ... - 'subject', subject, 'n_trials', numTrials, 'n_correct_trials', numCorrect); - obj.AlyxInstance.postData(obj.AlyxInstance.SessionURL, sessionData, 'put'); + 'n_trials', numTrials, 'n_correct_trials', numCorrect); + obj.AlyxInstance.postData(obj.AlyxInstance.SessionURL, sessionData, 'patch'); else % Infer from date session and retrieve using expFilePath end From d54582b72d6a57957a7a2719e2df5b6fbb6bc2ae Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 24 Jan 2019 19:18:28 +0200 Subject: [PATCH 265/393] Added change to patch cached put files to Alyx --- cortexlab/+git/changes.m | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 cortexlab/+git/changes.m diff --git a/cortexlab/+git/changes.m b/cortexlab/+git/changes.m new file mode 100644 index 00000000..d9e9f013 --- /dev/null +++ b/cortexlab/+git/changes.m @@ -0,0 +1,6 @@ +disp('Updating queued Alyx posts...') +posts = dirPlus(getOr(dat.paths, 'localAlyxQueue', 'C:/localAlyxQueue')); +posts = posts(endsWith(posts, 'put')); +newPosts = cellfun(@(str)[str(1:end-3) 'patch'], posts, 'uni', 0); +status = cellfun(@movefile, posts, newPosts); +assert(all(status), 'Unable to rename queued Alyx files, please do this manually') \ No newline at end of file From e4a0325f029e039a08f4ea9c9ac113a87eef9a91 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 24 Jan 2019 19:18:28 +0200 Subject: [PATCH 266/393] Added change to patch cached put files to Alyx --- cortexlab/+git/changes.m | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 cortexlab/+git/changes.m diff --git a/cortexlab/+git/changes.m b/cortexlab/+git/changes.m new file mode 100644 index 00000000..d9e9f013 --- /dev/null +++ b/cortexlab/+git/changes.m @@ -0,0 +1,6 @@ +disp('Updating queued Alyx posts...') +posts = dirPlus(getOr(dat.paths, 'localAlyxQueue', 'C:/localAlyxQueue')); +posts = posts(endsWith(posts, 'put')); +newPosts = cellfun(@(str)[str(1:end-3) 'patch'], posts, 'uni', 0); +status = cellfun(@movefile, posts, newPosts); +assert(all(status), 'Unable to rename queued Alyx files, please do this manually') \ No newline at end of file From 7ad3bab93386a26c83cbd9b4757eedbf09b96a8c Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Sat, 26 Jan 2019 16:45:25 +0200 Subject: [PATCH 267/393] Fix bug for when code never fetched --- cortexlab/+git/update.m | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/cortexlab/+git/update.m b/cortexlab/+git/update.m index 86cb25e9..aaf67546 100644 --- a/cortexlab/+git/update.m +++ b/cortexlab/+git/update.m @@ -9,7 +9,12 @@ function update(scheduled) if nargin < 1; scheduled = getOr(dat.paths, 'updateSchedule', 0); end root = fileparts(which('addRigboxPaths')); -lastFetch = getOr(dir(fullfile(root, '.git', 'FETCH_HEAD')), 'datenum'); +% Attempt to find date of last fetch +fetch_head = fullfile(root, '.git', 'FETCH_HEAD'); +lastFetch = iff(exist(fetch_head,'file')==2, ... % If FETCH_HEAD file exists + @()getOr(dir(fetch_head), 'datenum'), 0); % Retrieve date modified +% If the code has not been fetched in over a week, force and update, +% otherwise return if (scheduled && weekday(now) ~= scheduled && now - lastFetch < 7) || ... (~scheduled && now - lastFetch < 1/24) return From 7ae881309bc8413506b13e1449cf546f97f7266c Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Sun, 27 Jan 2019 17:03:20 +0200 Subject: [PATCH 268/393] Bug fix for timeplot --- signals | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/signals b/signals index e05d9537..93520307 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit e05d95376fdc1d36b2dac8e55ed9484c3e6edded +Subproject commit 93520307c1cb1a9dc12fa8bda685a01b9ab80401 From 5dcb8285193ccce8c7b816d3848511402ef89c46 Mon Sep 17 00:00:00 2001 From: jaib1 <jaib1@mit.edu> Date: Mon, 28 Jan 2019 08:16:59 +0000 Subject: [PATCH 269/393] add signals 'tutorials' folder to paths --- addRigboxPaths.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addRigboxPaths.m b/addRigboxPaths.m index b705367e..0364b06f 100644 --- a/addRigboxPaths.m +++ b/addRigboxPaths.m @@ -94,8 +94,8 @@ function addRigboxPaths(savePaths) % Add signals paths, this includes all the core code for running signals % experiments. This submodule is maintained by Chris Burgess. addpath(fullfile(root, 'signals'),... - fullfile(root, 'signals', 'mexnet'),... - fullfile(root, 'signals', 'util')); + fullfile(root, 'signals', 'mexnet'), fullfile(root, 'signals', 'util'),... + fullfile(root, 'signals', 'tutorials')); % Add the Java paths for signals jcp = fullfile(root, 'signals', 'java'); if ~any(strcmp(javaclasspath, jcp)); javaaddpath(jcp); end From 808d565bab222827b60f410842868d09c873b960 Mon Sep 17 00:00:00 2001 From: jaib1 <jaib1@mit.edu> Date: Mon, 28 Jan 2019 12:27:38 +0000 Subject: [PATCH 270/393] Revert "add signals 'tutorials' folder to paths" This reverts commit 5dcb8285193ccce8c7b816d3848511402ef89c46. --- addRigboxPaths.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addRigboxPaths.m b/addRigboxPaths.m index 0364b06f..b705367e 100644 --- a/addRigboxPaths.m +++ b/addRigboxPaths.m @@ -94,8 +94,8 @@ function addRigboxPaths(savePaths) % Add signals paths, this includes all the core code for running signals % experiments. This submodule is maintained by Chris Burgess. addpath(fullfile(root, 'signals'),... - fullfile(root, 'signals', 'mexnet'), fullfile(root, 'signals', 'util'),... - fullfile(root, 'signals', 'tutorials')); + fullfile(root, 'signals', 'mexnet'),... + fullfile(root, 'signals', 'util')); % Add the Java paths for signals jcp = fullfile(root, 'signals', 'java'); if ~any(strcmp(javaclasspath, jcp)); javaaddpath(jcp); end From 3d90407b2353337a7e5d790537a0fc791a9abbf5 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Mon, 28 Jan 2019 17:43:37 +0200 Subject: [PATCH 271/393] Update only occurs once on scheduled day --- cortexlab/+git/update.m | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/cortexlab/+git/update.m b/cortexlab/+git/update.m index aaf67546..fd6a7e0b 100644 --- a/cortexlab/+git/update.m +++ b/cortexlab/+git/update.m @@ -1,11 +1,15 @@ function update(scheduled) % GIT.UPDATE Pull latest Rigbox code % Pulls the latest code from the remote repository. If scheduled is a -% value in the range [1 7] corresponding to the days of the week, the -% function will only continue on that day, or if the last fetch was over -% a week ago. +% value in the range [1 7] corresponding to the days of the week starting +% Sunday, the function will only continue on that day, provided the last +% fetch was over a day ago. If it is not the scheduled day, but the last +% fetch was over a week ago, the function will pull changes. If +% scheduled is false, the function will pull changes provided the last +% fetch was over an hour ago. +% % TODO Find quicker way to check for changes -% See also +% See also DAT.PATHS if nargin < 1; scheduled = getOr(dat.paths, 'updateSchedule', 0); end root = fileparts(which('addRigboxPaths')); @@ -13,9 +17,15 @@ function update(scheduled) fetch_head = fullfile(root, '.git', 'FETCH_HEAD'); lastFetch = iff(exist(fetch_head,'file')==2, ... % If FETCH_HEAD file exists @()getOr(dir(fetch_head), 'datenum'), 0); % Retrieve date modified -% If the code has not been fetched in over a week, force and update, -% otherwise return + +% Don't pull changes if the following conditions are met: +% 1. The updates are scheduled for a different day and the last fetch was less +% than a week ago. +% 2. The updates are scheduled for today and the last fetch was today. +% 3. The updates are scheduled for every day and the last fetch was less +% than an hour ago. if (scheduled && weekday(now) ~= scheduled && now - lastFetch < 7) || ... + (scheduled && weekday(now) == scheduled && now - lastFetch < 1) || ... (~scheduled && now - lastFetch < 1/24) return end @@ -39,10 +49,10 @@ function update(scheduled) % Stash any WIP, check submodules are initialized, pull try - [status, cmdout] = system(cmdstrStash, '-echo'); - [status, cmdout] = system(cmdstrStashSubs, '-echo'); - [status, cmdout] = system(cmdstrInit, '-echo'); - [status, cmdout] = system(cmdstrPull, '-echo'); + [~, cmdout] = system(cmdstrStash, '-echo'); + [~, cmdout] = system(cmdstrStashSubs, '-echo'); + [~, cmdout] = system(cmdstrInit, '-echo'); + [~, cmdout] = system(cmdstrPull, '-echo'); %#ok<ASGLU> catch ex cd(origDir) error('gitUpdate:pull:pullFailed', 'Failed to pull latest changes:, %s', cmdout) @@ -55,4 +65,4 @@ function update(scheduled) delete(changesPath); end cd(origDir) -end +end \ No newline at end of file From 0ff74273ede9adfe8ec372ae13d9a41997cad8a7 Mon Sep 17 00:00:00 2001 From: Jai Bhagat <j.bhagat@ucl.ac.uk> Date: Mon, 28 Jan 2019 16:22:31 +0000 Subject: [PATCH 272/393] additional comments for git.update --- cortexlab/+git/update.m | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/cortexlab/+git/update.m b/cortexlab/+git/update.m index fd6a7e0b..a04a9b62 100644 --- a/cortexlab/+git/update.m +++ b/cortexlab/+git/update.m @@ -1,32 +1,34 @@ function update(scheduled) % GIT.UPDATE Pull latest Rigbox code -% Pulls the latest code from the remote repository. If scheduled is a -% value in the range [1 7] corresponding to the days of the week starting -% Sunday, the function will only continue on that day, provided the last -% fetch was over a day ago. If it is not the scheduled day, but the last -% fetch was over a week ago, the function will pull changes. If -% scheduled is false, the function will pull changes provided the last -% fetch was over an hour ago. +% Pulls the latest code from the remote Github repository. If 'scheduled' +% is a value in the range [1 7] - corresponding to the days of the week, +% with Sunday=1 - code will be pulled only on the 'scheduled' day, +% provided the last fetch was over a day ago. Code will also be pulled if +% it is not the scheduled day, but the last fetch was over a week ago. If +% scheduled is 0, the function will pull changes provided the last fetch +% was over an hour ago. % % TODO Find quicker way to check for changes % See also DAT.PATHS -if nargin < 1; scheduled = getOr(dat.paths, 'updateSchedule', 0); end -root = fileparts(which('addRigboxPaths')); +% If not given as input argument, find 'scheduled' in 'dat.paths'. If not +% found, set 'scheduled' to 0. +if nargin < 1; scheduled = getOr(dat.paths, 'updateSchedule', 0); end +root = fileparts(which('addRigboxPaths')); % Rigbox root directory % Attempt to find date of last fetch fetch_head = fullfile(root, '.git', 'FETCH_HEAD'); lastFetch = iff(exist(fetch_head,'file')==2, ... % If FETCH_HEAD file exists @()getOr(dir(fetch_head), 'datenum'), 0); % Retrieve date modified % Don't pull changes if the following conditions are met: -% 1. The updates are scheduled for a different day and the last fetch was less -% than a week ago. +% 1. The updates are scheduled for a different day and the last fetch was +% less than a week ago. % 2. The updates are scheduled for today and the last fetch was today. % 3. The updates are scheduled for every day and the last fetch was less % than an hour ago. -if (scheduled && weekday(now) ~= scheduled && now - lastFetch < 7) || ... - (scheduled && weekday(now) == scheduled && now - lastFetch < 1) || ... - (~scheduled && now - lastFetch < 1/24) +if ((scheduled && weekday(now)) ~= (scheduled && (now - lastFetch < 7))) || ... + ((scheduled && weekday(now)) == (scheduled && (now - lastFetch < 1))) || ... + (~scheduled && (now - lastFetch < 1/24)) return end disp('Updating code...') @@ -34,7 +36,7 @@ function update(scheduled) % Get the path to the Git exe gitexepath = getOr(dat.paths, 'gitExe'); if isempty(gitexepath) - [~,gitexepath] = system('where git'); % this doesn't always work + [~,gitexepath] = system('where git'); % todo: this doesn't always work end gitexepath = ['"', strtrim(gitexepath), '"']; @@ -42,6 +44,8 @@ function update(scheduled) origDir = pwd; cd(root) +% Create Windows system commands for git stashing, initializing submodules, +% and pulling cmdstrStash = [gitexepath, ' stash push -m "stash Rigbox working changes before scheduled git update"']; cmdstrStashSubs = [gitexepath, ' submodule foreach "git stash push"']; cmdstrInit = [gitexepath, ' submodule update --init']; @@ -65,4 +69,4 @@ function update(scheduled) delete(changesPath); end cd(origDir) -end \ No newline at end of file +end From 2a5185d259499e250caf8f05a8d6641098ab287e Mon Sep 17 00:00:00 2001 From: Jai Bhagat <j.bhagat@ucl.ac.uk> Date: Mon, 28 Jan 2019 16:25:34 +0000 Subject: [PATCH 273/393] updated git.update from commit 0ff742 in 'dev' --- cortexlab/+git/update.m | 47 +++++++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/cortexlab/+git/update.m b/cortexlab/+git/update.m index 86cb25e9..a04a9b62 100644 --- a/cortexlab/+git/update.m +++ b/cortexlab/+git/update.m @@ -1,17 +1,34 @@ function update(scheduled) % GIT.UPDATE Pull latest Rigbox code -% Pulls the latest code from the remote repository. If scheduled is a -% value in the range [1 7] corresponding to the days of the week, the -% function will only continue on that day, or if the last fetch was over -% a week ago. +% Pulls the latest code from the remote Github repository. If 'scheduled' +% is a value in the range [1 7] - corresponding to the days of the week, +% with Sunday=1 - code will be pulled only on the 'scheduled' day, +% provided the last fetch was over a day ago. Code will also be pulled if +% it is not the scheduled day, but the last fetch was over a week ago. If +% scheduled is 0, the function will pull changes provided the last fetch +% was over an hour ago. +% % TODO Find quicker way to check for changes -% See also +% See also DAT.PATHS + +% If not given as input argument, find 'scheduled' in 'dat.paths'. If not +% found, set 'scheduled' to 0. if nargin < 1; scheduled = getOr(dat.paths, 'updateSchedule', 0); end +root = fileparts(which('addRigboxPaths')); % Rigbox root directory +% Attempt to find date of last fetch +fetch_head = fullfile(root, '.git', 'FETCH_HEAD'); +lastFetch = iff(exist(fetch_head,'file')==2, ... % If FETCH_HEAD file exists + @()getOr(dir(fetch_head), 'datenum'), 0); % Retrieve date modified -root = fileparts(which('addRigboxPaths')); -lastFetch = getOr(dir(fullfile(root, '.git', 'FETCH_HEAD')), 'datenum'); -if (scheduled && weekday(now) ~= scheduled && now - lastFetch < 7) || ... - (~scheduled && now - lastFetch < 1/24) +% Don't pull changes if the following conditions are met: +% 1. The updates are scheduled for a different day and the last fetch was +% less than a week ago. +% 2. The updates are scheduled for today and the last fetch was today. +% 3. The updates are scheduled for every day and the last fetch was less +% than an hour ago. +if ((scheduled && weekday(now)) ~= (scheduled && (now - lastFetch < 7))) || ... + ((scheduled && weekday(now)) == (scheduled && (now - lastFetch < 1))) || ... + (~scheduled && (now - lastFetch < 1/24)) return end disp('Updating code...') @@ -19,7 +36,7 @@ function update(scheduled) % Get the path to the Git exe gitexepath = getOr(dat.paths, 'gitExe'); if isempty(gitexepath) - [~,gitexepath] = system('where git'); % this doesn't always work + [~,gitexepath] = system('where git'); % todo: this doesn't always work end gitexepath = ['"', strtrim(gitexepath), '"']; @@ -27,6 +44,8 @@ function update(scheduled) origDir = pwd; cd(root) +% Create Windows system commands for git stashing, initializing submodules, +% and pulling cmdstrStash = [gitexepath, ' stash push -m "stash Rigbox working changes before scheduled git update"']; cmdstrStashSubs = [gitexepath, ' submodule foreach "git stash push"']; cmdstrInit = [gitexepath, ' submodule update --init']; @@ -34,10 +53,10 @@ function update(scheduled) % Stash any WIP, check submodules are initialized, pull try - [status, cmdout] = system(cmdstrStash, '-echo'); - [status, cmdout] = system(cmdstrStashSubs, '-echo'); - [status, cmdout] = system(cmdstrInit, '-echo'); - [status, cmdout] = system(cmdstrPull, '-echo'); + [~, cmdout] = system(cmdstrStash, '-echo'); + [~, cmdout] = system(cmdstrStashSubs, '-echo'); + [~, cmdout] = system(cmdstrInit, '-echo'); + [~, cmdout] = system(cmdstrPull, '-echo'); %#ok<ASGLU> catch ex cd(origDir) error('gitUpdate:pull:pullFailed', 'Failed to pull latest changes:, %s', cmdout) From c9c27a22749d09111754585a90a2528d261598f3 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Mon, 28 Jan 2019 19:53:47 +0200 Subject: [PATCH 274/393] Fix'd incorrect brackets --- cortexlab/+git/update.m | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cortexlab/+git/update.m b/cortexlab/+git/update.m index a04a9b62..200ba269 100644 --- a/cortexlab/+git/update.m +++ b/cortexlab/+git/update.m @@ -11,8 +11,8 @@ function update(scheduled) % TODO Find quicker way to check for changes % See also DAT.PATHS -% If not given as input argument, find 'scheduled' in 'dat.paths'. If not -% found, set 'scheduled' to 0. +% If not given as input argument, use 'updateSchedule' in 'dat.paths'. If +% not found, set 'scheduled' to 0. if nargin < 1; scheduled = getOr(dat.paths, 'updateSchedule', 0); end root = fileparts(which('addRigboxPaths')); % Rigbox root directory % Attempt to find date of last fetch @@ -26,9 +26,9 @@ function update(scheduled) % 2. The updates are scheduled for today and the last fetch was today. % 3. The updates are scheduled for every day and the last fetch was less % than an hour ago. -if ((scheduled && weekday(now)) ~= (scheduled && (now - lastFetch < 7))) || ... - ((scheduled && weekday(now)) == (scheduled && (now - lastFetch < 1))) || ... - (~scheduled && (now - lastFetch < 1/24)) +if (scheduled && (weekday(now) ~= scheduled) && now - lastFetch < 7) || ... + (scheduled && (weekday(now) == scheduled) && now - lastFetch < 1) || ... + (~scheduled && now - lastFetch < 1/24) return end disp('Updating code...') From cbb131337a85a5b1a25c56b1254db16cf3cf3a44 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Mon, 28 Jan 2019 19:53:47 +0200 Subject: [PATCH 275/393] Fix'd incorrect brackets --- cortexlab/+git/update.m | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cortexlab/+git/update.m b/cortexlab/+git/update.m index a04a9b62..200ba269 100644 --- a/cortexlab/+git/update.m +++ b/cortexlab/+git/update.m @@ -11,8 +11,8 @@ function update(scheduled) % TODO Find quicker way to check for changes % See also DAT.PATHS -% If not given as input argument, find 'scheduled' in 'dat.paths'. If not -% found, set 'scheduled' to 0. +% If not given as input argument, use 'updateSchedule' in 'dat.paths'. If +% not found, set 'scheduled' to 0. if nargin < 1; scheduled = getOr(dat.paths, 'updateSchedule', 0); end root = fileparts(which('addRigboxPaths')); % Rigbox root directory % Attempt to find date of last fetch @@ -26,9 +26,9 @@ function update(scheduled) % 2. The updates are scheduled for today and the last fetch was today. % 3. The updates are scheduled for every day and the last fetch was less % than an hour ago. -if ((scheduled && weekday(now)) ~= (scheduled && (now - lastFetch < 7))) || ... - ((scheduled && weekday(now)) == (scheduled && (now - lastFetch < 1))) || ... - (~scheduled && (now - lastFetch < 1/24)) +if (scheduled && (weekday(now) ~= scheduled) && now - lastFetch < 7) || ... + (scheduled && (weekday(now) == scheduled) && now - lastFetch < 1) || ... + (~scheduled && now - lastFetch < 1/24) return end disp('Updating code...') From b36cadb46f9b9deec89de5d6edf93aec00237bb8 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 31 Jan 2019 00:38:11 +0200 Subject: [PATCH 276/393] Attempt to integrate new Parameter Editor into mc --- +eui/ConditionPanel.m | 252 ++++++++++++++++ +eui/FieldPanel.m | 164 +++++++++++ +eui/MControl.m | 24 +- +eui/ParamEditor.m | 630 +++++++++++++--------------------------- +eui/ParamEditor_old.m | 516 ++++++++++++++++++++++++++++++++ +exp/Parameters.m | 10 +- cortexlab/+git/update.m | 2 +- signals | 2 +- 8 files changed, 1160 insertions(+), 440 deletions(-) create mode 100644 +eui/ConditionPanel.m create mode 100644 +eui/FieldPanel.m create mode 100644 +eui/ParamEditor_old.m diff --git a/+eui/ConditionPanel.m b/+eui/ConditionPanel.m new file mode 100644 index 00000000..7c4b89fe --- /dev/null +++ b/+eui/ConditionPanel.m @@ -0,0 +1,252 @@ +classdef ConditionPanel < handle + %UNTITLED Summary of this class goes here + % Detailed explanation goes here + % TODO Document + % TODO Add sort by column + % TODO Add set condition idx + % TODO Use tags for menu items + + properties + ConditionTable + MinWidth = 80 +% MaxWidth = 140 +% Margin = 4 + UIPanel + ButtonPanel + ContextMenus + end + + properties %(Access = protected) + ParamEditor + Listener + NewConditionButton + DeleteConditionButton + MakeGlobalButton + SetValuesButton + SelectedCells %[row, column;...] of each selected cell + end + + methods + function obj = ConditionPanel(f, ParamEditor, varargin) + obj.ParamEditor = ParamEditor; + obj.UIPanel = uipanel('Parent', f, 'BorderType', 'none',... + 'BackgroundColor', 'white', 'Position', [0.5 0.05 0.5 0.95]); + % Create a child menu for the uiContextMenus + c = uicontextmenu; + obj.UIPanel.UIContextMenu = c; + obj.ContextMenus = uimenu(c, 'Label', 'Make Global', 'MenuSelectedFcn', @(~,~)obj.makeGlobal); + fcn = @(s,~)obj.ParamEditor.setRandomized(~strcmp(s.Checked, 'on')); + obj.ContextMenus(2) = uimenu(c, 'Label', 'Randomize conditions', ... + 'MenuSelectedFcn', fcn, 'Checked', 'on', 'Tag', 'randomize button'); + obj.ContextMenus(3) = uimenu(c, 'Label', 'Sort by selected column', ... + 'MenuSelectedFcn', @(~,~)disp('feature not yet implemented'), 'Tag', 'sort by'); + % Create condition table + obj.ConditionTable = uitable('Parent', obj.UIPanel,... + 'FontName', 'Consolas',... + 'RowName', [],... + 'RearrangeableColumns', true,... + 'Units', 'normalized',... + 'Position',[0 0 1 1],... + 'UIContextMenu', c,... + 'CellEditCallback', @obj.onEdit,... + 'CellSelectionCallback', @obj.onSelect); + % Create button panel to hold condition control buttons + obj.ButtonPanel = uipanel('BackgroundColor', 'white',... + 'Position', [0.5 0 0.5 0.05], 'BorderType', 'none'); + % Create callback so that width of button panel is slave to width of + % conditional UIPanel + b = obj.ButtonPanel; + fcn = @(s)set(obj.ButtonPanel, 'Position', ... + [s.Position(1) b.Position(2) s.Position(3) b.Position(4)]); + obj.Listener = event.listener(obj.UIPanel, 'SizeChanged', @(s,~)fcn(s)); + % Define some common properties + props.BackgroundColor = 'white'; + props.Style = 'pushbutton'; + props.Units = 'normalized'; + props.Parent = obj.ButtonPanel; + % Create out four buttons + obj.NewConditionButton = uicontrol(props,... + 'String', 'New condition',... + 'Position',[0 0 1/4 1],... + 'TooltipString', 'Add a new condition',... + 'Callback', @(~, ~) obj.newCondition()); + obj.DeleteConditionButton = uicontrol(props,... + 'String', 'Delete condition',... + 'Position',[1/4 0 1/4 1],... + 'TooltipString', 'Delete the selected condition',... + 'Enable', 'off',... + 'Callback', @(~, ~) obj.deleteSelectedConditions()); + obj.MakeGlobalButton = uicontrol(props,... + 'String', 'Globalise parameter',... + 'Position',[2/4 0 1/4 1],... + 'TooltipString', sprintf(['Make the selected condition-specific parameter global (i.e. not vary by trial)\n'... + 'This will move it to the global parameters section']),... + 'Enable', 'off',... + 'Callback', @(~, ~) obj.makeGlobal()); + obj.SetValuesButton = uicontrol(props,... + 'String', 'Set values',... + 'Position',[3/4 0 1/4 1],... + 'TooltipString', 'Set selected values to specified value, range or function',... + 'Enable', 'off',... + 'Callback', @(~, ~) obj.setSelectedValues()); + end + + function onEdit(obj, src, eventData) + disp('updating table cell'); + row = eventData.Indices(1); + col = eventData.Indices(2); + paramName = obj.ConditionTable.ColumnName{col}; + newValue = obj.ParamEditor.update(paramName, eventData.NewData, row); + reformed = obj.ParamEditor.paramValue2Control(newValue); + % If successful update the cell with default formatting + data = get(src, 'Data'); + if iscell(reformed) + % The reformed data type is a cell, this should be a one element + % wrapping cell + if numel(reformed) == 1 + reformed = reformed{1}; + else + error('Cannot handle data reformatted data type'); + end + end + data{row,col} = reformed; + set(src, 'Data', data); + end + + function clear(obj) + set(obj.ConditionTable, 'ColumnName', [], ... + 'Data', [], 'ColumnEditable', false); + end + + function delete(obj) + disp('delete called'); + delete(obj.UIPanel); + end + + function onSelect(obj, ~, eventData) + obj.SelectedCells = eventData.Indices; + if size(eventData.Indices, 1) > 0 + % cells selected, enable buttons + set(obj.MakeGlobalButton, 'Enable', 'on'); + set(obj.DeleteConditionButton, 'Enable', 'on'); + set(obj.SetValuesButton, 'Enable', 'on'); + set(obj.ContextMenus(1), 'Enable', 'on'); + set(obj.ContextMenus(3), 'Enable', 'on'); + else + % nothing selected, disable buttons + set(obj.MakeGlobalButton, 'Enable', 'off'); + set(obj.DeleteConditionButton, 'Enable', 'off'); + set(obj.SetValuesButton, 'Enable', 'off'); + set(obj.ContextMenus(1), 'Enable', 'off'); + set(obj.ContextMenus(3), 'Enable', 'off'); + end + end + + function makeGlobal(obj) + if isempty(obj.SelectedCells) + disp('nothing selected') + return + end + [cols, iu] = unique(obj.SelectedCells(:,2)); + names = obj.ConditionTable.ColumnName(cols); + rows = num2cell(obj.SelectedCells(iu,1)); %get rows of unique selected cols + PE = obj.ParamEditor; + cellfun(@PE.globaliseParamAtCell, names, rows); + end + + function deleteSelectedConditions(obj) + %DELETESELECTEDCONDITIONS Removes the selected conditions from table + % The callback for the 'Delete condition' button. This removes the + % selected conditions from the table and if less than two conditions + % remain, globalizes them. + % TODO: comment function better, index in a clearer fashion + % + % See also EXP.PARAMETERS, GLOBALISESELECTEDPARAMETERS + rows = unique(obj.SelectedCells(:,1)); + names = obj.ConditionTable.ColumnName; + numConditions = size(obj.ConditionTable.Data,2); + % If the number of remaining conditions is 1 or less... + if numConditions-length(rows) <= 1 + remainingIdx = find(all(1:numConditions~=rows,1)); + if isempty(remainingIdx); remainingIdx = 1; end + % change selected cells to be all fields (except numRepeats which + % is assumed to always be the last column) + obj.SelectedCells =[ones(length(names),1)*remainingIdx, (1:length(names))']; + %... globalize them + obj.makeGlobal; + else % Otherwise delete the selected conditions as usual + obj.ParamEditor.Parameters.removeConditions(rows); %FIXME: Should be in ParamEditor + end + % Refresh the table of conditions FIXME: Should be in ParamEditor + obj.ParamEditor.fillConditionTable(); + end + + function setSelectedValues(obj) % Set multiple fields in conditional table + disp('updating table cells'); + cols = obj.SelectedCells(:,2); % selected columns + uCol = unique(obj.SelectedCells(:,2)); + rows = obj.SelectedCells(:,1); % selected rows + % get current values of selected cells + currVals = arrayfun(@(u)obj.ConditionTable.Data(rows(cols==u),u), uCol, 'UniformOutput', 0); + names = obj.ConditionTable.ColumnName(uCol); % selected column names + promt = cellfun(@(a,b) [a ' (' num2str(sum(cols==b)) ')'],... + names, num2cell(uCol), 'UniformOutput', 0); % names of columns & num selected rows + defaultans = cellfun(@(c) c(1), currVals); + answer = inputdlg(promt,'Set values', 1, cellflat(defaultans)); % prompt for input + if isempty(answer) % if user presses cancel + return + end + % set values for each column + cellfun(@(a,b,c) setNewVals(a,b,c), answer, currVals, names, 'UniformOutput', 0); + function newVals = setNewVals(userIn, currVals, paramName) + % check array orientation + currVals = iff(size(currVals,1)>size(currVals,2),currVals',currVals); + if strStartsWith(userIn,'@') % anon function + func_h = str2func(userIn); + % apply function to each cell + currVals = cellfun(@str2double,currVals, 'UniformOutput', 0); % convert from char + newVals = cellfun(func_h, currVals, 'UniformOutput', 0); + elseif any(userIn==':') % array syntax + arr = eval(userIn); + newVals = num2cell(arr); % convert to cell array + elseif any(userIn==','|userIn==';') % 2D arrays + C = strsplit(userIn, ';'); + newVals = cellfun(@(c)textscan(c, '%f',... + 'ReturnOnError', false,... + 'delimiter', {' ', ','}, 'MultipleDelimsAsOne', 1),... + C); + else % single value to copy across all cells + userIn = str2double(userIn); + newVals = num2cell(ones(size(currVals))*userIn); + end + + if length(newVals)>length(currVals) % too many new values + newVals = newVals(1:length(currVals)); % truncate new array + elseif length(newVals)<length(currVals) % too few new values + % populate as many cells as possible + newVals = [newVals ... + cellfun(@(a)ui.ParamEditor.controlValue2Param(2,a),... + currVals(length(newVals)+1:end),'UniformOutput',0)]; + end + ic = strcmp(obj.ConditionTable.ColumnName, paramName); % find edited param names + % update param struct + obj.ParamEditor.Parameters.Struct.(paramName)(:,rows(cols==find(ic))) = cell2mat(newVals); + % update condtion table with strings + obj.ConditionTable.Data(rows(cols==find(ic)),ic)... + = cellfun(@(a)ui.ParamEditor.paramValue2Control(a), newVals', 'UniformOutput', 0); + end + notify(obj.ParamEditor, 'Changed'); + end + + function newCondition(obj) + disp('adding new condition row'); + PE = obj.ParamEditor; + cellfun(@PE.addEmptyConditionToParam, ... + PE.Parameters.TrialSpecificNames); + obj.ParamEditor.fillConditionTable(); + end + + + end + +end \ No newline at end of file diff --git a/+eui/FieldPanel.m b/+eui/FieldPanel.m new file mode 100644 index 00000000..33ae7ee1 --- /dev/null +++ b/+eui/FieldPanel.m @@ -0,0 +1,164 @@ +classdef FieldPanel < handle + %UNTITLED Summary of this class goes here + % Detailed explanation goes here + + properties + MinCtrlWidth = 40 + MaxCtrlWidth = 140 + Margin = 4 + RowSpacing = 1 + ColSpacing = 3 + UIPanel + ContextMenu + end + + properties %(Access = protected) + ParamEditor + MinRowHeight + Listener + Labels + Controls + LabelWidths + end + + events + Changed + end + + methods + function obj = FieldPanel(f, ParamEditor, varargin) + obj.ParamEditor = ParamEditor; + obj.UIPanel = uipanel('Parent', f, 'BorderType', 'none',... + 'BackgroundColor', 'white', 'Position', [0 0 0.5 1]); + obj.Listener = event.listener(obj.UIPanel, 'SizeChanged', @obj.onResize); + end + + function [label, ctrl] = addField(obj, name, ctrl) + if isempty(obj.ContextMenu) + obj.ContextMenu = uicontextmenu; + uimenu(obj.ContextMenu, 'Label', 'Make Coditional', ... + 'MenuSelectedFcn', @(~,~)obj.makeConditional); + end + props.BackgroundColor = 'white'; + props.HorizontalAlignment = 'left'; + props.UIContextMenu = obj.ContextMenu; + props.Parent = obj.UIPanel; + label = uicontrol('Style', 'text', 'String', name, props); + if nargin < 3 + ctrl = uicontrol('Style', 'edit', props); + end + callback = @(src,~)onEdit(obj, src, name); + set(ctrl, 'Callback', callback); + obj.Labels = [obj.Labels; label]; + obj.Controls = [obj.Controls; ctrl]; + end + + function onEdit(obj, src, id) + disp(id); + switch get(src, 'style') + case 'checkbox' + newValue = logical(get(src, 'value')); + obj.ParamEditor.update(id, newValue); + case 'edit' + % if successful update the control with default formatting and + % modified colour + newValue = obj.ParamEditor.update(id, get(src, 'string')); + set(src, 'String', obj.ParamEditor.paramValue2Control(newValue)); + end + changed = strcmp(id,{obj.Labels.String}); + obj.Labels(changed).ForegroundColor = [1 0 0]; + end + + function clear(obj, idx) % FIXME Rename to clearFields + if nargin == 1 + idx = true(size(obj.Labels)); + end + delete(obj.Labels(idx)) + delete(obj.Controls(idx)) + obj.Labels(idx) = []; + obj.LabelWidths(idx) = []; + obj.Controls(idx) = []; + end + + function makeConditional(obj, name) + if nargin == 1 + selected = obj.UIPanel.Parent.CurrentObject; %FIXME Doesn't work is parent is not figure + if isa(selected, 'matlab.ui.control.UIControl') && ... + strcmp(selected.Style, 'text') + name = selected.String; + else % Assume control + name = obj.Labels([obj.Controls]==selected).String; + end + end + idx = strcmp(name,{obj.Labels.String}); + assert(~ismember(name, {'randomiseConditions'}), ... + '%s can not be made a conditional parameter', name) + + obj.clear(idx); + obj.ParamEditor.Parameters.makeTrialSpecific(name); + obj.ParamEditor.fillConditionTable(); + obj.onResize; + end + + function delete(obj) + disp('delete called'); + delete(obj.UIPanel); + end + + function onResize(obj, ~, ~) + if isempty(obj.Controls) + return + end + if isempty(obj.LabelWidths) || numel(obj.LabelWidths) ~= numel(obj.Labels) + ext = reshape([obj.Labels.Extent], 4, [])'; + obj.LabelWidths = ext(:,3); + l = uicontrol('Parent', obj.UIPanel, 'Style', 'edit', 'String', 'something'); + obj.MinRowHeight = l.Extent(4); + delete(l); + end + +% %%% resize condition table +% w = numel(obj.ConditionTable.ColumnName); +% % nCols = max(cols); +% % globalWidth = (fullColWidth * nCols) + borderwidth; +% if w > 5; w = 0.5; else; w = 0.1 * w; end +% obj.UI(2).Position = [1-w 0 w 1]; +% obj.UI(1).Position = [0 0 1-w 1]; + + %%% general coordinates + pos = getpixelposition(obj.UIPanel); + borderwidth = obj.Margin; + bounds = [pos(3) pos(4)] - 2*borderwidth; + n = numel(obj.Labels); + vspace = obj.RowSpacing; + hspace = obj.ColSpacing; + rowHeight = obj.MinRowHeight + 2*vspace; + rowsPerCol = floor(bounds(2)/rowHeight); + cols = ceil((1:n)/rowsPerCol)'; + ncols = cols(end); + rows = mod(0:n - 1, rowsPerCol)' + 1; + labelColWidth = max(obj.LabelWidths) + 2*hspace; + ctrlWidthAvail = bounds(1)/ncols - labelColWidth; + ctrlColWidth = max(obj.MinCtrlWidth, min(ctrlWidthAvail, obj.MaxCtrlWidth)); + fullColWidth = labelColWidth + ctrlColWidth; + + %%% coordinates of labels + by = bounds(2) - rows*rowHeight + vspace + 1 + borderwidth; + labelPos = [vspace + (cols - 1)*fullColWidth + 1 + borderwidth... + by... + obj.LabelWidths... + repmat(rowHeight - 2*vspace, n, 1)]; + + %%% coordinates of edits + editPos = [labelColWidth + hspace + (cols - 1)*fullColWidth + 1 + borderwidth ... + by... + repmat(ctrlColWidth - 2*hspace, n, 1)... + repmat(rowHeight - 2*vspace, n, 1)]; + set(obj.Labels, {'Position'}, num2cell(labelPos, 2)); + set(obj.Controls, {'Position'}, num2cell(editPos, 2)); + + end + end + +end + diff --git a/+eui/MControl.m b/+eui/MControl.m index 5b25075d..5bad701e 100644 --- a/+eui/MControl.m +++ b/+eui/MControl.m @@ -247,10 +247,10 @@ function saveParamProfile(obj) % Called by 'Save...' button press, save a new pa end function loadParamProfile(obj, profile) + set(obj.ParamProfileLabel, 'String', 'loading...', 'ForegroundColor', [1 0 0]); % Red 'Loading...' while new set loads if ~isempty(obj.ParamEditor) - %delete existing parameters control - delete(obj.ParamEditor); - set(obj.ParamProfileLabel, 'String', 'loading...', 'ForegroundColor', [1 0 0]); % Red 'Loading...' while new set loads + % Clear existing parameters control + % TODO end factory = obj.NewExpFactory; % Find which 'world' we are in @@ -305,12 +305,18 @@ function loadParamProfile(obj, profile) paramStruct = rmfield(paramStruct, 'services'); end obj.Parameters.Struct = paramStruct; - if ~isempty(paramStruct) % Now parameters are loaded, pass to ParamEditor for display, etc. - obj.ParamEditor = eui.ParamEditor(obj.Parameters, obj.ParamPanel); % Build parameter list in Global panel by calling eui.ParamEditor - obj.ParamEditor.addlistener('Changed', @(src,~) obj.paramChanged); - if strcmp(obj.RemoteRigs.Selected.Status, 'idle') - set(obj.BeginExpButton, 'Enable', 'on') % Re-enable start button - end + if isempty(paramStruct); return; end + % Now parameters are loaded, pass to ParamEditor for display, etc. + if isempty(obj.ParamEditor) + panel = uipanel('Parent', obj.ParamPanel, 'Position', [0 0 1 1]); +% panel = uiextras.Panel('Parent', obj.ParamPanel); + obj.ParamEditor = eui.ParamEditor(obj.Parameters, panel); % Build parameter list in Global panel by calling eui.ParamEditor + else + obj.ParamEditor.buildUI(obj.Parameters); + end + obj.ParamEditor.addlistener('Changed', @(src,~) obj.paramChanged); + if strcmp(obj.RemoteRigs.Selected.Status, 'idle') + set(obj.BeginExpButton, 'Enable', 'on') % Re-enable start button end end diff --git a/+eui/ParamEditor.m b/+eui/ParamEditor.m index 75aca882..74fa16e4 100644 --- a/+eui/ParamEditor.m +++ b/+eui/ParamEditor.m @@ -1,34 +1,20 @@ classdef ParamEditor < handle - %EUI.PARAMEDITOR UI control for configuring experiment parameters - % TODO. See also EXP.PARAMETERS. - % - % Part of Rigbox - - % 2012-11 CB created - % 2017-03 MW/NS Made global panel scrollable & improved performance of - % buildGlobalUI. - % 2017-03 MW Added set values button + %UNTITLED2 Summary of this class goes here + % Detailed explanation goes here properties - GlobalVSpacing = 20 Parameters end - properties (Dependent) - Enable + properties %(Access = private) + GlobalUI + ConditionalUI + Parent + Listener end - properties (Access = private) - Root - GlobalGrid - ConditionTable - TableColumnParamNames = {} - NewConditionButton - DeleteConditionButton - MakeGlobalButton - SetValuesButton - SelectedCells %[row, column;...] of each selected cell - GlobalControls + properties (Dependent) + Enable end events @@ -36,120 +22,103 @@ end methods - function obj = ParamEditor(params, parent) - if nargin < 2 % Can call this function to display parameters is new window - parent = figure('Name', 'Parameters', 'NumberTitle', 'off',... - 'Toolbar', 'none', 'Menubar', 'none'); + function obj = ParamEditor(pars, f) + if nargin == 0; pars = []; end + if nargin < 2 + f = figure('Name', 'Parameters', 'NumberTitle', 'off',... + 'Toolbar', 'none', 'Menubar', 'none', 'DeleteFcn', @(~,~)obj.delete); end - obj.Parameters = params; - obj.build(parent); + obj.Parent = f; + obj.Listener = event.listener(f, 'SizeChanged', @(~,~)obj.onResize); + obj.GlobalUI = eui.FieldPanel(f, obj); + obj.ConditionalUI = eui.ConditionPanel(f, obj); + obj.buildUI(pars); end function delete(obj) - disp('ParamEditor destructor called'); - if obj.Root.isvalid - obj.Root.delete(); - end - end - - function value = get.Enable(obj) - value = obj.Root.Enable; + delete(obj.GlobalUI); + delete(obj.ConditionalUI); end - + function set.Enable(obj, value) - obj.Root.Enable = value; + cUI = obj.ConditionalUI; + fig = obj.Parent; + if value == true + arrayfun(@(prop) set(prop, 'Enable', 'on'), findobj(fig,'Enable','off')); + if isempty(cUI.SelectedCells) + set(cUI.MakeGlobalButton, 'Enable', 'off'); + set(cUI.DeleteConditionButton, 'Enable', 'off'); + set(cUI.SetValuesButton, 'Enable', 'off'); + end + obj.Enable = true; + else + arrayfun(@(prop) set(prop, 'Enable', 'off'), findobj(fig,'Enable','on')); + obj.Enable = false; + end end - end - - methods %(Access = protected) - function build(obj, parent) % Build parameters panel - obj.Root = uiextras.HBox('Parent', parent, 'Padding', 5, 'Spacing', 5); % Add horizontal container for Global and Conditional panels -% globalPanel = uiextras.Panel('Parent', obj.Root,... % Make 'Global' parameters panel -% 'Title', 'Global', 'Padding', 5); - globPanel = uiextras.Panel('Parent', obj.Root,... % Make 'Global' parameters panel - 'Title', 'Global', 'Padding', 5); - globalPanel = uix.ScrollingPanel('Parent', globPanel,... % Make 'Global' scroll panel - 'Padding', 5); - - obj.GlobalGrid = uiextras.Grid('Parent', globalPanel, 'Padding', 4); % Make grid for parameter fields - obj.buildGlobalUI; % Populate Global panel - globalPanel.Heights = sum(obj.GlobalGrid.RowSizes)+45; - - conditionPanel = uiextras.Panel('Parent', obj.Root,... - 'Title', 'Conditional', 'Padding', 5); % Make 'Conditional' parameters panel - conditionVBox = uiextras.VBox('Parent', conditionPanel); - obj.ConditionTable = uitable('Parent', conditionVBox,... - 'FontName', 'Consolas',... - 'RowName', [],... - 'CellEditCallback', @obj.cellEditCallback,... - 'CellSelectionCallback', @obj.cellSelectionCallback); + + function buildUI(obj, pars) + obj.Parameters = pars; + clear(obj.GlobalUI); + clear(obj.ConditionalUI); + c = obj.GlobalUI; + names = pars.GlobalNames; + for nm = names' + if strcmp(nm, 'randomiseConditions'); continue; end + if islogical(pars.Struct.(nm{:})) % If parameter is logical, make checkbox + ctrl = uicontrol('Parent', c.UIPanel, 'Style', 'checkbox', ... + 'Value', pars.Struct.(nm{:}), 'BackgroundColor', 'white'); + addField(c, nm{:}, ctrl); + else + [~, ctrl] = addField(c, nm{:}); + ctrl.String = obj.paramValue2Control(pars.Struct.(nm{:})); + end + end obj.fillConditionTable(); - conditionButtonBox = uiextras.HBox('Parent', conditionVBox); - conditionVBox.Sizes = [-1 25]; - obj.NewConditionButton = uicontrol('Parent', conditionButtonBox,... - 'Style', 'pushbutton',... - 'String', 'New condition',... - 'TooltipString', 'Add a new condition',... - 'Callback', @(~, ~) obj.newCondition()); - obj.DeleteConditionButton = uicontrol('Parent', conditionButtonBox,... - 'Style', 'pushbutton',... - 'String', 'Delete condition',... - 'TooltipString', 'Delete the selected condition',... - 'Enable', 'off',... - 'Callback', @(~, ~) obj.deleteSelectedConditions()); - obj.MakeGlobalButton = uicontrol('Parent', conditionButtonBox,... - 'Style', 'pushbutton',... - 'String', 'Globalise parameter',... - 'TooltipString', sprintf(['Make the selected condition-specific parameter global (i.e. not vary by trial)\n'... - 'This will move it to the global parameters section']),... - 'Enable', 'off',... - 'Callback', @(~, ~) obj.globaliseSelectedParameters()); - obj.SetValuesButton = uicontrol('Parent', conditionButtonBox,... - 'Style', 'pushbutton',... - 'String', 'Set values',... - 'TooltipString', 'Set selected values to specified value, range or function',... - 'Enable', 'off',... - 'Callback', @(~, ~) obj.setSelectedValues()); - - obj.Root.Sizes = [sum(obj.GlobalGrid.ColumnSizes) + 32, -1]; + obj.GlobalUI.onResize(); + %%% Special parameters + if ismember('randomiseConditions', obj.Parameters.Names) && ~pars.Struct.randomiseConditions + obj.ConditionalUI.ConditionTable.RowName = 'numbered'; + set(obj.ConditionalUI.ContextMenus(2), 'Checked', 'off'); + end end - function buildGlobalUI(obj) % Function to essemble global parameters - globalParamNames = fieldnames(obj.Parameters.assortForExperiment); % assortForExperiment divides params into global and trial-specific parameter structures - obj.GlobalControls = gobjects(length(globalParamNames),3); % Initialize object array (faster than assigning to end of array which results in two calls to constructor) - for i=1:length(globalParamNames) % using for loop (sorry Chris!) to populate object array 2017-02-14 MW - [obj.GlobalControls(i,1), obj.GlobalControls(i,2), obj.GlobalControls(i,3)]... % [editors, labels, buttons] - = obj.addParamUI(globalParamNames{i}); + function setRandomized(obj, value) + % If randomiseConditions doesn't exist and new value is false, add + % the parameter and set it to false + if ~ismember('randomiseConditions', obj.Parameters.Names) && value == false + description = 'Whether to randomise the conditional paramters or present them in order'; + obj.Parameters.set('randomiseConditions', false, description, 'logical') + elseif ismember('randomiseConditions', obj.Parameters.Names) + obj.update('randomiseConditions', logical(value)); + end + menu = obj.ConditionalUI.ContextMenus(2); + if value == false + obj.ConditionalUI.ConditionTable.RowName = 'numbered'; + menu.Checked = 'off'; + else + obj.ConditionalUI.ConditionTable.RowName = []; + menu.Checked = 'on'; end - % Above code replaces the following as after 2014a, MATLAB doesn't no - % longer uses numrical handles but instead uses object arrays -% [editors, labels, buttons] = cellfun(... -% @(n) obj.addParamUI(n), fieldnames(globalParams), 'UniformOutput', false); -% editors = cell2mat(editors); -% labels = cell2mat(labels); -% buttons = cell2mat(buttons); -% obj.GlobalControls = [labels, editors, buttons]; -% obj.GlobalGrid.Children = obj.GlobalControls(:); - -% obj.GlobalGrid.Children = -% blah = cat(1,obj.GlobalControls(:,1),obj.GlobalControls(:,2),obj.GlobalControls(:,3)); -% Doesn't work for some reason - MW 2017-02-15 - - child_handles = allchild(obj.GlobalGrid); % Get child handles for GlobalGrid - child_handles = [child_handles(end-1:-3:1); child_handles(end:-3:1); child_handles(end-2:-3:1)]; % Reorder them so all labels come first, then ctrls, then buttons -% child_handles = [child_handles(2:3:end); child_handles(3:3:end); child_handles(1:3:end)]; % Reorder them so all labels come first, then ctrls, then buttons - obj.GlobalGrid.Contents = child_handles; % Set children to new order - % uistack - - obj.GlobalGrid.ColumnSizes = [180, 200, 40]; % Set column sizes - obj.GlobalGrid.Spacing = 1; - obj.GlobalGrid.RowSizes = repmat(obj.GlobalVSpacing, 1, size(obj.GlobalControls, 1)); end -% function swapConditions(obj, idx1, idx2) % Function started, never -% finished - MW 2017-02-15 -% % params = obj.Parameters.trial -% end + function fillConditionTable(obj) + % Build the condition table + titles = obj.Parameters.TrialSpecificNames; + [~, trialParams] = obj.Parameters.assortForExperiment; + if isempty(titles) + obj.ConditionalUI.ButtonPanel.Visible = 'off'; + obj.ConditionalUI.UIPanel.Visible = 'off'; + obj.GlobalUI.UIPanel.Position(3) = 1; + else + obj.ConditionalUI.ButtonPanel.Visible = 'on'; + obj.ConditionalUI.UIPanel.Visible = 'on'; + data = reshape(struct2cell(trialParams), numel(titles), [])'; + data = mapToCell(@(e) obj.paramValue2Control(e), data); + set(obj.ConditionalUI.ConditionTable, 'ColumnName', titles, 'Data', data,... + 'ColumnEditable', true(1, numel(titles))); + end + end function addEmptyConditionToParam(obj, name) assert(obj.Parameters.isTrialSpecific(name),... @@ -183,217 +152,134 @@ function addEmptyConditionToParam(obj, name) obj.Parameters.Struct.(name) = cat(2, obj.Parameters.Struct.(name), newValue); end - function cellSelectionCallback(obj, src, eventData) - obj.SelectedCells = eventData.Indices; - if size(eventData.Indices, 1) > 0 - %cells selected, enable buttons - set(obj.MakeGlobalButton, 'Enable', 'on'); - set(obj.DeleteConditionButton, 'Enable', 'on'); - set(obj.SetValuesButton, 'Enable', 'on'); + function newValue = update(obj, name, value, row) + % FIXME change name to updateGlobal + if nargin < 4; row = 1; end + currValue = obj.Parameters.Struct.(name)(:,row); + if iscell(currValue) + % cell holders are allowed to be different types of value + newValue = obj.controlValue2Param(currValue{1}, value, true); + obj.Parameters.Struct.(name){:,row} = newValue; else - %nothing selected, disable buttons - set(obj.MakeGlobalButton, 'Enable', 'off'); - set(obj.DeleteConditionButton, 'Enable', 'off'); - set(obj.SetValuesButton, 'Enable', 'off'); + newValue = obj.controlValue2Param(currValue, value); + obj.Parameters.Struct.(name)(:,row) = newValue; end + notify(obj, 'Changed'); end - function newCondition(obj) - disp('adding new condition row'); - cellfun(@obj.addEmptyConditionToParam, obj.Parameters.TrialSpecificNames); - obj.fillConditionTable(); - end - - function deleteSelectedConditions(obj) - %DELETESELECTEDCONDITIONS Removes the selected conditions from table - % The callback for the 'Delete condition' button. This removes the - % selected conditions from the table and if less than two conditions - % remain, globalizes them. - % TODO: comment function better, index in a clearer fashion + function globaliseParamAtCell(obj, name, row) + % Make parameter 'name' a global parameter and set it's value to be + % that of the specified row. % - % See also EXP.PARAMETERS, GLOBALISESELECTEDPARAMETERS - rows = unique(obj.SelectedCells(:,1)); - % If the number of remaining conditions is 1 or less... - names = obj.Parameters.TrialSpecificNames; - numConditions = size(obj.Parameters.Struct.(names{1}),2); - if numConditions-length(rows) <= 1 - remainingIdx = find(all(1:numConditions~=rows,1)); - if isempty(remainingIdx); remainingIdx = 1; end - % change selected cells to be all fields (except numRepeats which - % is assumed to always be the last column) - obj.SelectedCells =[ones(length(names)-1,1)*remainingIdx, (1:length(names)-1)']; - %... globalize them - obj.globaliseSelectedParameters; - obj.Parameters.removeConditions(rows) -% for i = 1:numel(names) -% newValue = iff(any(remainingIdx), obj.Struct.(names{i})(:,remainingIdx), obj.Struct.(names{i})(1)); -% % If the parameter is Num repeats, set the value -% if strcmp(names{i}, 'numRepeats') -% obj.Struct.(names{i}) = newValue; -% else -% obj.makeGlobal(names{i}, newValue); -% end -% end - else % Otherwise delete the selected conditions as usual - obj.Parameters.removeConditions(rows); - end - obj.fillConditionTable(); %refresh the table of conditions - end - - function globaliseSelectedParameters(obj) - [cols, iu] = unique(obj.SelectedCells(:,2)); - names = obj.TableColumnParamNames(cols); - rows = obj.SelectedCells(iu,1); %get rows of unique selected cols - arrayfun(@obj.globaliseParamAtCell, rows, cols); - obj.fillConditionTable(); %refresh the table of conditions - %now add global controls for parameters - newGlobals = gobjects(length(names),3); % Initialize object array (faster than assigning to end of array which results in two calls to constructor) - for i=length(names):-1:1 % using for loop (sorry Chris!) to initialize and populate object array 2017-02-15 MW - [newGlobals(i,1), newGlobals(i,2), newGlobals(i,3)]... % [editors, labels, buttons] - = obj.addParamUI(names{i}); - end - -% [editors, labels, buttons] = arrayfun(@obj.addParamUI, names); % -% 2017-02-15 MW can no longer use arrayfun with object outputs - idx = size(obj.GlobalControls, 1); % Calculate number of current Global params - new = numel(newGlobals); - obj.GlobalControls = [obj.GlobalControls; newGlobals]; % Add new globals to object - ggHandles = obj.GlobalGrid.Contents; - ggHandles = [ggHandles(1:idx); ggHandles((end-new+2):3:end);... - ggHandles(idx+1:idx*2); ggHandles((end-new+1):3:end);... - ggHandles(idx*2+1:idx*3); ggHandles((end-new+3):3:end)]; % Reorder them so all labels come first, then ctrls, then buttons - obj.GlobalGrid.Contents = ggHandles; % Set children to new order - - % Reset sizes - obj.GlobalGrid.RowSizes = repmat(obj.GlobalVSpacing, 1, size(obj.GlobalControls, 1)); - set(get(obj.GlobalGrid, 'Parent'),... - 'Heights', sum(obj.GlobalGrid.RowSizes)+45); % Reset height of globalPanel - obj.GlobalGrid.ColumnSizes = [180, 200, 40]; - obj.GlobalGrid.Spacing = 1; - end - - function globaliseParamAtCell(obj, row, col) - name = obj.TableColumnParamNames{col}; + % See also EXP.PARAMETERS/MAKEGLOBAL, UI.CONDITIONPANEL/MAKEGLOBAL value = obj.Parameters.Struct.(name)(:,row); obj.Parameters.makeGlobal(name, value); - end - - function setSelectedValues(obj) % Set multiple fields in conditional table - disp('updating table cells'); - cols = obj.SelectedCells(:,2); % selected columns - uCol = unique(obj.SelectedCells(:,2)); - rows = obj.SelectedCells(:,1); % selected rows - % get current values of selected cells - currVals = arrayfun(@(u)obj.ConditionTable.Data(rows(cols==u),u), uCol, 'UniformOutput', 0); - names = obj.TableColumnParamNames(uCol); % selected column names - promt = cellfun(@(a,b) [a ' (' num2str(sum(cols==b)) ')'],... - names, num2cell(uCol), 'UniformOutput', 0); % names of columns & num selected rows - defaultans = cellfun(@(c) c(1), currVals); - answer = inputdlg(promt,'Set values', 1, cellflat(defaultans)); % prompt for input - if isempty(answer) % if user presses cancel - return + % Refresh the table of conditions + obj.fillConditionTable; + % Add new global parameter to field panel + if islogical(value) % If parameter is logical, make checkbox + ctrl = uicontrol('Parent', obj.GlobalUI.UIPanel, 'Style', 'checkbox', ... + 'Value', value, 'BackgroundColor', 'white'); + addField(obj.GlobalUI, name, ctrl); + else + [~, ctrl] = addField(obj.GlobalUI, name); + ctrl.String = obj.paramValue2Control(value); end - % set values for each column - cellfun(@(a,b,c) setNewVals(a,b,c), answer, currVals, names, 'UniformOutput', 0); - function newVals = setNewVals(userIn, currVals, paramName) - % check array orientation - currVals = iff(size(currVals,1)>size(currVals,2),currVals',currVals); - if strStartsWith(userIn,'@') % anon function - func_h = str2func(userIn); - % apply function to each cell - currVals = cellfun(@str2double,currVals, 'UniformOutput', 0); % convert from char - newVals = cellfun(func_h, currVals, 'UniformOutput', 0); - elseif any(userIn==':') % array syntax - arr = eval(userIn); - newVals = num2cell(arr); % convert to cell array - elseif any(userIn==','|userIn==';') % 2D arrays - C = strsplit(userIn, ';'); - newVals = cellfun(@(c)textscan(c, '%f',... - 'ReturnOnError', false,... - 'delimiter', {' ', ','}, 'MultipleDelimsAsOne', 1),... - C); - else % single value to copy across all cells - userIn = str2double(userIn); - newVals = num2cell(ones(size(currVals))*userIn); - end - - if length(newVals)>length(currVals) % too many new values - newVals = newVals(1:length(currVals)); % truncate new array - elseif length(newVals)<length(currVals) % too few new values - % populate as many cells as possible - newVals = [newVals ... - cellfun(@(a)obj.controlValue2Param(2,a),... - currVals(length(newVals)+1:end),'UniformOutput',0)]; - end - ic = strcmp(obj.TableColumnParamNames,paramName); % find edited param names - % update param struct - obj.Parameters.Struct.(paramName)(:,rows(cols==find(ic))) = cell2mat(newVals); - % update condtion table with strings - obj.ConditionTable.Data(rows(cols==find(ic)),ic)... - = cellfun(@(a)obj.paramValue2Control(a), newVals', 'UniformOutput', 0); - end - notify(obj, 'Changed'); + obj.GlobalUI.onResize(); + obj.notify('Changed'); end - - function cellEditCallback(obj, src, eventData) - disp('updating table cell'); - row = eventData.Indices(1); - col = eventData.Indices(2); - paramName = obj.TableColumnParamNames{col}; - currValue = obj.Parameters.Struct.(paramName)(:,row); - if iscell(currValue) - % cell holders are allowed to be different types of value - newParam = obj.controlValue2Param(currValue{1}, eventData.NewData, true); - obj.Parameters.Struct.(paramName){:,row} = newParam; + + function onResize(obj) + %%% resize condition table + notify(obj.ConditionalUI.ButtonPanel, 'SizeChanged'); + cUI = obj.ConditionalUI.UIPanel; + gUI = obj.GlobalUI.UIPanel; + + pos = obj.GlobalUI.Controls(end).Position; + colExtent = pos(1) + pos(3) + obj.GlobalUI.Margin; + colWidth = pos(3) + obj.GlobalUI.Margin + obj.GlobalUI.ColSpacing; % FIXME: inaccurate + pos = getpixelposition(gUI); + gUIExtent = pos(3); + pos = getpixelposition(cUI); + cUIExtent = pos(3); + + extent = get(obj.ConditionalUI.ConditionTable, 'Extent'); + panelWidth = cUI.Position(3); + if colExtent > gUIExtent && cUIExtent > obj.ConditionalUI.MinWidth + % If global UI controls are cut off and there is no dead space in + % the table but the minimum table width hasn't been reached, reduce + % the conditional UI width: table has scroll bar and global panel + % does not + % FIXME calculate how much space required for min control width +% obj.GlobalUI.MinCtrlWidth + % Calculate conditional UI width in normalized units + requiredWidth = (cUI.Position(3) / cUIExtent) * (colExtent - gUIExtent); + minConditionalWidth = (cUI.Position(3) / cUIExtent) * obj.ConditionalUI.MinWidth; + if requiredWidth < minConditionalWidth + % If the required width is smaller that the minimum table width, + % use minimum table width + cUI.Position(3) = minConditionalWidth; + else % Otherwise use this width + cUI.Position(3) = requiredWidth; + end + cUI.Position(1) = 1-cUI.Position(3); + gUI.Position(3) = 1-cUI.Position(3); + elseif extent(3) < 1 && colWidth < obj.GlobalUI.MaxCtrlWidth + % If there is dead table space and the global UI columns are cut + % off or squashed, reduce the conditional panel + cUI.Position(3) = cUI.Position(3) - (panelWidth - (panelWidth * extent(3))); + cUI.Position(1) = cUI.Position(1) + (panelWidth - (panelWidth * extent(3))); + gUI.Position(3) = cUI.Position(1); + elseif extent(3) < 1 && colExtent < gUIExtent + % Plenty of space! Increase conditional UI a bit + deadspace = gUIExtent - colExtent; % Spece between panels in pixels + % Convert global UI pixels to relative units + gUI.Position(3) = (gUI.Position(3) / gUIExtent) * (gUIExtent - (deadspace/2)); + cUI.Position(1) = gUI.Position(3); + cUI.Position(3) = 1-gUI.Position(3); + elseif extent(3) >= 1 && colExtent < gUIExtent + % If the table space is cut off and there is dead space in the + % global UI panel, reduce the global UI panel + % If the extra space is minimum, return + if floor(gUIExtent - colExtent) <= 2; return; end + deadspace = gUIExtent - colExtent; % Spece between panels in pixels + gUI.Position(3) = (gUI.Position(3) / gUIExtent) * (gUIExtent - deadspace); + cUI.Position(3) = 1-gUI.Position(3); + cUI.Position(1) = gUI.Position(3); else - newParam = obj.controlValue2Param(currValue, eventData.NewData); - obj.Parameters.Struct.(paramName)(:,row) = newParam; - end - % if successful update the cell with default formatting - data = get(src, 'Data'); - reformed = obj.paramValue2Control(newParam); - if iscell(reformed) - % the reformed data type is a cell, this should be a one element - % wrapping cell - if numel(reformed) == 1 - reformed = reformed{1}; - else - error('Cannot handle data reformatted data type'); - end + % Compromise by having both panels take up half the figure +% [cUI.Position([1,3]), gUI.Position(3)] = deal(0.5); end - data{row,col} = reformed; - set(src, 'Data', data); - %notify listeners of change - notify(obj, 'Changed'); + notify(obj.ConditionalUI.ButtonPanel, 'SizeChanged'); end - - function updateGlobal(obj, param, src) - currParamValue = obj.Parameters.Struct.(param); - switch get(src, 'style') - case 'checkbox' - newValue = logical(get(src, 'value')); - obj.Parameters.Struct.(param) = newValue; - case 'edit' - newValue = obj.controlValue2Param(currParamValue, get(src, 'string')); - obj.Parameters.Struct.(param) = newValue; - % if successful update the control with default formatting and - % modified colour - set(src, 'String', obj.paramValue2Control(newValue),... - 'ForegroundColor', [1 0 0]); %red indicating it has changed - %notify listeners of change - notify(obj, 'Changed'); + end + + methods (Static) + function data = paramValue2Control(data) + % convert from parameter value to control value, i.e. a value class + % that can be easily displayed and edited by the user. Everything + % except logicals are converted to charecter arrays. + switch class(data) + case 'function_handle' + % convert a function handle to it's string name + data = func2str(data); + case 'logical' + data = data ~= 0; % If logical do nothing, basically. + case 'string' + data = char(data); % Strings not allowed in condition table data + otherwise + if isnumeric(data) + % format numeric types as string number list + strlist = mapToCell(@num2str, data); + data = strJoin(strlist, ', '); + elseif iscellstr(data) + data = strJoin(data, ', '); + end end - end - - function [data, paramNames, titles] = tableData(obj) - [~, trialParams] = obj.Parameters.assortForExperiment; - paramNames = fieldnames(trialParams); - titles = obj.Parameters.title(paramNames); - data = reshape(struct2cell(trialParams), numel(paramNames), [])'; - data = mapToCell(@(e) obj.paramValue2Control(e), data); + % all other data types stay as they are end - function data = controlValue2Param(obj, currParam, data, allowTypeChange) + function data = controlValue2Param(currParam, data, allowTypeChange) % Convert the values displayed in the UI ('control values') to % parameter values. String representations of numrical arrays and % functions are converted back to their 'native' classes. @@ -432,111 +318,7 @@ function updateGlobal(obj, param, src) end end end - - function data = paramValue2Control(obj, data) - % convert from parameter value to control value, i.e. a value class - % that can be easily displayed and edited by the user. Everything - % except logicals are converted to charecter arrays. - switch class(data) - case 'function_handle' - % convert a function handle to it's string name - data = func2str(data); - case 'logical' - data = data ~= 0; % If logical do nothing, basically. - case 'string' - data = char(data); % Strings not allowed in condition table data - otherwise - if isnumeric(data) - % format numeric types as string number list - strlist = mapToCell(@num2str, data); - data = strJoin(strlist, ', '); - elseif iscellstr(data) - data = strJoin(data, ', '); - end - end - % all other data types stay as they are - end - - function fillConditionTable(obj) - [data, params, titles] = obj.tableData; - set(obj.ConditionTable, 'ColumnName', titles, 'Data', data,... - 'ColumnEditable', true(1, numel(titles))); - obj.TableColumnParamNames = params; - end - - function makeTrialSpecific(obj, paramName, ctrls) - [uirow, ~] = find(obj.GlobalControls == ctrls{1}); - assert(numel(uirow) == 1, 'Unexpected number of matching global controls'); - cellfun(@(c) delete(c), ctrls); - obj.GlobalControls(uirow,:) = []; - obj.GlobalGrid.RowSizes(uirow) = []; - obj.Parameters.makeTrialSpecific(paramName); - obj.fillConditionTable(); - set(get(obj.GlobalGrid, 'Parent'),... - 'Heights', sum(obj.GlobalGrid.RowSizes)+45); % Reset height of globalPanel - end - - function [ctrl, label, buttons] = addParamUI(obj, name) % Adds ui element for each parameter - parent = obj.GlobalGrid; % Made by build function above - ctrl = []; - label = []; - buttons = []; - if iscell(name) % 2017-02-14 MW function now called with arrayFun (instead of cellFun) - name = name{1,1}; - end - value = obj.paramValue2Control(obj.Parameters.Struct.(name)); % convert from parameter value to control value (everything but logical values become strings) - title = obj.Parameters.title(name); - description = obj.Parameters.description(name); - - if islogical(value) % If parameter is logical, make checkbox - for i = 1:length(value) - ctrl(end+1) = uicontrol('Parent', parent,... - 'Style', 'checkbox',... - 'TooltipString', description,... - 'Value', value(i),... % Added 2017-02-15 MW set checkbox to what ever the parameter value is - 'Callback', @(src, e) obj.updateGlobal(name, src)); - end - elseif ischar(value) - ctrl = uicontrol('Parent', parent,... - 'BackgroundColor', [1 1 1],... - 'Style', 'edit',... - 'String', value,... - 'TooltipString', description,... - 'UserData', name,... % save the name of the parameter in userdata - 'HorizontalAlignment', 'left',... - 'Callback', @(src, e) obj.updateGlobal(name, src)); -% elseif iscellstr(value) -% lines = mkStr(value, [], sprintf('\n'), []); -% ctrl = uicontrol('Parent', parent,... -% 'BackgroundColor', [1 1 1],... -% 'Style', 'edit',... -% 'Max', 2,... %make it multiline -% 'String', lines,... -% 'TooltipString', description,... -% 'HorizontalAlignment', 'left',... -% 'UserData', name,... % save the name of the parameter in userdata -% 'Callback', @(src, e) obj.updateGlobal(name, src)); - end - if ~isempty(ctrl) % If control box is made, add label and conditional button - label = uicontrol('Parent', parent,... - 'Style', 'text', 'String', title, 'HorizontalAlignment', 'left',... - 'TooltipString', description); % Why not use bui.label? MW 2017-02-15 - bbox = uiextras.HBox('Parent', parent); % Make HBox for button - % UIContainer no longer present in GUILayoutToolbox, it used to - % call uipanel with the following args: - % 'Units', 'Normalized'; 'BorderType', 'none') -% buttons = bbox.UIContainer; - buttons = uicontrol('Parent', bbox, 'Style', 'pushbutton',... % Make 'conditional parameter' button - 'String', '[...]',... - 'TooltipString', sprintf(['Make this a condition parameter (i.e. vary by trial).\n'... - 'This will move it to the trial conditions table.']),... - 'FontSize', 7,... - 'Callback', @(~,~) obj.makeTrialSpecific(name, {ctrl, label, bbox})); - bbox.Sizes = 29; % Resize button height to 29px - end - end end - end diff --git a/+eui/ParamEditor_old.m b/+eui/ParamEditor_old.m new file mode 100644 index 00000000..05855aef --- /dev/null +++ b/+eui/ParamEditor_old.m @@ -0,0 +1,516 @@ +classdef ParamEditor < handle + %EUI.PARAMEDITOR UI control for configuring experiment parameters + % TODO. See also EXP.PARAMETERS. + % + % Part of Rigbox + + % 2012-11 CB created + % 2017-03 MW/NS Made global panel scrollable & improved performance of + % buildGlobalUI. + % 2017-03 MW Added set values button + + properties + GlobalVSpacing = 20 + Parameters + end + + properties (Dependent) + Enable + end + + properties (Access = private) + Root + GlobalGrid + ConditionTable + TableColumnParamNames = {} + NewConditionButton + DeleteConditionButton + MakeGlobalButton + SetValuesButton + SelectedCells %[row, column;...] of each selected cell + GlobalControls + end + + events + Changed + end + + methods + function obj = ParamEditor(params, parent) + if nargin < 2 % Can call this function to display parameters is new window + parent = figure('Name', 'Parameters', 'NumberTitle', 'off',... + 'Toolbar', 'none', 'Menubar', 'none'); + end + obj.Parameters = params; + obj.build(parent); + end + + function delete(obj) + disp('ParamEditor destructor called'); + if obj.Root.isvalid + obj.Root.delete(); + end + end + + function value = get.Enable(obj) + value = obj.Root.Enable; + end + + function set.Enable(obj, value) + obj.Root.Enable = value; + end + end + + methods %(Access = protected) + function build(obj, parent) % Build parameters panel + obj.Root = uiextras.HBox('Parent', parent, 'Padding', 5, 'Spacing', 5); % Add horizontal container for Global and Conditional panels +% globalPanel = uiextras.Panel('Parent', obj.Root,... % Make 'Global' parameters panel +% 'Title', 'Global', 'Padding', 5); + globPanel = uiextras.Panel('Parent', obj.Root,... % Make 'Global' parameters panel + 'Title', 'Global', 'Padding', 5); + globalPanel = uix.ScrollingPanel('Parent', globPanel,... % Make 'Global' scroll panel + 'Padding', 5); + + obj.GlobalGrid = uiextras.Grid('Parent', globalPanel, 'Padding', 4); % Make grid for parameter fields + obj.buildGlobalUI; % Populate Global panel + globalPanel.Heights = sum(obj.GlobalGrid.RowSizes)+45; + + conditionPanel = uiextras.Panel('Parent', obj.Root,... + 'Title', 'Conditional', 'Padding', 5); % Make 'Conditional' parameters panel + conditionVBox = uiextras.VBox('Parent', conditionPanel); + obj.ConditionTable = uitable('Parent', conditionVBox,... + 'FontName', 'Consolas',... + 'RowName', [],... + 'CellEditCallback', @obj.cellEditCallback,... + 'CellSelectionCallback', @obj.cellSelectionCallback); + obj.fillConditionTable(); + conditionButtonBox = uiextras.HBox('Parent', conditionVBox); + conditionVBox.Sizes = [-1 25]; + obj.NewConditionButton = uicontrol('Parent', conditionButtonBox,... + 'Style', 'pushbutton',... + 'String', 'New condition',... + 'TooltipString', 'Add a new condition',... + 'Callback', @(~, ~) obj.newCondition()); + obj.DeleteConditionButton = uicontrol('Parent', conditionButtonBox,... + 'Style', 'pushbutton',... + 'String', 'Delete condition',... + 'TooltipString', 'Delete the selected condition',... + 'Enable', 'off',... + 'Callback', @(~, ~) obj.deleteSelectedConditions()); + obj.MakeGlobalButton = uicontrol('Parent', conditionButtonBox,... + 'Style', 'pushbutton',... + 'String', 'Globalise parameter',... + 'TooltipString', sprintf(['Make the selected condition-specific parameter global (i.e. not vary by trial)\n'... + 'This will move it to the global parameters section']),... + 'Enable', 'off',... + 'Callback', @(~, ~) obj.globaliseSelectedParameters()); + obj.SetValuesButton = uicontrol('Parent', conditionButtonBox,... + 'Style', 'pushbutton',... + 'String', 'Set values',... + 'TooltipString', 'Set selected values to specified value, range or function',... + 'Enable', 'off',... + 'Callback', @(~, ~) obj.setSelectedValues()); + + obj.Root.Sizes = [sum(obj.GlobalGrid.ColumnSizes) + 32, -1]; + end + + function buildGlobalUI(obj) % Function to essemble global parameters + globalParamNames = fieldnames(obj.Parameters.assortForExperiment); % assortForExperiment divides params into global and trial-specific parameter structures + obj.GlobalControls = gobjects(length(globalParamNames),3); % Initialize object array (faster than assigning to end of array which results in two calls to constructor) + for i=1:length(globalParamNames) % using for loop (sorry Chris!) to populate object array 2017-02-14 MW + [obj.GlobalControls(i,1), obj.GlobalControls(i,2), obj.GlobalControls(i,3)]... % [editors, labels, buttons] + = obj.addParamUI(globalParamNames{i}); + end + + child_handles = allchild(obj.GlobalGrid); % Get child handles for GlobalGrid + child_handles = [child_handles(end-1:-3:1); child_handles(end:-3:1); child_handles(end-2:-3:1)]; % Reorder them so all labels come first, then ctrls, then buttons + obj.GlobalGrid.Contents = child_handles; % Set children to new order + + obj.GlobalGrid.ColumnSizes = [180, 200, 40]; % Set column sizes + obj.GlobalGrid.Spacing = 1; + obj.GlobalGrid.RowSizes = repmat(obj.GlobalVSpacing, 1, size(obj.GlobalControls, 1)); + end + +% function swapConditions(obj, idx1, idx2) % Function started, never +% finished - MW 2017-02-15 +% % params = obj.Parameters.trial +% end + + function addEmptyConditionToParam(obj, name) + assert(obj.Parameters.isTrialSpecific(name),... + 'Tried to add a new condition to global parameter ''%s''', name); + % work out what the right 'empty' is for the parameter + currValue = obj.Parameters.Struct.(name); + if isnumeric(currValue) + newValue = zeros(size(currValue, 1), 1, class(currValue)); + elseif islogical(currValue) + newValue = false(size(currValue, 1), 1); + elseif iscell(currValue) + if numel(currValue) > 0 + if iscellstr(currValue) + % if all elements are strings, default to a blank string + newValue = {''}; + elseif isa(currValue{1}, 'function_handle') + % first element is a function handle, so create with a @nop + % handle + newValue = {@nop}; + else + % misc cell case - default to empty element + newValue = {[]}; + end + else + % misc case - default to empty element + newValue = {[]}; + end + else + error('Adding empty condition for ''%s'' type not implemented', class(currValue)); + end + obj.Parameters.Struct.(name) = cat(2, obj.Parameters.Struct.(name), newValue); + end + + function cellSelectionCallback(obj, src, eventData) + obj.SelectedCells = eventData.Indices; + if size(eventData.Indices, 1) > 0 + %cells selected, enable buttons + set(obj.MakeGlobalButton, 'Enable', 'on'); + set(obj.DeleteConditionButton, 'Enable', 'on'); + set(obj.SetValuesButton, 'Enable', 'on'); + else + %nothing selected, disable buttons + set(obj.MakeGlobalButton, 'Enable', 'off'); + set(obj.DeleteConditionButton, 'Enable', 'off'); + set(obj.SetValuesButton, 'Enable', 'off'); + end + end + + function newCondition(obj) + disp('adding new condition row'); + cellfun(@obj.addEmptyConditionToParam, obj.Parameters.TrialSpecificNames); + obj.fillConditionTable(); + end + + function deleteSelectedConditions(obj) + %DELETESELECTEDCONDITIONS Removes the selected conditions from table + % The callback for the 'Delete condition' button. This removes the + % selected conditions from the table and if less than two conditions + % remain, globalizes them. + % TODO: comment function better, index in a clearer fashion + % + % See also EXP.PARAMETERS, GLOBALISESELECTEDPARAMETERS + rows = unique(obj.SelectedCells(:,1)); + % If the number of remaining conditions is 1 or less... + names = obj.Parameters.TrialSpecificNames; + numConditions = size(obj.Parameters.Struct.(names{1}),2); + if numConditions-length(rows) <= 1 + remainingIdx = find(all(1:numConditions~=rows,1)); + if isempty(remainingIdx); remainingIdx = 1; end + % change selected cells to be all fields (except numRepeats which + % is assumed to always be the last column) + obj.SelectedCells =[ones(length(names)-1,1)*remainingIdx, (1:length(names)-1)']; + %... globalize them + obj.globaliseSelectedParameters; + obj.Parameters.removeConditions(rows) + else % Otherwise delete the selected conditions as usual + obj.Parameters.removeConditions(rows); + end + obj.fillConditionTable(); %refresh the table of conditions + end + + function globaliseSelectedParameters(obj) + [cols, iu] = unique(obj.SelectedCells(:,2)); + names = obj.TableColumnParamNames(cols); + rows = obj.SelectedCells(iu,1); %get rows of unique selected cols + arrayfun(@obj.globaliseParamAtCell, rows, cols); + obj.fillConditionTable(); %refresh the table of conditions + %now add global controls for parameters + newGlobals = gobjects(length(names),3); % Initialize object array (faster than assigning to end of array which results in two calls to constructor) + for i=length(names):-1:1 % using for loop (sorry Chris!) to initialize and populate object array 2017-02-15 MW + [newGlobals(i,1), newGlobals(i,2), newGlobals(i,3)]... % [editors, labels, buttons] + = obj.addParamUI(names{i}); + end + + idx = size(obj.GlobalControls, 1); % Calculate number of current Global params + new = numel(newGlobals); + obj.GlobalControls = [obj.GlobalControls; newGlobals]; % Add new globals to object + ggHandles = obj.GlobalGrid.Contents; + ggHandles = [ggHandles(1:idx); ggHandles((end-new+2):3:end);... + ggHandles(idx+1:idx*2); ggHandles((end-new+1):3:end);... + ggHandles(idx*2+1:idx*3); ggHandles((end-new+3):3:end)]; % Reorder them so all labels come first, then ctrls, then buttons + obj.GlobalGrid.Contents = ggHandles; % Set children to new order + + % Reset sizes + obj.GlobalGrid.RowSizes = repmat(obj.GlobalVSpacing, 1, size(obj.GlobalControls, 1)); + set(get(obj.GlobalGrid, 'Parent'),... + 'Heights', sum(obj.GlobalGrid.RowSizes)+45); % Reset height of globalPanel + obj.GlobalGrid.ColumnSizes = [180, 200, 40]; + obj.GlobalGrid.Spacing = 1; + end + + function globaliseParamAtCell(obj, row, col) + name = obj.TableColumnParamNames{col}; + value = obj.Parameters.Struct.(name)(:,row); + obj.Parameters.makeGlobal(name, value); + end + + function setSelectedValues(obj) % Set multiple fields in conditional table + disp('updating table cells'); + cols = obj.SelectedCells(:,2); % selected columns + uCol = unique(obj.SelectedCells(:,2)); + rows = obj.SelectedCells(:,1); % selected rows + % get current values of selected cells + currVals = arrayfun(@(u)obj.ConditionTable.Data(rows(cols==u),u), uCol, 'UniformOutput', 0); + names = obj.TableColumnParamNames(uCol); % selected column names + promt = cellfun(@(a,b) [a ' (' num2str(sum(cols==b)) ')'],... + names, num2cell(uCol), 'UniformOutput', 0); % names of columns & num selected rows + defaultans = cellfun(@(c) c(1), currVals); + answer = inputdlg(promt,'Set values', 1, cellflat(defaultans)); % prompt for input + if isempty(answer) % if user presses cancel + return + end + % set values for each column + cellfun(@(a,b,c) setNewVals(a,b,c), answer, currVals, names, 'UniformOutput', 0); + function newVals = setNewVals(userIn, currVals, paramName) + % check array orientation + currVals = iff(size(currVals,1)>size(currVals,2),currVals',currVals); + if strStartsWith(userIn,'@') % anon function + func_h = str2func(userIn); + % apply function to each cell + currVals = cellfun(@str2double,currVals, 'UniformOutput', 0); % convert from char + newVals = cellfun(func_h, currVals, 'UniformOutput', 0); + elseif any(userIn==':') % array syntax + arr = eval(userIn); + newVals = num2cell(arr); % convert to cell array + elseif any(userIn==','|userIn==';') % 2D arrays + C = strsplit(userIn, ';'); + newVals = cellfun(@(c)textscan(c, '%f',... + 'ReturnOnError', false,... + 'delimiter', {' ', ','}, 'MultipleDelimsAsOne', 1),... + C); + else % single value to copy across all cells + userIn = str2double(userIn); + newVals = num2cell(ones(size(currVals))*userIn); + end + + if length(newVals)>length(currVals) % too many new values + newVals = newVals(1:length(currVals)); % truncate new array + elseif length(newVals)<length(currVals) % too few new values + % populate as many cells as possible + newVals = [newVals ... + cellfun(@(a)obj.controlValue2Param(2,a),... + currVals(length(newVals)+1:end),'UniformOutput',0)]; + end + ic = strcmp(obj.TableColumnParamNames,paramName); % find edited param names + % update param struct + obj.Parameters.Struct.(paramName)(:,rows(cols==find(ic))) = cell2mat(newVals); + % update condtion table with strings + obj.ConditionTable.Data(rows(cols==find(ic)),ic)... + = cellfun(@(a)obj.paramValue2Control(a), newVals', 'UniformOutput', 0); + end + notify(obj, 'Changed'); + end + + function cellEditCallback(obj, src, eventData) + disp('updating table cell'); + row = eventData.Indices(1); + col = eventData.Indices(2); + paramName = obj.TableColumnParamNames{col}; + currValue = obj.Parameters.Struct.(paramName)(:,row); + if iscell(currValue) + % cell holders are allowed to be different types of value + newParam = obj.controlValue2Param(currValue{1}, eventData.NewData, true); + obj.Parameters.Struct.(paramName){:,row} = newParam; + else + newParam = obj.controlValue2Param(currValue, eventData.NewData); + obj.Parameters.Struct.(paramName)(:,row) = newParam; + end + % if successful update the cell with default formatting + data = get(src, 'Data'); + reformed = obj.paramValue2Control(newParam); + if iscell(reformed) + % the reformed data type is a cell, this should be a one element + % wrapping cell + if numel(reformed) == 1 + reformed = reformed{1}; + else + error('Cannot handle data reformatted data type'); + end + end + data{row,col} = reformed; + set(src, 'Data', data); + %notify listeners of change + notify(obj, 'Changed'); + end + + function updateGlobal(obj, param, src) + currParamValue = obj.Parameters.Struct.(param); + switch get(src, 'style') + case 'checkbox' + newValue = logical(get(src, 'value')); + obj.Parameters.Struct.(param) = newValue; + case 'edit' + newValue = obj.controlValue2Param(currParamValue, get(src, 'string')); + obj.Parameters.Struct.(param) = newValue; + % if successful update the control with default formatting and + % modified colour + set(src, 'String', obj.paramValue2Control(newValue),... + 'ForegroundColor', [1 0 0]); %red indicating it has changed + %notify listeners of change + notify(obj, 'Changed'); + end + end + + function [data, paramNames, titles] = tableData(obj) + [~, trialParams] = obj.Parameters.assortForExperiment; + paramNames = fieldnames(trialParams); + titles = obj.Parameters.title(paramNames); + data = reshape(struct2cell(trialParams), numel(paramNames), [])'; + data = mapToCell(@(e) obj.paramValue2Control(e), data); + end + + function data = controlValue2Param(obj, currParam, data, allowTypeChange) + % Convert the values displayed in the UI ('control values') to + % parameter values. String representations of numrical arrays and + % functions are converted back to their 'native' classes. + if nargin < 4 + allowTypeChange = false; + end + switch class(currParam) + case 'function_handle' + data = str2func(data); + case 'logical' + data = data ~= 0; + case 'char' + % do nothing - strings stay as strings + otherwise + if isnumeric(currParam) + % parse string as numeric vector + try + C = textscan(data, '%f',... + 'ReturnOnError', false,... + 'delimiter', {' ', ','}, 'MultipleDelimsAsOne', 1); + data = C{1}; + catch ex + % if a type change is allowed, then a numeric can become a + % string, otherwise rethrow the parse error + if ~allowTypeChange + rethrow(ex); + end + end + elseif iscellstr(currParam) + C = textscan(data, '%s',... + 'ReturnOnError', false,... + 'delimiter', {' ', ','}, 'MultipleDelimsAsOne', 1); + data = C{1};%deblank(num2cell(data, 2)); + else + error('Cannot update unimplemented type ''%s''', class(currParam)); + end + end + end + + function data = paramValue2Control(obj, data) + % convert from parameter value to control value, i.e. a value class + % that can be easily displayed and edited by the user. Everything + % except logicals are converted to charecter arrays. + switch class(data) + case 'function_handle' + % convert a function handle to it's string name + data = func2str(data); + case 'logical' + data = data ~= 0; % If logical do nothing, basically. + case 'string' + data = char(data); % Strings not allowed in condition table data + otherwise + if isnumeric(data) + % format numeric types as string number list + strlist = mapToCell(@num2str, data); + data = strJoin(strlist, ', '); + elseif iscellstr(data) + data = strJoin(data, ', '); + end + end + % all other data types stay as they are + end + + function fillConditionTable(obj) + [data, params, titles] = obj.tableData; + set(obj.ConditionTable, 'ColumnName', titles, 'Data', data,... + 'ColumnEditable', true(1, numel(titles))); + obj.TableColumnParamNames = params; + end + + function makeTrialSpecific(obj, paramName, ctrls) + [uirow, ~] = find(obj.GlobalControls == ctrls{1}); + assert(numel(uirow) == 1, 'Unexpected number of matching global controls'); + cellfun(@(c) delete(c), ctrls); + obj.GlobalControls(uirow,:) = []; + obj.GlobalGrid.RowSizes(uirow) = []; + obj.Parameters.makeTrialSpecific(paramName); + obj.fillConditionTable(); + set(get(obj.GlobalGrid, 'Parent'),... + 'Heights', sum(obj.GlobalGrid.RowSizes)+45); % Reset height of globalPanel + end + + function [ctrl, label, buttons] = addParamUI(obj, name) % Adds ui element for each parameter + parent = obj.GlobalGrid; % Made by build function above + ctrl = []; + label = []; + buttons = []; + if iscell(name) % 2017-02-14 MW function now called with arrayFun (instead of cellFun) + name = name{1,1}; + end + value = obj.paramValue2Control(obj.Parameters.Struct.(name)); % convert from parameter value to control value (everything but logical values become strings) + title = obj.Parameters.title(name); + description = obj.Parameters.description(name); + + if islogical(value) % If parameter is logical, make checkbox + for i = 1:length(value) + ctrl(end+1) = uicontrol('Parent', parent,... + 'Style', 'checkbox',... + 'TooltipString', description,... + 'Value', value(i),... % Added 2017-02-15 MW set checkbox to what ever the parameter value is + 'Callback', @(src, e) obj.updateGlobal(name, src)); + end + elseif ischar(value) + ctrl = uicontrol('Parent', parent,... + 'BackgroundColor', [1 1 1],... + 'Style', 'edit',... + 'String', value,... + 'TooltipString', description,... + 'UserData', name,... % save the name of the parameter in userdata + 'HorizontalAlignment', 'left',... + 'Callback', @(src, e) obj.updateGlobal(name, src)); +% elseif iscellstr(value) +% lines = mkStr(value, [], sprintf('\n'), []); +% ctrl = uicontrol('Parent', parent,... +% 'BackgroundColor', [1 1 1],... +% 'Style', 'edit',... +% 'Max', 2,... %make it multiline +% 'String', lines,... +% 'TooltipString', description,... +% 'HorizontalAlignment', 'left',... +% 'UserData', name,... % save the name of the parameter in userdata +% 'Callback', @(src, e) obj.updateGlobal(name, src)); + end + + if ~isempty(ctrl) % If control box is made, add label and conditional button + label = uicontrol('Parent', parent,... + 'Style', 'text', 'String', title, 'HorizontalAlignment', 'left',... + 'TooltipString', description); % Why not use bui.label? MW 2017-02-15 + bbox = uiextras.HBox('Parent', parent); % Make HBox for button + % UIContainer no longer present in GUILayoutToolbox, it used to + % call uipanel with the following args: + % 'Units', 'Normalized'; 'BorderType', 'none') +% buttons = bbox.UIContainer; + buttons = uicontrol('Parent', bbox, 'Style', 'pushbutton',... % Make 'conditional parameter' button + 'String', '[...]',... + 'TooltipString', sprintf(['Make this a condition parameter (i.e. vary by trial).\n'... + 'This will move it to the trial conditions table.']),... + 'FontSize', 7,... + 'Callback', @(~,~) obj.makeTrialSpecific(name, {ctrl, label, bbox})); + bbox.Sizes = 29; % Resize button height to 29px + end + end + end + +end + diff --git a/+exp/Parameters.m b/+exp/Parameters.m index 4fa2d9ce..a48e3d09 100644 --- a/+exp/Parameters.m +++ b/+exp/Parameters.m @@ -71,9 +71,9 @@ function set(obj, name, value, description, units) n = numel(obj.pNames); obj.IsTrialSpecific = struct; isTrialSpecificDefault = @(n) ... - strcmp(n, 'numRepeats') ||... % numRepeats always trail specific - (ischar(obj.pStruct.(n)) && size(obj.pStruct.(n), 1) > 1) ||... % Number of rows > 1 for chars - (~ischar(obj.pStruct.(n)) && size(obj.pStruct.(n), 2) > 1); % Number of columns > 1 for all others + ~strcmp(n, 'randomiseConditions') &&... % randomiseConditions always global + ((ischar(obj.pStruct.(n)) && size(obj.pStruct.(n), 1) > 1) ||... % Number of rows > 1 for chars + (~ischar(obj.pStruct.(n)) && size(obj.pStruct.(n), 2) > 1)); % Number of columns > 1 for all others for i = 1:n name = obj.pNames{i}; obj.IsTrialSpecific.(name) = isTrialSpecificDefault(name); @@ -182,8 +182,8 @@ function makeGlobal(obj, name, newValue) 'UniformOutput', false); % concatenate trial parameter trialParamValues = cat(1, trialParamValues{:}); - if isempty(trialParamValues) - trialParamValues = {1}; + if isempty(trialParamValues) % Removed MW 30.01.19 + trialParamValues = {}; end trialParams = cell2struct(trialParamValues, trialParamNames, 1)'; globalParams = cell2struct(globalParamValues, globalParamNames, 1); diff --git a/cortexlab/+git/update.m b/cortexlab/+git/update.m index 200ba269..cd939a83 100644 --- a/cortexlab/+git/update.m +++ b/cortexlab/+git/update.m @@ -28,7 +28,7 @@ function update(scheduled) % than an hour ago. if (scheduled && (weekday(now) ~= scheduled) && now - lastFetch < 7) || ... (scheduled && (weekday(now) == scheduled) && now - lastFetch < 1) || ... - (~scheduled && now - lastFetch < 1/24) + (~scheduled && now - lastFetch < 1/24) return end disp('Updating code...') diff --git a/signals b/signals index 93520307..85072177 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit 93520307c1cb1a9dc12fa8bda685a01b9ab80401 +Subproject commit 850721777f0f4cd27c6a5248cf83aa246b53c9a2 From 04a8a3d9b0971cc31d87e397beacc02b5c70efcb Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 5 Feb 2019 12:51:16 +0200 Subject: [PATCH 277/393] Updates for GUILT compatibility --- +eui/ConditionPanel.m | 28 +++++++++++++++------------- +eui/FieldPanel.m | 3 ++- +eui/MControl.m | 2 +- +eui/ParamEditor.m | 18 ++++++++++++------ signals | 2 +- 5 files changed, 31 insertions(+), 22 deletions(-) diff --git a/+eui/ConditionPanel.m b/+eui/ConditionPanel.m index 7c4b89fe..eacd317f 100644 --- a/+eui/ConditionPanel.m +++ b/+eui/ConditionPanel.m @@ -29,8 +29,7 @@ methods function obj = ConditionPanel(f, ParamEditor, varargin) obj.ParamEditor = ParamEditor; - obj.UIPanel = uipanel('Parent', f, 'BorderType', 'none',... - 'BackgroundColor', 'white', 'Position', [0.5 0.05 0.5 0.95]); + obj.UIPanel = uix.VBox('Parent', f, 'BackgroundColor', 'white'); % Create a child menu for the uiContextMenus c = uicontextmenu; obj.UIPanel.UIContextMenu = c; @@ -41,7 +40,8 @@ obj.ContextMenus(3) = uimenu(c, 'Label', 'Sort by selected column', ... 'MenuSelectedFcn', @(~,~)disp('feature not yet implemented'), 'Tag', 'sort by'); % Create condition table - obj.ConditionTable = uitable('Parent', obj.UIPanel,... + p = uix.Panel('Parent', obj.UIPanel); + obj.ConditionTable = uitable('Parent', p,... 'FontName', 'Consolas',... 'RowName', [],... 'RearrangeableColumns', true,... @@ -51,14 +51,14 @@ 'CellEditCallback', @obj.onEdit,... 'CellSelectionCallback', @obj.onSelect); % Create button panel to hold condition control buttons - obj.ButtonPanel = uipanel('BackgroundColor', 'white',... - 'Position', [0.5 0 0.5 0.05], 'BorderType', 'none'); + obj.ButtonPanel = uix.HBox('Parent', obj.UIPanel, ... + 'BackgroundColor', 'white'); % Create callback so that width of button panel is slave to width of % conditional UIPanel - b = obj.ButtonPanel; - fcn = @(s)set(obj.ButtonPanel, 'Position', ... - [s.Position(1) b.Position(2) s.Position(3) b.Position(4)]); - obj.Listener = event.listener(obj.UIPanel, 'SizeChanged', @(s,~)fcn(s)); +% b = obj.ButtonPanel; +% fcn = @(s)set(obj.ButtonPanel, 'Position', ... +% [s.Position(1) b.Position(2) s.Position(3) b.Position(4)]); +% obj.Listener = event.listener(obj.UIPanel, 'SizeChanged', @(s,~)fcn(s)); % Define some common properties props.BackgroundColor = 'white'; props.Style = 'pushbutton'; @@ -67,28 +67,30 @@ % Create out four buttons obj.NewConditionButton = uicontrol(props,... 'String', 'New condition',... - 'Position',[0 0 1/4 1],... + ...'Position',[0 0 1/4 1],... 'TooltipString', 'Add a new condition',... 'Callback', @(~, ~) obj.newCondition()); obj.DeleteConditionButton = uicontrol(props,... 'String', 'Delete condition',... - 'Position',[1/4 0 1/4 1],... + ...'Position',[1/4 0 1/4 1],... 'TooltipString', 'Delete the selected condition',... 'Enable', 'off',... 'Callback', @(~, ~) obj.deleteSelectedConditions()); obj.MakeGlobalButton = uicontrol(props,... 'String', 'Globalise parameter',... - 'Position',[2/4 0 1/4 1],... + ...'Position',[2/4 0 1/4 1],... 'TooltipString', sprintf(['Make the selected condition-specific parameter global (i.e. not vary by trial)\n'... 'This will move it to the global parameters section']),... 'Enable', 'off',... 'Callback', @(~, ~) obj.makeGlobal()); obj.SetValuesButton = uicontrol(props,... 'String', 'Set values',... - 'Position',[3/4 0 1/4 1],... + ...'Position',[3/4 0 1/4 1],... 'TooltipString', 'Set selected values to specified value, range or function',... 'Enable', 'off',... 'Callback', @(~, ~) obj.setSelectedValues()); + obj.ButtonPanel.Widths = [-1 -1 -1 -1]; + obj.UIPanel.Heights = [-1 25]; end function onEdit(obj, src, eventData) diff --git a/+eui/FieldPanel.m b/+eui/FieldPanel.m index 33ae7ee1..10980cb6 100644 --- a/+eui/FieldPanel.m +++ b/+eui/FieldPanel.m @@ -28,7 +28,8 @@ methods function obj = FieldPanel(f, ParamEditor, varargin) obj.ParamEditor = ParamEditor; - obj.UIPanel = uipanel('Parent', f, 'BorderType', 'none',... + p = uix.Panel('Parent', f); + obj.UIPanel = uipanel('Parent', p, 'BorderType', 'none',... 'BackgroundColor', 'white', 'Position', [0 0 0.5 1]); obj.Listener = event.listener(obj.UIPanel, 'SizeChanged', @obj.onResize); end diff --git a/+eui/MControl.m b/+eui/MControl.m index 5bad701e..69c8628d 100644 --- a/+eui/MControl.m +++ b/+eui/MControl.m @@ -250,7 +250,7 @@ function loadParamProfile(obj, profile) set(obj.ParamProfileLabel, 'String', 'loading...', 'ForegroundColor', [1 0 0]); % Red 'Loading...' while new set loads if ~isempty(obj.ParamEditor) % Clear existing parameters control - % TODO + clear(obj.ParamEditor) end factory = obj.NewExpFactory; % Find which 'world' we are in diff --git a/+eui/ParamEditor.m b/+eui/ParamEditor.m index 74fa16e4..4c375b7e 100644 --- a/+eui/ParamEditor.m +++ b/+eui/ParamEditor.m @@ -7,6 +7,7 @@ end properties %(Access = private) + UIPanel GlobalUI ConditionalUI Parent @@ -27,11 +28,12 @@ if nargin < 2 f = figure('Name', 'Parameters', 'NumberTitle', 'off',... 'Toolbar', 'none', 'Menubar', 'none', 'DeleteFcn', @(~,~)obj.delete); + obj.Listener = event.listener(f, 'SizeChanged', @(~,~)obj.onResize); end obj.Parent = f; - obj.Listener = event.listener(f, 'SizeChanged', @(~,~)obj.onResize); - obj.GlobalUI = eui.FieldPanel(f, obj); - obj.ConditionalUI = eui.ConditionPanel(f, obj); + obj.UIPanel = uix.HBox('Parent', f); + obj.GlobalUI = eui.FieldPanel(obj.UIPanel, obj); + obj.ConditionalUI = eui.ConditionPanel(obj.UIPanel, obj); obj.buildUI(pars); end @@ -57,10 +59,14 @@ function delete(obj) end end - function buildUI(obj, pars) - obj.Parameters = pars; + function clear(obj) clear(obj.GlobalUI); clear(obj.ConditionalUI); + end + + function buildUI(obj, pars) + obj.Parameters = pars; + obj.clear() c = obj.GlobalUI; names = pars.GlobalNames; for nm = names' @@ -75,12 +81,12 @@ function buildUI(obj, pars) end end obj.fillConditionTable(); - obj.GlobalUI.onResize(); %%% Special parameters if ismember('randomiseConditions', obj.Parameters.Names) && ~pars.Struct.randomiseConditions obj.ConditionalUI.ConditionTable.RowName = 'numbered'; set(obj.ConditionalUI.ContextMenus(2), 'Checked', 'off'); end + obj.GlobalUI.onResize(); end function setRandomized(obj, value) diff --git a/signals b/signals index 85072177..23f93fb3 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit 850721777f0f4cd27c6a5248cf83aa246b53c9a2 +Subproject commit 23f93fb365c441d803e7ff43b5d8f17801a409e9 From e47f4624469a9935d8c77e8372ad005447b6a597 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 8 Feb 2019 11:46:53 +0200 Subject: [PATCH 278/393] New infer params --- +exp/inferParameters.m | 33 ++++++--------------------------- signals | 2 +- 2 files changed, 7 insertions(+), 28 deletions(-) diff --git a/+exp/inferParameters.m b/+exp/inferParameters.m index 945845d1..50009901 100644 --- a/+exp/inferParameters.m +++ b/+exp/inferParameters.m @@ -18,34 +18,16 @@ expdef = which(func2str(expdef)); end -net = sig.Net; -e = struct; -e.t = net.origin('t'); -e.events = net.subscriptableOrigin('events'); -e.pars = net.subscriptableOrigin('pars'); -e.pars.CacheSubscripts = true; -e.visual = net.subscriptableOrigin('visual'); -e.audio.Devices = @dummyDev; -e.inputs = net.subscriptableOrigin('inputs'); -e.outputs = net.subscriptableOrigin('outputs'); +e = sig.void; +pars = sig.void(true); +audio.Devices = @dummyDev; try + expdeffun(e.t, e.events, pars, e.visual, e.inputs, e.outputs, audio); - expdeffun(e.t, e.events, e.pars, e.visual, e.inputs, e.outputs, e.audio); - - % paramNames will be the strings corresponding to the fields of e.pars + % paramNames will be the strings corresponding to the fields of pars % that the user tried to reference in her expdeffun. - paramNames = e.pars.Subscripts.keys'; - %The paramValues are signals corresponding to those parameters and they - %will all be empty, except when they've been given explicit numerical - %definitions right at the end of the function - and in that case, we'll - %take those values (extracted into matlab datatypes, from the signals, - %using .Node.CurrValue) to be the desired default values. - paramValues = e.pars.Subscripts.values'; - parsStruct = cell2struct(cell(size(paramNames)), paramNames); - for i = 1:size(paramNames,1) - parsStruct.(paramNames{i}) = paramValues{i}.Node.CurrValue; - end + parsStruct = pars.Subscripts; sz = iff(isempty(fieldnames(parsStruct)), 1,... % if there are no paramters sz = 1 structfun(@(a)size(a,2), parsStruct)); % otherwise get number of columns isChar = structfun(@ischar, parsStruct); % we disregard charecter arrays @@ -60,12 +42,9 @@ ExpPanel_fn = [path filesep ExpPanel_name ext]; if exist(ExpPanel_fn,'file'); parsStruct.expPanelFun = ExpPanel_name; end catch ex - net.delete(); rethrow(ex) end -net.delete(); - function dev = dummyDev(~) % Returns a dummy audio device structure, regardless of input % Returns a standard structure with values for generating tone diff --git a/signals b/signals index 93520307..c81b99e0 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit 93520307c1cb1a9dc12fa8bda685a01b9ab80401 +Subproject commit c81b99e0922dbab9ab5d9a9770ff476c96fb6126 From 0f4bff25239bf0f9d96431f07f432b5e520d97e1 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 8 Feb 2019 12:44:53 +0200 Subject: [PATCH 279/393] Check for reserved params --- +exp/inferParameters.m | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/+exp/inferParameters.m b/+exp/inferParameters.m index 50009901..7e1a937e 100644 --- a/+exp/inferParameters.m +++ b/+exp/inferParameters.m @@ -5,12 +5,6 @@ % create some signals just to pass to the definition function and track % which parameter names are used -% if ischar(expdef) && file.exists(expdef) -% expdeffun = fileFunction(expdef); -% else -% expdeffun = expdef; -% expdef = which(func2str(expdef)); -% end if ischar(expdef) && file.exists(expdef) expdeffun = fileFunction(expdef); else @@ -28,6 +22,14 @@ % paramNames will be the strings corresponding to the fields of pars % that the user tried to reference in her expdeffun. parsStruct = pars.Subscripts; + + % Check for reserved fieldnames + reserved = {'randomiseConditions', 'services', 'expPanelFun', ... + 'numRepeats', 'defFunction', 'waterType', 'isPassive'}; + assert(~any(ismember(fieldnames(parsStruct), reserved)), ... + 'Lord have mercy, the following param names are reserved:\n%s', ... + strjoin(intersect(fieldnames(parsStruct), reserved), ', ')) + sz = iff(isempty(fieldnames(parsStruct)), 1,... % if there are no paramters sz = 1 structfun(@(a)size(a,2), parsStruct)); % otherwise get number of columns isChar = structfun(@ischar, parsStruct); % we disregard charecter arrays From c5a2e9821a77ad0a8750a196d04d46e3605e0a9e Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 8 Feb 2019 13:39:56 +0200 Subject: [PATCH 280/393] No more multiple default aud dev names --- +hw/devices.m | 4 ++-- signals | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/+hw/devices.m b/+hw/devices.m index 2734fe92..cdf5f493 100644 --- a/+hw/devices.m +++ b/+hw/devices.m @@ -69,8 +69,8 @@ % Get list of audio devices devs = getOr(rig, 'audioDevices', PsychPortAudio('GetDevices')); % Sanitize the names - names = matlab.lang.makeValidName([{'default'} {devs(2:end).DeviceName}],... - 'ReplacementStyle', 'delete'); + names = matlab.lang.makeValidName({devs.DeviceName}, 'ReplacementStyle', 'delete'); + names = iff(ismember('defaut', names), names, @()[{'default'} names(2:end)]); for i = 1:length(names); devs(i).DeviceName = names{i}; end rig.audioDevices = devs; end diff --git a/signals b/signals index 93520307..c81b99e0 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit 93520307c1cb1a9dc12fa8bda685a01b9ab80401 +Subproject commit c81b99e0922dbab9ab5d9a9770ff476c96fb6126 From 571ee1844a46ed90b82835ab27930cbc7e5f15c7 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 8 Feb 2019 14:28:40 +0200 Subject: [PATCH 281/393] Bug fix for numRepeats when there's signal char param --- +exp/inferParameters.m | 7 +++---- signals | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/+exp/inferParameters.m b/+exp/inferParameters.m index 7e1a937e..3c44117c 100644 --- a/+exp/inferParameters.m +++ b/+exp/inferParameters.m @@ -30,10 +30,9 @@ 'Lord have mercy, the following param names are reserved:\n%s', ... strjoin(intersect(fieldnames(parsStruct), reserved), ', ')) + szFcn = @(a)iff(ischar(a), @()size(a,1), @()size(a,2)); sz = iff(isempty(fieldnames(parsStruct)), 1,... % if there are no paramters sz = 1 - structfun(@(a)size(a,2), parsStruct)); % otherwise get number of columns - isChar = structfun(@ischar, parsStruct); % we disregard charecter arrays - if any(isChar); sz = sz(~isChar); end + structfun(szFcn, parsStruct)); % otherwise get number of columns % add 'numRepeats' parameter, where total number of trials = 1000 parsStruct.numRepeats = ones(1,max(sz))*floor(1000/max(sz)); parsStruct.defFunction = expdef; @@ -56,4 +55,4 @@ 'DefaultSampleRate', 44100,... 'NrOutputChannels', 2); end -end \ No newline at end of file +end diff --git a/signals b/signals index c81b99e0..8a56f9e6 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit c81b99e0922dbab9ab5d9a9770ff476c96fb6126 +Subproject commit 8a56f9e6b3b5cf0d37c34e5b0b948c2e55bb45d8 From 6e19d6126595524949e0fe0c6e9d33e89a9ce884 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 8 Feb 2019 15:39:51 +0200 Subject: [PATCH 282/393] Subjects list disabled during login --- +eui/AlyxPanel.m | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index f8b6da14..7abcff6e 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -217,6 +217,8 @@ function login(obj) % Logging out does not cause the token to expire, instead the % token is simply deleted from this object. + % Temporarily disable the Subject Selector + obj.NewExpSubject.UIControl.Enable = 'off'; % Reset headless flag in case user wishes to retry connection obj.AlyxInstance.Headless = false; % Are we logging in or out? @@ -282,6 +284,8 @@ function login(obj) notify(obj, 'Disconnected'); % Notify listeners of logout obj.log('Logged out of Alyx'); end + % Reable the Subject Selector + obj.NewExpSubject.UIControl.Enable = 'on'; obj.dispWaterReq() end From 8522c13d1c58d286acc1dd29c347444322b31621 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Sun, 10 Feb 2019 21:15:15 +0000 Subject: [PATCH 283/393] Field change to water-requirement endpoint --- +eui/AlyxPanel.m | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index f8b6da14..aade462f 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -601,13 +601,13 @@ function viewSubjectHistory(obj, ax) axWater = axes('Parent',plotBox); plot(axWater, dates, obj.round([records.given_water_total], 'up'), '.-'); hold(axWater, 'on'); - plot(axWater, dates, obj.round([records.given_water_hydrogel], 'down'), '.-'); - plot(axWater, dates, obj.round([records.given_water_liquid], 'down'), '.-'); + plot(axWater, dates, obj.round([records.given_water_supplement], 'down'), '.-'); + plot(axWater, dates, obj.round([records.given_water_reward], 'down'), '.-'); plot(axWater, dates, obj.round([records.expected_water], 'up'), 'r', 'LineWidth', 2.0); box(axWater, 'off'); xlim(axWater, [min(dates) maxDate]); set(axWater, 'XTickLabel', arrayfun(@(x)datestr(x, 'dd-mmm'), get(axWater, 'XTick'), 'uni', false)) - ylabel(axWater, 'water/hydrogel (mL)'); + ylabel(axWater, 'water (mL)'); % Create table of useful weight and water information, % sorted by date @@ -627,14 +627,14 @@ function viewSubjectHistory(obj, ax) arrayfun(@(x)iff(isnan(x), [], @()sprintf('%.1f', 0.8*(x-iw)+iw)), expected', 'uni', false), ... weightPctByDate'); waterDat = (... - num2cell(horzcat([records.given_water_liquid]', [records.given_water_hydrogel]', ... + num2cell(horzcat([records.given_water_reward]', [records.given_water_supplement]', ... [records.given_water_total]', [records.expected_water]',... [records.given_water_total]'-[records.expected_water]'))); waterDat = cellfun(@(x)sprintf('%.2f', x), waterDat, 'uni', false); waterDat(~[records.is_water_restricted],[1,3]) = {'ad lib'}; dat = horzcat(dat, waterDat); - set(histTable, 'ColumnName', {'date', 'meas. weight', '80% weight', 'weight pct', 'water', 'hydrogel', 'total', 'min water', 'excess'}, ... + set(histTable, 'ColumnName', {'date', 'meas. weight', '80% weight', 'weight pct', 'water', 'supplement', 'total', 'min water', 'excess'}, ... 'Data', dat(end:-1:1,:),... 'ColumnEditable', false(1,5)); histbox.Widths = [ -1 725]; From 210c8486bc26b96d5fe8ee1ebecca8262c0e789c Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Sun, 10 Feb 2019 22:39:48 +0000 Subject: [PATCH 284/393] Pagination support --- alyx-matlab | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alyx-matlab b/alyx-matlab index 6fc933b9..13264858 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit 6fc933b99bec09689a83b024284e8023d2c5793d +Subproject commit 132648581fe7ff7be9136baa00cdefe2cbb67c2f From 4ff9735bbf7b9147db107c99b95a6681ee8fab43 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Sun, 10 Feb 2019 21:15:15 +0000 Subject: [PATCH 285/393] Field change to water-requirement endpoint --- +eui/AlyxPanel.m | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index f8b6da14..aade462f 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -601,13 +601,13 @@ function viewSubjectHistory(obj, ax) axWater = axes('Parent',plotBox); plot(axWater, dates, obj.round([records.given_water_total], 'up'), '.-'); hold(axWater, 'on'); - plot(axWater, dates, obj.round([records.given_water_hydrogel], 'down'), '.-'); - plot(axWater, dates, obj.round([records.given_water_liquid], 'down'), '.-'); + plot(axWater, dates, obj.round([records.given_water_supplement], 'down'), '.-'); + plot(axWater, dates, obj.round([records.given_water_reward], 'down'), '.-'); plot(axWater, dates, obj.round([records.expected_water], 'up'), 'r', 'LineWidth', 2.0); box(axWater, 'off'); xlim(axWater, [min(dates) maxDate]); set(axWater, 'XTickLabel', arrayfun(@(x)datestr(x, 'dd-mmm'), get(axWater, 'XTick'), 'uni', false)) - ylabel(axWater, 'water/hydrogel (mL)'); + ylabel(axWater, 'water (mL)'); % Create table of useful weight and water information, % sorted by date @@ -627,14 +627,14 @@ function viewSubjectHistory(obj, ax) arrayfun(@(x)iff(isnan(x), [], @()sprintf('%.1f', 0.8*(x-iw)+iw)), expected', 'uni', false), ... weightPctByDate'); waterDat = (... - num2cell(horzcat([records.given_water_liquid]', [records.given_water_hydrogel]', ... + num2cell(horzcat([records.given_water_reward]', [records.given_water_supplement]', ... [records.given_water_total]', [records.expected_water]',... [records.given_water_total]'-[records.expected_water]'))); waterDat = cellfun(@(x)sprintf('%.2f', x), waterDat, 'uni', false); waterDat(~[records.is_water_restricted],[1,3]) = {'ad lib'}; dat = horzcat(dat, waterDat); - set(histTable, 'ColumnName', {'date', 'meas. weight', '80% weight', 'weight pct', 'water', 'hydrogel', 'total', 'min water', 'excess'}, ... + set(histTable, 'ColumnName', {'date', 'meas. weight', '80% weight', 'weight pct', 'water', 'supplement', 'total', 'min water', 'excess'}, ... 'Data', dat(end:-1:1,:),... 'ColumnEditable', false(1,5)); histbox.Widths = [ -1 725]; From 18452921d1047317eeb54c7c3b71df46245639f5 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Mon, 11 Feb 2019 13:12:59 +0000 Subject: [PATCH 286/393] Pagination support --- alyx-matlab | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alyx-matlab b/alyx-matlab index 1f29e306..38d33275 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit 1f29e306747a39cc76d99bb3ac51175309ecbda4 +Subproject commit 38d332754402aad2c608890bb50347d0ad969e0e From b5ea66e02e52d4025c7af63367be79be326d138a Mon Sep 17 00:00:00 2001 From: kevin-j-miller <kjmd10@gmail.com> Date: Fri, 15 Feb 2019 15:57:28 +0000 Subject: [PATCH 287/393] Modify Alyx login to use newid instead of inputdlg. This makes the 'enter' key on the keyboard synonymous with the 'ok' button --- +eui/AlyxPanel.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index 78f9c781..ef221f98 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -318,7 +318,7 @@ function giveFutureWater(obj) 'enter space-separated numbers, i.e. \n',... '[tomorrow, day after that, day after that.. etc] \n\n',... 'Enter "0" to skip a day\nEnter "-1" to indicate training for that day\n']); - amtStr = inputdlg(prompt,'Future Amounts', [1 50]); + amtStr = newid(prompt,'Future Amounts', [1 50]); if isempty(amtStr)||~obj.AlyxInstance.IsLoggedIn return % user pressed 'Close' or 'x' end @@ -450,10 +450,10 @@ function recordWeight(obj, weight, subject) dlgTitle = 'Manual weight logging'; numLines = 1; defaultAns = {'',''}; - weight = inputdlg(prompt, dlgTitle, numLines, defaultAns); + weight = newid(prompt, dlgTitle, numLines, defaultAns); if isempty(weight); return; end end - % inputdlg returns weight as a cell, otherwise it may now be + % newid returns weight as a cell, otherwise it may now be weight = ensureCell(weight); % ensure it's a cell % convert to double if weight is a string weight = iff(ischar(weight{1}), str2double(weight{1}), weight{1}); From e9c2f5e3e7aa46c4f96a0ba501faa777624dd0df Mon Sep 17 00:00:00 2001 From: kevin-j-miller <kjmd10@gmail.com> Date: Fri, 15 Feb 2019 15:57:40 +0000 Subject: [PATCH 288/393] Modify Alyx login to use newid instead of inputdlg. This makes the 'enter' key on the keyboard synonymous with the 'ok' button --- alyx-matlab | 2 +- cortexlab/+git/changes.m | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) delete mode 100644 cortexlab/+git/changes.m diff --git a/alyx-matlab b/alyx-matlab index 13264858..77a077ea 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit 132648581fe7ff7be9136baa00cdefe2cbb67c2f +Subproject commit 77a077ead0c34ba3acdfb007e8da9ca40243547c diff --git a/cortexlab/+git/changes.m b/cortexlab/+git/changes.m deleted file mode 100644 index d9e9f013..00000000 --- a/cortexlab/+git/changes.m +++ /dev/null @@ -1,6 +0,0 @@ -disp('Updating queued Alyx posts...') -posts = dirPlus(getOr(dat.paths, 'localAlyxQueue', 'C:/localAlyxQueue')); -posts = posts(endsWith(posts, 'put')); -newPosts = cellfun(@(str)[str(1:end-3) 'patch'], posts, 'uni', 0); -status = cellfun(@movefile, posts, newPosts); -assert(all(status), 'Unable to rename queued Alyx files, please do this manually') \ No newline at end of file From 5f2fb584ee3c8fc9c70058ddf8fba4bbc5380cac Mon Sep 17 00:00:00 2001 From: kevin-j-miller <kjmd10@gmail.com> Date: Fri, 15 Feb 2019 17:11:00 +0000 Subject: [PATCH 289/393] Expose the AlyxPanel property of the MControl object, so that the login method can be invoked from the command line. I want to do this so that I can put a batch file on the desktop of the computer that runs MC to help streamline the workflow. --- +eui/MControl.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/+eui/MControl.m b/+eui/MControl.m index 5b25075d..9d5c201c 100644 --- a/+eui/MControl.m +++ b/+eui/MControl.m @@ -17,6 +17,7 @@ properties LoggingDisplay % control for showing log output + AlyxPanel % holds the AlyxPanel object (see buildUI(), eui.AlyxPanel()) end properties (SetAccess = private) @@ -33,7 +34,6 @@ properties (Access = private) ParamEditor ParamPanel - AlyxPanel % holds the AlyxPanel object (see buildUI(), eui.AlyxPanel()) BeginExpButton % The 'Start' button that begins an experiment RigOptionsButton % The 'Options' button that opens the rig options dialog NewExpFactory % A struct containing all availiable experiment types and function handles to constructors for their default parameters From 9b56e4f80de5b49a3f686a2aa5066c00ebfd1155 Mon Sep 17 00:00:00 2001 From: kevin-j-miller <kjmd10@gmail.com> Date: Mon, 18 Feb 2019 14:17:51 +0000 Subject: [PATCH 290/393] modified alyx-matlab with more convenient textboxes and desktop shortcuts for common use-cases --- alyx-matlab | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alyx-matlab b/alyx-matlab index 77a077ea..c5c5c1ba 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit 77a077ead0c34ba3acdfb007e8da9ca40243547c +Subproject commit c5c5c1ba22dce86549ed56fd257c3852f5949391 From 64416dd363e42c0936d2d170c8672adba1945b78 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 19 Feb 2019 22:35:11 +0200 Subject: [PATCH 291/393] Bug fix for turning empty objects into struct --- cb-tools/obj2struct.m | 8 +++++--- tests/obj2json_test.m | 23 +++++++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 tests/obj2json_test.m diff --git a/cb-tools/obj2struct.m b/cb-tools/obj2struct.m index 60e91eb6..67937832 100644 --- a/cb-tools/obj2struct.m +++ b/cb-tools/obj2struct.m @@ -23,12 +23,15 @@ end s = obj2struct(m); else % Normal object + s.ClassContructor = class(obj); % Supply class name for loading object names = fieldnames(obj); % Get list of public properties for i = 1:length(names) - if isobject(obj.(names{i})) % Property contains an object + if isempty(obj) % Object and therefore all properties are empty + s.(names{i}) = []; + elseif isobject(obj.(names{i})) % Property contains an object if startsWith(class(obj.(names{i})),'daq.ni.') % Do not attempt to save ni daq sessions of channels - s.(names{i}) = []; + s.(names{i}) = []; else % Recurse s.(names{i}) = obj2struct(obj.(names{i})); end @@ -55,7 +58,6 @@ s.(names{i}) = obj.(names{i}); end end - s.ClassContructor = class(obj); % Supply class name for loading object end elseif iscell(obj) % If dealing with cell array, recurse through elements diff --git a/tests/obj2json_test.m b/tests/obj2json_test.m new file mode 100644 index 00000000..e26dd2cf --- /dev/null +++ b/tests/obj2json_test.m @@ -0,0 +1,23 @@ +%% Test obj2struct with given data +data = struct; +data.A = struct(... % Scalar struct + 'field1', zeros(10), ... + 'field2', true(10), ... + 'field3', pi, ... + 'field4', single(10), ... + 'field5', '10'); +data.B = hw.DaqController(); % Obj containing empty obj +v = daq.getVendors(); +if v(strcmp({v.ID},'ni')).IsOperational + data.B.createDaqChannels(); % Add daq.ni obj +end +data.C = struct; % Non-scalar struct +data.C(1,1).a = 1; +data.C(2,1).a = 2; +data.C(1,2).a = 3; +data.C(2,2).a = 4; +data.D = @(a,b,c)zeros(c,b,a); % Function handle + +json = obj2json(data); +out = '{"A":{"field1":[[0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0]],"field2":[[true,true,true,true,true,true,true,true,true,true],[true,true,true,true,true,true,true,true,true,true],[true,true,true,true,true,true,true,true,true,true],[true,true,true,true,true,true,true,true,true,true],[true,true,true,true,true,true,true,true,true,true],[true,true,true,true,true,true,true,true,true,true],[true,true,true,true,true,true,true,true,true,true],[true,true,true,true,true,true,true,true,true,true],[true,true,true,true,true,true,true,true,true,true],[true,true,true,true,true,true,true,true,true,true]],"field3":3.1415926535897931,"field4":10,"field5":"10"},"B":{"ClassContructor":"hw.DaqController","ChannelNames":[],"SignalGenerators":{"ClassContructor":"hw.PulseSwitcher","OpenValue":[],"ClosedValue":[],"ParamsFun":[],"DefaultCommand":[],"DefaultValue":[]},"DaqIds":"Dev1","DaqChannelIds":[],"SampleRate":1000,"DaqSession":[],"DigitalDaqSession":[],"Value":[],"NumChannels":0,"AnalogueChannelsIdx":[]},"C":[[{"a":1},{"a":3}],[{"a":2},{"a":4}]],"D":"@(a,b,c)zeros(c,b,a)"}'; +assert(strcmp(json,out), 'Test failed') \ No newline at end of file From 4fda5c090a63f98180a7c8752e81520333a58f18 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 20 Feb 2019 00:38:50 +0200 Subject: [PATCH 292/393] Added error msgID and tests --- +exp/inferParameters.m | 3 +- tests/expDefinitions/advancedChoiceWorld.m | 195 ++++++ .../advancedChoiceWorldExpPanel.m | 1 + .../advancedChoiceWorld_parameters.mat | Bin 0 -> 729 bytes tests/expDefinitions/choiceWorld.m | 636 ++++++++++++++++++ .../expDefinitions/choiceWorld_parameters.mat | Bin 0 -> 709 bytes tests/inferParamsPerformanceTest.m | 21 + tests/inferParamsTest.m | 65 ++ 8 files changed, 920 insertions(+), 1 deletion(-) create mode 100644 tests/expDefinitions/advancedChoiceWorld.m create mode 100644 tests/expDefinitions/advancedChoiceWorldExpPanel.m create mode 100644 tests/expDefinitions/advancedChoiceWorld_parameters.mat create mode 100644 tests/expDefinitions/choiceWorld.m create mode 100644 tests/expDefinitions/choiceWorld_parameters.mat create mode 100644 tests/inferParamsPerformanceTest.m create mode 100644 tests/inferParamsTest.m diff --git a/+exp/inferParameters.m b/+exp/inferParameters.m index 3c44117c..275c964f 100644 --- a/+exp/inferParameters.m +++ b/+exp/inferParameters.m @@ -27,7 +27,8 @@ reserved = {'randomiseConditions', 'services', 'expPanelFun', ... 'numRepeats', 'defFunction', 'waterType', 'isPassive'}; assert(~any(ismember(fieldnames(parsStruct), reserved)), ... - 'Lord have mercy, the following param names are reserved:\n%s', ... + 'exp:InferParameters:ReservedParameters', ... + 'The following param names are reserved:\n%s', ... strjoin(intersect(fieldnames(parsStruct), reserved), ', ')) szFcn = @(a)iff(ischar(a), @()size(a,1), @()size(a,2)); diff --git a/tests/expDefinitions/advancedChoiceWorld.m b/tests/expDefinitions/advancedChoiceWorld.m new file mode 100644 index 00000000..836ef546 --- /dev/null +++ b/tests/expDefinitions/advancedChoiceWorld.m @@ -0,0 +1,195 @@ +function advancedChoiceWorld(t, evts, p, vs, in, out, audio) +%% advancedChoiceWorld +% Burgess 2AUFC task with contrast discrimination and baited equal contrast +% trial conditions. +% 2017-03-25 Added contrast discrimination MW +% 2017-08 Added baited trials (thanks PZH) +% 2017-09-26 Added manual reward key presses +% 2017-10-26 p.wheelGain now in mm/deg units +% 2018-03-15 Added time sampler function for delays + +%% parameters +wheel = in.wheelMM; % The wheel input in mm turned tangential to the surface +rewardKey = p.rewardKey.at(evts.expStart); % get value of rewardKey at experiemnt start, otherwise it will take the same value each new trial +rewardKeyPressed = in.keyboard.strcmp(rewardKey); % true each time the reward key is pressed +contrastLeft = p.stimulusContrast(1); +contrastRight = p.stimulusContrast(2); + +%% when to present stimuli & allow visual stim to move +% stimulus should come on after the wheel has been held still for the +% duration of the preStimulusDelay. The quiescence threshold is a tenth of +% the rotary encoder resolution. +preStimulusDelay = p.preStimulusDelay.map(@timeSampler).at(evts.newTrial); % at(evts.newTrial) fix for rig pre-delay +stimulusOn = sig.quiescenceWatch(preStimulusDelay, t, wheel, 10); +interactiveDelay = p.interactiveDelay.map(@timeSampler); +interactiveOn = stimulusOn.delay(interactiveDelay); % the closed-loop period starts when the stimulus comes on, plus an 'interactive delay' + +audioDevice = audio.Devices('default'); +onsetToneSamples = p.onsetToneAmplitude*... + mapn(p.onsetToneFrequency, 0.1, audioDevice.DefaultSampleRate,... + 0.02, audioDevice.NrOutputChannels, @aud.pureTone); % aud.pureTone(freq, duration, samprate, "ramp duration", nAudChannels) +audio.default = onsetToneSamples.at(interactiveOn); % At the time of 'interative on', send samples to audio device and log as 'onsetTone' + +%% wheel position to stimulus displacement +% Here we define the multiplication factor for changing the wheel signal +% into mm/deg visual angle units. The Lego wheel used has a 31mm radius. +% The standard K�BLER rotary encoder uses X4 encoding; we record all edges +% (up and down) from both channels for maximum resolution. This means that +% e.g. a K�BLER 2400 with 100 pulses per revolution will actually generate +% *400* position ticks per full revolution. +wheelOrigin = wheel.at(interactiveOn); % wheel position sampled at 'interactiveOn' +stimulusDisplacement = p.wheelGain*(wheel - wheelOrigin); % yoke the stimulus displacment to the wheel movement during closed loop + +%% define response and response threshold +responseTimeOver = (t - t.at(interactiveOn)) > p.responseWindow; % p.responseWindow may be set to Inf +threshold = interactiveOn.setTrigger(... + abs(stimulusDisplacement) >= abs(p.stimulusAzimuth) | responseTimeOver); + +response = cond(... + responseTimeOver, 0,... % if the response time is over the response = 0 + true, -sign(stimulusDisplacement)); % otherwise it should be the inverse of the sign of the stimulusDisplacement + +response = response.at(threshold); % only update the response signal when the threshold has been crossed +stimulusOff = threshold.delay(1); % true a second after the threshold is crossed + +%% define correct response and feedback +% each trial randomly pick -1 or 1 value for use in baited (guess) trials +rndDraw = map(evts.newTrial, @(x) sign(rand(x)-0.5)); +correctResponse = cond(contrastLeft > contrastRight, -1,... % contrast left + contrastLeft < contrastRight, 1,... % contrast right + (contrastLeft + contrastRight == 0), 0,... % no-go (zero contrast) + (contrastLeft == contrastRight) & (rndDraw < 0), -1,... % equal contrast (baited) + (contrastLeft == contrastRight) & (rndDraw > 0), 1); % equal contrast (baited) +feedback = correctResponse == response; +% Only update the feedback signal at the time of the threshold being crossed +feedback = feedback.at(threshold).delay(0.1); + +noiseBurstSamples = p.noiseBurstAmp*... + mapn(audioDevice.NrOutputChannels, p.noiseBurstDur*audioDevice.DefaultSampleRate, @randn); +audio.default = noiseBurstSamples.at(feedback==0); % When the subject gives an incorrect response, send samples to audio device and log as 'noiseBurst' + +reward = merge(rewardKeyPressed, feedback > 0);% only update when feedback changes to greater than 0, or reward key is pressed +out.reward = p.rewardSize.at(reward); % output this signal to the reward controller + +%% stimulus azimuth +azimuth = cond(... + stimulusOn.to(interactiveOn), 0,... % Before the closed-loop condition, the stimulus is at it's starting azimuth + interactiveOn.to(threshold), stimulusDisplacement,... % Closed-loop condition, where the azimuth yoked to the wheel + threshold.to(stimulusOff), -response*abs(p.stimulusAzimuth)); % Once threshold is reached the stimulus is fixed again + +%% define the visual stimulus + +% Test stim left +leftStimulus = vis.grating(t, 'sinusoid', 'gaussian'); % create a Gabor grating +leftStimulus.orientation = p.stimulusOrientation(1); +leftStimulus.altitude = 0; +leftStimulus.sigma = [9,9]; % in visual degrees +leftStimulus.spatialFreq = p.spatialFrequency; % in cylces per degree +leftStimulus.phase = 2*pi*evts.newTrial.map(@(v)rand); % phase randomly changes each trial +leftStimulus.contrast = contrastLeft; +leftStimulus.azimuth = -p.stimulusAzimuth + azimuth; +% When show is true, the stimulus is visible +leftStimulus.show = stimulusOn.to(stimulusOff); + +vs.leftStimulus = leftStimulus; % store stimulus in visual stimuli set and log as 'leftStimulus' + +% Test stim right +rightStimulus = vis.grating(t, 'sinusoid', 'gaussian'); +rightStimulus.orientation = p.stimulusOrientation(2); +rightStimulus.altitude = 0; +rightStimulus.sigma = [9,9]; +rightStimulus.spatialFreq = p.spatialFrequency; +rightStimulus.phase = 2*pi*evts.newTrial.map(@(v)rand); +rightStimulus.contrast = contrastRight; +rightStimulus.azimuth = p.stimulusAzimuth + azimuth; +rightStimulus.show = stimulusOn.to(stimulusOff); + +vs.rightStimulus = rightStimulus; % store stimulus in visual stimuli set + +%% End trial and log events +% Let's use the next set of conditional paramters only if positive feedback +% was given, or if the parameter 'Repeat incorrect' was set to false. +nextCondition = feedback > 0 | p.repeatIncorrect == false; + +% we want to save these signals so we put them in events with appropriate +% names: +evts.stimulusOn = stimulusOn; +evts.preStimulusDelay = preStimulusDelay; +% save the contrasts as a difference between left and right +evts.contrast = p.stimulusContrast.map(@diff); +evts.contrastLeft = contrastLeft; +evts.contrastRight = contrastRight; +evts.azimuth = azimuth; +evts.response = response; +evts.feedback = feedback; +evts.interactiveOn = interactiveOn; +% Accumulate reward signals and append microlitre units +evts.totalReward = out.reward.scan(@plus, 0).map(fun.partial(@sprintf, '%.1f�l')); + +% Trial ends when evts.endTrial updates. +% If the value of evts.endTrial is false, the current set of conditional +% parameters are used for the next trial, if evts.endTrial updates to true, +% the next set of randowmly picked conditional parameters is used +evts.endTrial = nextCondition.at(stimulusOff).delay(p.interTrialDelay.map(@timeSampler)); + +%% Parameter defaults +% See timeSampler for full details on what values the *Delay paramters can +% take. Conditional perameters are defined as having ncols > 1, where each +% column is a condition. All conditional paramters must have the same +% number of columns. +try +%%% Contrast starting set +% C = [1 0;0 1;0.5 0;0 0.5]'; +%%% Contrast discrimination set +% c = [1 0.5 0.25 0.12 0.06 0]; +% c = combvec(c, c); +% C = unique([c, flipud(c)]', 'rows')'; +%%% Contrast detection set +c = [1 0.5 0.25 0.12 0.06 0]; +C = [c, zeros(1, numel(c)-1); zeros(1, numel(c)-1), c]; +%%% +p.stimulusContrast = C; + +p.repeatIncorrect = abs(diff(C,1)) > 0.25; % | all(C==0); +p.onsetToneFrequency = 5000; +p.interactiveDelay = 0.4; +p.onsetToneAmplitude = 0.15; +p.responseWindow = Inf; +p.stimulusAzimuth = 90; +p.noiseBurstAmp = 0.01; +p.noiseBurstDur = 0.5; +p.rewardSize = 3; +p.rewardKey = 'r'; +p.stimulusOrientation = [0, 0]'; +p.spatialFrequency = 0.19; % Prusky & Douglas, 2004 +p.interTrialDelay = 0.5; +p.wheelGain = 5; +p.preStimulusDelay = [0 0.1 0.09]'; +catch % ex +% disp(getReport(ex, 'extended', 'hyperlinks', 'on')) +end + +%% Helper functions +function duration = timeSampler(time) +% TIMESAMPLER Sample a time from some distribution +% If time is a single value, duration is that value. If time = [min max], +% then duration is sampled uniformally. If time = [min, max, time const], +% then duration is sampled from a exponential distribution, giving a flat +% hazard rate. If numel(time) > 3, duration is a randomly sampled value +% from time. +% +% See also exp.TimeSampler + if nargin == 0; duration = 0; return; end + switch length(time) + case 3 % A time sampled with a flat hazard function + duration = time(1) + exprnd(time(3)); + duration = iff(duration > time(2), time(2), duration); + case 2 % A time sampled from a uniform distribution + duration = time(1) + (time(2) - time(1))*rand; + case 1 % A fixed time + duration = time(1); + otherwise % Pick on of the values + duration = randsample(time, 1); + end +end +end \ No newline at end of file diff --git a/tests/expDefinitions/advancedChoiceWorldExpPanel.m b/tests/expDefinitions/advancedChoiceWorldExpPanel.m new file mode 100644 index 00000000..929b1a28 --- /dev/null +++ b/tests/expDefinitions/advancedChoiceWorldExpPanel.m @@ -0,0 +1 @@ +% --pass \ No newline at end of file diff --git a/tests/expDefinitions/advancedChoiceWorld_parameters.mat b/tests/expDefinitions/advancedChoiceWorld_parameters.mat new file mode 100644 index 0000000000000000000000000000000000000000..d84446fbc143b407ec1e2f38af5d21fca954679c GIT binary patch literal 729 zcmeZu4DoSvQZUssQ1EpO(M`+DN!3vZ$Vn_o%P-2cQV4Jk_w+L}(NS<NN=+<DO;O0t zvr-5tO;vD9O;RwlR4_8OGBmR?HdZh)0P=_g3^2gx>B|pf2Qo1*RLt2Lx<CK2gUG&R znF@}pv@SAocG@#4Zd6d2GHnTON8<#s&{cb4-i8-Vs!f0Aez}d2ZGuE(V(R)E8r<C6 z$>vAI(x(06ymPa!w(wF!>CwsOo`3(e@B6;*@BVD+5wP0Z6yW<l{OhU(5z~Zf4@U9s zbvjeXAhbi`cj(2ePrup!J}@|Nr<%!KLs-#sL5*ruk5^x-$+iiH!;<ov`<2U`N@L!5 z6)~*r4P-2u9T>syG~eTCW9scEAA%T5w?1FM|DfDpVOSiWlF2^nmZ}_slc}{U%5V1P zOc7+{7Zndu_uMdZ?b-^d^H;XHwi(3U4)$yC(&1d}(NcAM-_dgk&IdjjmP$T<;HKO0 z`tGw^rn=&RqF<E4CqDg<P#+v<-eoIRDH5fV)OdJ9x8{#qmRogSm$IimkDgn+&n@hW zw10szclhatXKZ(-J$zMF>$!QJsrt;`qF<S-=HIFM{{803?{n(reEwfw=sUOn!p~B@ zMJNBwu*#44;V<7?lWkU4%O`KL&&+pEb@8L8zhnL16wbN7^kcd5&x>c~mkaJ&<^SpL z*O%2nEvsK_U%I3|^~bTIZ@YZ%-SvJ_yS3`hPve^CUFSFU=-o*_TrDX2eWy`tw|Y_J zzP&FV{qOm)F7iiJs#Jdc`Iq1K8q_WD{<z`ywy=izN0ZGbPdP4aQopWrt%Y{yy<P8Z z#DCq_uCmH~w#wmr?!%IfH>+)zyT`rR=<MEf!OYhG_6C0q!^z<mH>?j_VO@VU{Yb0q uvD)`#6W$-!k=rW6zj(KohuD$Q67CP?#TARsD{P+F^Z&$qdGps#)&T&{M@$$1 literal 0 HcmV?d00001 diff --git a/tests/expDefinitions/choiceWorld.m b/tests/expDefinitions/choiceWorld.m new file mode 100644 index 00000000..bcf265e3 --- /dev/null +++ b/tests/expDefinitions/choiceWorld.m @@ -0,0 +1,636 @@ +function choiceWorld(t, events, p, visStim, inputs, outputs, audio) +% ChoiceWorld(t, events, parameters, visStim, inputs, outputs, audio) +% +% A simple training protocol closely following that of our manual training. +% Contrasts are presented randomly (no staircase). The session is ended +% automatically if after a minimum number of trials either the median +% response time over the last 20 trials is over 5x (default) longer than +% that of the whole session, or if there is a greater than 50% (default) +% decrease in performance over the last 20 trials (compared to total +% performance over the whole session). +% +% The wheel gain changes only after the subject completes over 200 trials +% in a session. The gain only changes once and remains changed for all +% future sessions. +% +% There is no longer any change in reward volume, and there is no cue +% interactive delay. + +%% Fixed parameters +contrastSet = p.contrastSet.at(events.expStart); +startingContrasts = p.startingContrasts.at(events.expStart); +repeatOnMiss = p.repeatOnMiss.at(events.expStart); +trialsToBuffer = p.trialsToBuffer.at(events.expStart); +trialsToZeroContrast = p.trialsToZeroContrast.at(events.expStart); +rewardSize = p.rewardSize.at(events.expStart); +initialGain = p.initialGain.at(events.expStart); +normalGain = p.normalGain.at(events.expStart); +responseWindow = p.responseWindow.at(events.expStart); + +% Sounds +audioDevice = audio.Devices('default'); +onsetToneFreq = 5000; +onsetToneDuration = 0.1; +onsetToneRampDuration = 0.01; +toneSamples = p.onsetToneAmplitude*events.expStart.map(@(x) ... + aud.pureTone(onsetToneFreq, onsetToneDuration, audioDevice.DefaultSampleRate, ... + onsetToneRampDuration, audioDevice.NrOutputChannels)); +missNoiseDuration = 0.5; +missNoiseSamples = p.missNoiseAmplitude*events.expStart.map(@(x) ... + randn(audioDevice.NrOutputChannels, audioDevice.DefaultSampleRate*missNoiseDuration)); + +%% Initialize trial data +trialDataInit = events.expStart.mapn(... + contrastSet, startingContrasts, repeatOnMiss, ... + trialsToBuffer, trialsToZeroContrast, rewardSize,... + @initializeTrialData).subscriptable; + +%% Set up wheel +wheel = inputs.wheelMM; +quiescThreshold = 1000; +% millimetersFactor = events.newTrial.map2(31*2*pi/(p.encoderRes*4), @times); % convert the wheel gain to a value in mm/deg +gain = events.expStart.mapn(initialGain, normalGain, @initWheelGain); +enoughTrials = events.trialNum > 200; +wheelGain = iff(enoughTrials, normalGain, gain); + +%% Trial event times +% (this is set up to be independent of trial conditon, that way the trial +% condition can be chosen in a performance-dependent manner) + +% Resetting pre-stim quiescent period +prestimQuiescentPeriod = at(p.prestimQuiescentTime.map(@(A)rnd.exp(A(3),1,A(1:2))), events.newTrial); +preStimQuiescence = sig.quiescenceWatch(prestimQuiescentPeriod, t, wheel, quiescThreshold); +% Stimulus onset +stimOn = at(true, preStimQuiescence); % FIXME test whether at is needed here +% Play tone at interactive onset +audio.default = toneSamples.at(stimOn); +% The wheel displacement is zeroed at stimOn +stimDisplacement = wheelGain*(wheel - wheel.at(stimOn)); + +responseTimeOver = (t - t.at(stimOn)) > responseWindow; % p.responseWindow may be set to Inf +threshold = stimOn.setTrigger(... + abs(stimDisplacement) >= abs(p.responseDisplacement) | responseTimeOver); +response = cond(... + responseTimeOver, 3,... % if the response time is over the response = 0 + true, -sign(stimDisplacement)); % otherwise it should be the inverse of the sign of the stimulusDisplacement +response = response.at(stimOn.setTrigger(threshold)); % only update the response signal when the threshold has been crossed + +%% Bias +bias = merge(response.keepWhen(response~=3).bufferUpTo(10).map(@sum), ... + at(0, events.expStart)); % Initialize with 0 at expStart + +%% Update performance at response +responseData = vertcat(stimDisplacement, events.trialNum, response, bias); +trialData = responseData.at(response).scan(@updateTrialData, trialDataInit).subscriptable; +% trialData = response.scan(@updateTrialData, trialDataInit, 'pars', stimDisplacement, events.trialNum, bias).subscriptable; +% Set trial contrast (chosen when updating performance) +trialContrast = trialData.trialContrast.at(events.newTrial); +hit = trialData.hit.at(response); + +%% Task disengagement +% Response time = duration (seconds) between new trial and response +rt = t.at(stimOn).map2(t, @(a,b)diff([a,b])).at(response); +% The median response time over the last 20 trials +windowedRT = rt.buffer(20).map(@median); +% The median response time over all trials +baselineRT = rt.bufferUpTo(1000).map(@median); +% tooSlow is true when windowed rt is x times longer than median rt for the +% session, where x is the rtCriterion +tooSlow = windowedRT > baselineRT*p.rtCriterion; +% noResponse is true when mouse fails to respond for over x seconds, where +% x is maxRespWindow +% noResponse = t-t.at(events.newTrial) > p.maxRespWindow; + +% A rolloing buffer of performance (proportion of last 20 trials that were +% correct) - this includes repeat on incorrect trials +windowedPerf = hit.buffer(20).map(@(a)sum(a)/length(a)); +% Proportion of all trials that were correct +baselinePerf = hit.bufferUpTo(1000).map(@(a)sum(a)/length(a)); +% True when there is an x% decrease in performance over the last 20 trials +% compared to the session average, where x is pctPerfDecrease +poorPerformance = iff(trialData.proportionLeft == 0.5, ... + (baselinePerf - windowedPerf)/baselinePerf > p.pctPerfDecrease/100, false); +% poorPerformance = (baselinePerf - windowedPerf)/baselinePerf > p.pctPerfDecrease/100; + +% The subject is identified as disengaged from the task when, after +% minTrials have been completed, the subject is either too slow or exhibits +% a significant drop in performance. If the subject has not completed the +% minimum number of trials in 45 minutes it is also classed as disengaged. +disengaged = iff(events.trialNum > p.minTrials, tooSlow, ... + events.expStart.delay(60*45)); +% The session is finished when either the session has been running for x +% seconds, where x is trialDataInit.endAfter (20min on the first day, 40min +% on the seconds, Inf otherwise), or when the subject is disengaged +% finish = merge(at(true, disengaged),... +% at(true, events.expStart.delay(trialDataInit.endAfter))); +finish = cond(disengaged, true,... + events.expStart.delay(trialDataInit.endAfter), true); + +% When finish takes a value (it may only sample true), this is posted to +% events.expStop to trigger the end of the session +expStop = events.expStop; +expStop.Node.Listeners = [expStop.Node.Listeners, ... + into(finish, expStop)]; + +%% Give feedback and end trial +% Ensures reward size is not re-calculated at the response time +rewardSize = trialData.rewardSize.at(events.newTrial); +% NOTE: there is a 10ms delay for water output, because otherwise water and +% stim output compete and stim is delayed +outputs.reward = rewardSize.at(hit==true).delay(0.01); +% Play noise on miss +audio.default = missNoiseSamples.at(delay(hit==false, 0.01)); +% ITI defined by outcome +iti = iff(hit==1, p.itiHit, p.itiMiss); +% Stim stays on until the end of the ITI +stimOff = threshold.delay(iti); + +%% Visual stimulus +% Azimuth control +% 1) stim fixed in place until interactive on +% 2) wheel-conditional during interactive +% 3) fixed at response displacement azimuth after response +trialSide = trialData.trialSide.at(stimOn); +azimuth = cond( ... + stimOn.to(threshold), p.startingAzimuth*trialSide + stimDisplacement, ... + threshold.to(events.newTrial), ... + p.startingAzimuth*trialSide + ... + iff(response~=3, -response*abs(p.responseDisplacement), trialSide*abs(p.responseDisplacement))); + +% Stim flicker +% stimFlicker = sin((t - t.at(stimOn))*stimFlickerFrequency*2*pi) > 0; +stim = vis.grating(t, 'sine', 'gaussian'); +stim.sigma = p.sigma; +stim.spatialFreq = p.spatialFreq; +stim.phase = 2*pi*events.newTrial.map(@(v)rand); +stim.azimuth = azimuth; +%stim.contrast = trialContrast.at(stimOn)*stimFlicker; +stim.contrast = trialContrast; +stim.show = stimOn.to(stimOff); + +visStim.stim = stim; + +%% Display and save +% events.pPerf = (baselinePerf - windowedPerf)/baselinePerf > p.pctPerfDecrease/100; +% Wheel and stim +events.azimuth = azimuth; + +% Trial times +events.prestimQuiescentPeriod = prestimQuiescentPeriod; +events.stimulusOn = stimOn; +events.interactiveOn = stimOn; +events.stimulusOff = stimOff; +events.feedback = iff(hit==1, hit, -1); +events.threshold = threshold; +% End trial samples a false when the next trial is to be a repeat trial. +% NB: the identity function is used to ensure that stimOff takes a value +% before endTrial +events.endTrial = at(~trialData.repeatTrial, stimOff.identity); +% Used to identify what form of disengagement has occured +events.disengaged = skipRepeats(keepWhen(cond(... + tooSlow, 'long RT',... + true, 'false'), events.trialNum > p.minTrials)); +events.windowedRT = windowedRT.map(fun.partial(@sprintf, '%.1f sec')); +events.baselineRT = baselineRT.map(fun.partial(@sprintf, '%.1f sec')); +events.pctDecrease = map(((baselinePerf - windowedPerf)/baselinePerf)*100, fun.partial(@sprintf, '%.1f%%')); +events.endAfter = trialDataInit.endAfter/60; + +% Trial side probability +events.bias = bias; + +% Performance +events.contrastSet = trialData.contrastSet; +events.repeatOnMiss = trialData.repeatOnMiss; +events.contrastLeft = iff(trialData.trialSide == -1, trialData.trialContrast, trialData.trialContrast*0); +events.contrastRight = iff(trialData.trialSide == 1, trialData.trialContrast, trialData.trialContrast*0); +% events.trialSide = trialData.trialSide; +events.hit = hit; +events.response = at(iff(response==3, 0, response), threshold); +events.useContrasts = trialData.useContrasts; +events.trialsToZeroContrast = trialData.trialsToZeroContrast; +events.hitBuffer = trialData.hitBuffer; +events.wheelGain = wheelGain; +events.totalWater = outputs.reward.scan(@plus, 0).map(fun.partial(@sprintf, '%.1f�l')); + +%% Defaults +try +% The entire stimulus/target contrast set +p.contrastSet = [1,0.5,0.25,0.125,0.06,0]'; +% (which conrasts to use at the beginning of training) +p.startingContrasts = double([true,true,false,false,false,false]'); +% (which contrasts to repeat on incorrect) +p.repeatOnMiss = double([true,true,false,false,false,false]'); +% (number of trials to judge rolling performance) +p.trialsToBuffer = 50; +% (number of trials after introducing 12.5% contrast to introduce 0%) +p.trialsToZeroContrast = 200; +p.spatialFreq = 1/10; +p.sigma = [7, 7]'; +% stimFlickerFrequency = 5; % DISABLED BELOW +p.startingAzimuth = 35; % (degrees) +p.responseDisplacement = 35; % (degrees) +% Starting reward size (this value is ignored after the first session) +p.rewardSize = 3; % (microliters) +% Initial wheel gain +p.initialGain = 8; % ~= 20 @ 90 deg; +p.normalGain = 4; % ~= 10 @ 90 deg; + +% Timing +p.prestimQuiescentTime = [0.2, 0.5, 0.35]'; % (seconds) +% p.cueInteractiveDelay = 0.2; +% Inter-trial interval on correct response +p.itiHit = 1; % (seconds) +% Inter-trial interval on incorrect response +p.itiMiss = 2; % (seconds) +p.responseWindow = 60; % (seconds) + +% How many times slower the subject must become in order to be marked as +% disengaged +p.rtCriterion = 5; % (multiplier) +% The percent decrease in performance that subject must exhibit to be +% marked as disengaged +p.pctPerfDecrease = 50; % (percent) +% The minimum number of trials to be completed before the subject may be +% classified as disengaged +p.minTrials = 400; +% The maximum number of seconds the subject can take to give a response +% before being classified as disengaged +p.maxRespWindow = 60; % (seconds) + +% Audio +p.missNoiseAmplitude = 0.01; +p.onsetToneAmplitude = 0.15; +catch +end +end +function wheelGain = initWheelGain(expRef, initialGain, normalGain) +subject = dat.parseExpRef(expRef); +expRef = dat.listExps(subject); +wheelGain = initialGain; +if length(expRef) > 1 + % Loop through blocks from latest to oldest, if any have the relevant + % parameters then carry them over + for check_expt = length(expRef)-1:-1:1 + previousBlockFilename = dat.expFilePath(expRef{check_expt}, 'block', 'master'); + trialNum = []; + if exist(previousBlockFilename,'file') + previousBlock = load(previousBlockFilename); + if isfield(previousBlock.block,'events')&&isfield(previousBlock.block.events,'newTrialValues') + trialNum = previousBlock.block.events.newTrialValues; + end + end + % Check if the relevant fields exist + if length(trialNum) > 200 + % Break the loop and use these parameters + wheelGain = normalGain; + break + end + end +end +end + +function trialDataInit = initializeTrialData(expRef, ... + contrastSet,startingContrasts,repeatOnMiss,trialsToBuffer, ... + trialsToZeroContrast,rewardSize) + +%%%% Get the subject +% (from events.expStart - derive subject from expRef) +subject = dat.parseExpRef(expRef); + +startingContrasts = logical(startingContrasts)'; +repeatOnMiss = logical(repeatOnMiss)'; + +%%%% Initialize all of the session-independent performance values +trialDataInit = struct; + +% Store which trials are repeated on miss +trialDataInit.repeatOnMiss = repeatOnMiss; +% Set up the flag for repeating incorrect +trialDataInit.repeatTrial = false; +% Initialize hit/miss +trialDataInit.hit = nan; + +%%%% Load the last experiment for the subject if it exists +% (note: MC creates folder on initilization, so start search at 1-back) +expRef = dat.listExps(subject); +% Check how many days mouse has been trained +[~, dates] = dat.parseExpRef(expRef); +dayNum = find(floor(now) == unique(dates), 1, 'last'); +trialDataInit.endAfter = iff(dayNum<3, 60*20*dayNum, Inf); +trialDataInit.endAfter = Inf; + +useOldParams = false; +if length(expRef) > 1 + % Loop through blocks from latest to oldest, if any have the relevant + % parameters then carry them over + for check_expt = length(expRef)-1:-1:1 + learned = isLearned(expRef{check_expt}); + previousBlockFilename = dat.expFilePath(expRef{check_expt}, 'block', 'master'); + if exist(previousBlockFilename,'file') + previousBlock = load(previousBlockFilename); + if ~isfield(previousBlock.block, 'outputs')||... + ~isfield(previousBlock.block.outputs, 'rewardValues')||... + isempty(previousBlock.block.outputs.rewardValues) + lastRewardSize = rewardSize; + else + lastRewardSize = previousBlock.block.outputs.rewardValues(end); + end + + if isfield(previousBlock.block,'events') + previousBlock = previousBlock.block.events; + else + previousBlock = []; + end + end + % Check if the relevant fields exist + if exist('previousBlock','var') && all(isfield(previousBlock, ... + {'useContrastsValues','hitBufferValues','trialsToZeroContrastValues'})) &&... + length(previousBlock.newTrialValues) > 5 + % Break the loop and use these parameters + useOldParams = true; + break + end + end +end + +if useOldParams + % If the last experiment file has the relevant fields, set up performance + + % Which contrasts are currently in use + try + len = length(previousBlock.contrastSetValues)/length(previousBlock.contrastSetTimes); + trialDataInit.contrastSet = previousBlock.contrastSetValues(end-len+1:end); + catch + len = length(contrastSet'); + trialDataInit.contrastSet = contrastSet'; + end + trialDataInit.useContrasts = previousBlock.useContrastsValues(end-len+1:end); + + % The buffer to judge recent performance for adding contrasts + trialDataInit.hitBuffer = ... + previousBlock.hitBufferValues(:,end-len+1:end,:); + + % The countdown to adding 0% contrast + trialDataInit.trialsToZeroContrast = previousBlock.trialsToZeroContrastValues(end); + + % If zero contrasts have been introduced and lapse rate is < 0.2 for + % 50% contrasts, remove them. +% if trialDataInit.trialsToZeroContrast == 0 && ... +% sum(trialDataInit.hitBuffer(:,2,1))/size(trialDataInit.hitBuffer,1) > 0.8 && ... +% sum(trialDataInit.hitBuffer(:,2,2))/size(trialDataInit.hitBuffer,1) > 0.8 +% trialDataInit.useContrasts(trialDataInit.contrastSet == 0.5) = false; +% end + + % If the subject did over 200 trials last session, reduce the reward by + % 0.1, unless it is 2ml + if length(previousBlock.newTrialValues) > 200 && lastRewardSize > 1.5 + trialDataInit.rewardSize = lastRewardSize-0.1; + else + trialDataInit.rewardSize = lastRewardSize; + end + if learned + % Remove repeat on incorrect + trialDataInit.repeatOnMiss = zeros(1,length(trialDataInit.contrastSet)); + end + +else + % If this animal has no previous experiments, initialize performance + % Store the contrasts which are used + trialDataInit.contrastSet = contrastSet'; + trialDataInit.useContrasts = startingContrasts; + trialDataInit.hitBuffer = nan(trialsToBuffer, length(contrastSet), 2); % two tables, one for each side + trialDataInit.trialsToZeroContrast = trialsToZeroContrast; + % Initialize water reward size & wheel gain + trialDataInit.rewardSize = rewardSize; +end + +% Set the first contrast +contrasts = trialDataInit.contrastSet(trialDataInit.useContrasts); +w = ((contrasts~=0) + 1) / length(unique([contrasts, -contrasts])); +trialDataInit.trialContrast = randsample(contrasts, 1, true, w); +trialDataInit.trialSide = iff(rand <= 0.5, -1, 1); +end + +function trialData = updateTrialData(trialData,responseData) +% Update the performance and pick the next contrast +stimDisplacement = responseData(1); +response = responseData(3); +% bias normalized by trial number: abs(bias) = 0:1 +bias = responseData(4)/10; +% windowedRT = responseData(2); +% trialNum = responseData(3); + +% if trialNum > 50 && windowedRT < 60 +% trialData.wheelGain = 3; +% end +% +%%%% Get index of current trial contrast +currentContrastIdx = trialData.trialContrast == trialData.contrastSet; + +%%%% Define response type based on trial condition +trialData.hit = response~=3 && stimDisplacement*trialData.trialSide < 0; + +% Index for whether contrast was on the left or the right as performance is +% calculated for both sides. If the contrast was on the left, the index is +% 1, otherwise 2 +trialSideIdx = iff(trialData.trialSide<0, 1, 2); + + +%%%% Update buffers and counters if not a repeat trial +if ~trialData.repeatTrial + %%%% Contrast-adding performance buffer + % Update hit buffer for running performance + trialData.hitBuffer(:,currentContrastIdx,trialSideIdx) = ... + [trialData.hit;trialData.hitBuffer(1:end-1,currentContrastIdx,trialSideIdx)]; +end + +%%%% Add new contrasts as necessary given performance +% This is based on the last trialsToBuffer trials for rolling performance +% (these parameters are hard-coded because too specific) +% (these are side-dependent) +current_min_contrast = min(trialData.contrastSet(trialData.useContrasts & trialData.contrastSet ~= 0)); +trialsToBuffer = size(trialData.hitBuffer,1); +switch current_min_contrast + + case 0.5 + % Lower from 0.5 contrast after > 70% correct + min_hit_percentage = 0.70; + + contrast_buffer_idx = ismember(trialData.contrastSet,[0.5,1]); + contrast_total_trials = sum(~isnan(trialData.hitBuffer(:,contrast_buffer_idx,:))); + % If there have been enough buffer trials, check performance + if sum(contrast_total_trials) >= size(trialData.hitBuffer,1) + % Sample as evenly as possible across pooled contrasts. Here + % we pool the columns representing the 50% and 100% contrasts + % for each side (dim 3) individually, then shift the dimentions + % so that pooled_hits(1,:) = all 50% and 100% trials on the + % left, and pooled_hits(2,:) = all 50% and 100% trials on the + % right. + pooled_hits = shiftdim(... + reshape(trialData.hitBuffer(:,contrast_buffer_idx,:),[],1,2), 2); + use_hits(1) = sum(pooled_hits(1,(find(~isnan(pooled_hits(1,:)),trialsToBuffer/2)))); + use_hits(2) = sum(pooled_hits(2,(find(~isnan(pooled_hits(2,:)),trialsToBuffer/2)))); + min_hits = find(1 - binocdf(1:trialsToBuffer/2,trialsToBuffer/2,min_hit_percentage) < 0.05,1); + if all(use_hits >= min_hits) + trialData.useContrasts(find(~trialData.useContrasts,1)) = true; + end + end + + case 0.25 + % Lower from 0.25 contrast after > 50% correct + min_hit_percentage = 0.70; + + contrast_buffer_idx = ismember(trialData.contrastSet,current_min_contrast); + contrast_total_trials = sum(~isnan(trialData.hitBuffer(:,contrast_buffer_idx,:))); + % If there have been enough buffer trials, check performance + if sum(contrast_total_trials) >= size(trialData.hitBuffer,1) + % Sample as evenly as possible across pooled contrasts + pooled_hits = shiftdim(... + reshape(trialData.hitBuffer(:,contrast_buffer_idx,:),[],1,2), 2); + use_hits(1) = sum(pooled_hits(1,(find(~isnan(pooled_hits(1,:)),trialsToBuffer/2)))); + use_hits(2) = sum(pooled_hits(2,(find(~isnan(pooled_hits(2,:)),trialsToBuffer/2)))); + min_hits = find(1 - binocdf(1:trialsToBuffer/2,trialsToBuffer/2,min_hit_percentage) < 0.05,1); + if all(use_hits >= min_hits) + trialData.useContrasts(find(~trialData.useContrasts,1)) = true; + end + end + + case 0.125 + % Lower from 0.125 contrast after > 65% correct + min_hit_percentage = 0.65; + + contrast_buffer_idx = ismember(trialData.contrastSet,current_min_contrast); + contrast_total_trials = sum(~isnan(trialData.hitBuffer(:,contrast_buffer_idx,:))); + % If there have been enough buffer trials, check performance + if sum(contrast_total_trials) >= size(trialData.hitBuffer,1) + % Sample as evenly as possible across pooled contrasts + pooled_hits = shiftdim(... + reshape(trialData.hitBuffer(:,contrast_buffer_idx,:),[],1,2), 2); + use_hits(1) = sum(pooled_hits(1,(find(~isnan(pooled_hits(1,:)),trialsToBuffer/2)))); + use_hits(2) = sum(pooled_hits(2,(find(~isnan(pooled_hits(2,:)),trialsToBuffer/2)))); + min_hits = find(1 - binocdf(1:trialsToBuffer/2,trialsToBuffer/2,min_hit_percentage) < 0.05,1); + if all(use_hits >= min_hits) + trialData.useContrasts(find(~trialData.useContrasts,1)) = true; + end + end + +end + +% 200 trials after 12.5 % contrast introduced, put 6% +% 400 trials after 12.5 % contrast introduced, put 0% +% 600 trials after 12.5 % contrast introduced, remove 50% +if min(trialData.contrastSet(trialData.useContrasts)) <= 0.125 && ... + trialData.trialsToZeroContrast > 0 + % Subtract one from the countdown + trialData.trialsToZeroContrast = trialData.trialsToZeroContrast-1; + + if trialData.trialsToZeroContrast == 0 && ... + ~trialData.useContrasts(trialData.contrastSet == 0.06) + trialData.useContrasts(trialData.contrastSet == 0.06) = true; % Add 6% + trialData.trialsToZeroContrast = 200; % Reset counter + + elseif trialData.trialsToZeroContrast == 0 && ... + ~trialData.useContrasts(trialData.contrastSet == 0) + trialData.useContrasts(trialData.contrastSet == 0) = true; % Add 0% + trialData.trialsToZeroContrast = 200; % Reset counter + + elseif trialData.trialsToZeroContrast == 0 && ... + trialData.useContrasts(trialData.contrastSet == 0) + trialData.useContrasts(trialData.contrastSet == 0.5) = false; % Remove 50% + end +end + +%%%% Set flag to repeat - skip trial choice if so +if ~trialData.hit && any(trialData.repeatOnMiss==true) && ... + ismember(trialData.trialContrast,trialData.contrastSet(trialData.repeatOnMiss)) + % If the response is a no-go, repeat the same trial side + if response ~= 3 + % Otherwise take biased sample from normal distribution + sd = 0.5; % standard deviation + r = 0.5 + sd.*randn; % pull number from normal dist with mean 0.5 + trialData.trialSide = iff((r - bias) > 0.5, 1, -1); + % trialData.trialSide = iff(binornd(1,bias), + end + trialData.repeatTrial = true; + return +else + trialData.repeatTrial = false; +end + +%%%% Pick next contrast + +% Next contrast is random from current contrast set +contrasts = trialData.contrastSet(trialData.useContrasts); +w = ((contrasts~=0) + 1) / length(unique([contrasts, -contrasts])); +trialData.trialContrast = randsample(contrasts, 1, true, w); +%%%% Pick next side +trialData.trialSide = iff(rand <= 0.5, -1, 1); +end +function learned = isLearned(ref) +learned = false; +subject = dat.parseExpRef(ref); +expRef = dat.listExps(subject); +j = 1; +pooledCont = []; +pooledIncl = []; +pooledChoice = []; +for i = length(expRef):-1:1 + p = dat.expFilePath(expRef{i}, 'block', 'master'); + if exist(p,'file')==2 + % Block doesn't exist + p = fileparts(p); + else + fprintf('No block file for session %s: skipping\n', expRef{i}) + continue + end + try + feedback = readNPY(fullfile(p,'_ibl_trials.feedbackType.npy')); + contrastLeft = readNPY(fullfile(p,'_ibl_trials.contrastLeft.npy')); + contrastRight = readNPY(fullfile(p,'_ibl_trials.contrastRight.npy')); + incl = readNPY(fullfile(p,'_ibl_trials.included.npy')); + choice = readNPY(fullfile(p,'_ibl_trials.choice.npy')); + catch + warning('isLearned:ALFLoad:MissingFiles', ... + 'Unable to load files for session %s', expRef{i}) + continue + end + % If the zero contrast stimuli have not been introduced, the subject + % can't have learned. NB: Unfortunately if the hand of fate not once + % chose a zero contrast trial then the mouse would fail here, even if it + % was available to sample. This is fairly unlikely to happen and this + % method is much quicker than loading the block file to retreive the + % actual contrast set. + contrast = diff([contrastLeft,contrastRight],[],2); + if ~any(contrast==0) + fprintf('Low contrasts not yet introduced\n') + return + end + perfOnEasy = sum(feedback==1 & abs(contrast > 0.25)) / sum(abs(contrast > 0.25)); + if length(feedback) > 200 && perfOnEasy > 0.8 + pooledCont = [pooledCont; contrast]; + pooledIncl = [pooledIncl; incl]; + pooledChoice = [pooledChoice; choice]; + if j < 3 + j = j+1; + else + % All three sessions meet criteria + contrastSet = unique(pooledCont); + nn = arrayfun(@(c)sum(pooledCont==c & pooledIncl), contrastSet); + pp = arrayfun(@(c)sum(pooledCont==c & pooledIncl & pooledChoice==-1), contrastSet)./nn; + pars = psy.mle_fit_psycho([contrastSet';nn';pp'], 'erf_psycho_2gammas',... + [mean(contrastSet), 3, 0.05, 0.05],... + [min(contrastSet), 10, 0, 0],... + [max(contrastSet), 30, 0.4, 0.4]); + if abs(pars(1)) < 16 && pars(2) < 19 && pars(3) < 0.2 && pars(4) < 0.2 + learned = true; + else + fprintf('Fit parameter values below threshold\n') + return + end + end + else + fprintf('Low trial count or performance at high contrast\n') + return + end +end +end \ No newline at end of file diff --git a/tests/expDefinitions/choiceWorld_parameters.mat b/tests/expDefinitions/choiceWorld_parameters.mat new file mode 100644 index 0000000000000000000000000000000000000000..35d67dbd3e5e9d754c33dd1387b8e9ef492ba80f GIT binary patch literal 709 zcmeZu4DoSvQZUssQ1EpO(M`+DN!3vZ$Vn_o%P-2cQV4Jk_w+L}(NS<NN=+<DO;O0t zvr-5tO;vD9O;RwlR4_8OGBUC<F;g%y0P=_g3^2gx>B|pf+cGgQRLogBb$`CFp~&&t zpBLplLU=-wk`moTbvP7VRRbrt%KJ8JEtD?Me?Lk6=(oiE!Ojth@44z-o^Mjzc;MI( z9%&)&dZvA+pB9z(a&4`+WPLjR_v3W?{gby#B&$pJti5^7Y|XJ}*UtQkJGefgf5$T> zjxCXAvNycV`Ln8(S=F=HncYFh+&M=0RG_uAPb9BkL6wsHLc^avmrkgsEs#5w$gxYw zj(x3|cf_9=uOBj=OOR||=(4GJx6IR^DPG;H(h7w&Gam>%4UkPwQo7*ac|-Ifi>91f zHuvuR$GLnxdvyFYz9q8=H!NY~I&uBYf=)FXhD7!m%eJ}A)(>g@5a{6V*e=UqQZRY0 z!vdKl>bg0TZ+`wVA>!tmh*MFTCwCi*81@<qx;KSqq$$ly$@D*bda1^bE&1Q$Pq9zl z_+t6>kRH>P0{v->Q~g}lFM8zqWLxTA*63^3qn4R-|8=cZ-IiIazPx_>v$x;Z{GGK` zK04j}{=ZxH*14jO@9cJ&bIa-TKl%N|jPK_@wk<n8@%Nh(S7mqdy03e9!Zx9Pf4|oD znJ>$Oe_ttj8Xs@}_TP-Xo<Hxg*3`1rAO7{bytDS?Ycrp>(Wh1Ux66FKUh*X7)yBk= z!sm78J>PMDmhyM~pKInm&dRC1yI1b{*AE9@{uBRyF!lZ8^xAX2+%d_l*R>0-Z>syf z@Bf(_*KPm)k`+C2QakOB-X#mWProg!f`aG2d7WM}!%ddsviWtp>N<lki31BSO@5dg Zc=r0LAK6oXc-nXHY972Hznbqt9RO3rIJE!( literal 0 HcmV?d00001 diff --git a/tests/inferParamsPerformanceTest.m b/tests/inferParamsPerformanceTest.m new file mode 100644 index 00000000..a6a5ff86 --- /dev/null +++ b/tests/inferParamsPerformanceTest.m @@ -0,0 +1,21 @@ +function tests = inferParamsPerformanceTest +% expDefPath = fullfile(fileparts(which('addRigboxPaths')), 'tests', 'expDefinitions'); +% def1 = fullfile(expDefPath, 'advancedChoiceWorld.m'); +% def2 = fullfile(expDefPath, 'choiceWorld.m'); +% fcn{1} = @()exp.inferParameters(def1); +% fcn{2} = @()exp.inferParameters(def2); + +tests = functiontests(localfunctions); +end + +function testInferParams(testCase) +expDefPath = fullfile(fileparts(which('addRigboxPaths')), 'tests', 'expDefinitions'); +def1 = fullfile(expDefPath, 'advancedChoiceWorld.m'); +exp.inferParameters(def1); +end + +function testInferParams2(testCase) +expDefPath = fullfile(fileparts(which('addRigboxPaths')), 'tests', 'expDefinitions'); +def2 = fullfile(expDefPath, 'choiceWorld.m'); +exp.inferParameters(def2); +end \ No newline at end of file diff --git a/tests/inferParamsTest.m b/tests/inferParamsTest.m new file mode 100644 index 00000000..a6e0a1bc --- /dev/null +++ b/tests/inferParamsTest.m @@ -0,0 +1,65 @@ +%inferParams test +expDefPath = fullfile(fileparts(which('addRigboxPaths')), 'tests', 'expDefinitions'); + +% preconditions +parameters = exp.inferParameters(@nop); +correct = struct('numRepeats', 1000, 'defFunction', which('nop'), 'type', 'custom'); +assert(isequal(parameters, correct),'Fundamental problem: inferParameters not returning pars') + +%% Test 1: advancedChoiceWorld +% Test an experiment definition that has conditional parameters, an +% experiment panel function file and various default paramters data types +pars = exp.inferParameters([expDefPath filesep 'advancedChoiceWorld.m']); +load(fullfile(expDefPath, 'advancedChoiceWorld_parameters.mat')); + +assert(isequal(pars, parameters), 'Unexpected parameter struct returned') + +%% Test 2: choiceWorld +% Test an experiment definition that has no conditional parameters +pars = exp.inferParameters([expDefPath filesep 'choiceWorld.m']); +load(fullfile(expDefPath, 'choiceWorld_parameters.mat')); + +assert(isequal(pars, parameters), 'Unexpected parameter struct returned') + +%% Test 3: single global parameter +% Test an experiment definition that has single parameter which is a +% character array +pars = exp.inferParameters(@singleCharParam); + +parameters = struct(... + 'char', 'charecter array',... + 'numRepeats', 1000,... + 'defFunction', '',... + 'type', 'custom'); + +assert(isequal(pars, parameters), 'Unexpected parameter struct returned') + +%% Test 4: reserved names +% Test an experiment definition that uses reserved parameter names +try + pars = exp.inferParameters(@reservedParams); + id = ''; +catch ex + id = ex.identifier; +end +assert(strcmp(id, 'exp:InferParameters:ReservedParameters'), ... + 'Failed to throw reserved parameter name error') + + +function singleCharParam(~, ~, p, varargin) +% Helper function to test an expDef where there is a single parameter which +% is a charecter array +p.char +p.char = 'charecter array'; +end + +function reservedParams(~, ~, p, varargin) +% Helper function to test use of reserved parameter names +p.randomiseConditions; +p.services; +p.expPanelFun; +p.numRepeats; +p.defFunction; +p.waterType; +p.isPassive; +end \ No newline at end of file From de7c0c23500274bcae6c653672824a8b2b8693cd Mon Sep 17 00:00:00 2001 From: kevin-j-miller <kjmd10@gmail.com> Date: Wed, 20 Feb 2019 16:00:33 +0000 Subject: [PATCH 293/393] modify alyx_matlab --- alyx-matlab | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alyx-matlab b/alyx-matlab index c5c5c1ba..a013557f 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit c5c5c1ba22dce86549ed56fd257c3852f5949391 +Subproject commit a013557feab80af3b90903a6b4cd06b8b5c85293 From fde8177a6a44164d8365b95b201c08955b5c4079 Mon Sep 17 00:00:00 2001 From: kevin-j-miller <kjmd10@gmail.com> Date: Wed, 20 Feb 2019 16:01:37 +0000 Subject: [PATCH 294/393] move alyxpanel property to private setaccess --- +eui/MControl.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/+eui/MControl.m b/+eui/MControl.m index 9d5c201c..0c432533 100644 --- a/+eui/MControl.m +++ b/+eui/MControl.m @@ -17,10 +17,10 @@ properties LoggingDisplay % control for showing log output - AlyxPanel % holds the AlyxPanel object (see buildUI(), eui.AlyxPanel()) end properties (SetAccess = private) + AlyxPanel % holds the AlyxPanel object (see buildUI(), eui.AlyxPanel()) LogSubject % Subject selector control NewExpSubject % Experiment selector control NewExpType % Experiment type selector control From 6f07fc8ec517b60ec593733f3e57daea43221795 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 21 Feb 2019 18:32:30 +0200 Subject: [PATCH 295/393] Bugfix for registering hw info --- +srv/expServer.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/+srv/expServer.m b/+srv/expServer.m index 7fb3ca2f..7af7d14c 100644 --- a/+srv/expServer.m +++ b/+srv/expServer.m @@ -274,7 +274,8 @@ function handleMessage(id, data, host) rig.stimWindow.flip(); % clear the screen after % save a copy of the hardware in JSON - fid = fopen(dat.expFilePath(expRef, 'hw-info', 'master', 'json'), 'w'); + hwInfo = dat.expFilePath(expRef, 'hw-info', 'master', 'json'); + fid = fopen(hwInfo, 'w'); fprintf(fid, '%s', obj2json(rig)); fclose(fid); if ~strcmp(dat.parseExpRef(expRef), 'default') From 6bceeeaf5d3be0052534a2bab21264631839b2c5 Mon Sep 17 00:00:00 2001 From: Jai Bhagat <j.bhagat@ucl.ac.uk> Date: Thu, 21 Feb 2019 18:55:16 +0000 Subject: [PATCH 296/393] bugfix for registering hw.info --- +srv/expServer.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/+srv/expServer.m b/+srv/expServer.m index 7fb3ca2f..7af7d14c 100644 --- a/+srv/expServer.m +++ b/+srv/expServer.m @@ -274,7 +274,8 @@ function handleMessage(id, data, host) rig.stimWindow.flip(); % clear the screen after % save a copy of the hardware in JSON - fid = fopen(dat.expFilePath(expRef, 'hw-info', 'master', 'json'), 'w'); + hwInfo = dat.expFilePath(expRef, 'hw-info', 'master', 'json'); + fid = fopen(hwInfo, 'w'); fprintf(fid, '%s', obj2json(rig)); fclose(fid); if ~strcmp(dat.parseExpRef(expRef), 'default') From 9b5cb2a1840fb08e78e4a13a2052d8d495b2276c Mon Sep 17 00:00:00 2001 From: jaib1 <j.bhagat@ucl.ac.uk> Date: Fri, 22 Feb 2019 16:42:02 +0000 Subject: [PATCH 297/393] added test for 'vis.sinusoidLayer' and updated 'alyx' (dev) and 'signals' (dev) submodules --- alyx-matlab | 2 +- signals | 2 +- tests/sinusoidLayer_test.m | 49 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 tests/sinusoidLayer_test.m diff --git a/alyx-matlab b/alyx-matlab index a013557f..b44b998d 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit a013557feab80af3b90903a6b4cd06b8b5c85293 +Subproject commit b44b998da6323289adeaf404eddd637251174435 diff --git a/signals b/signals index c81b99e0..4b45e84b 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit c81b99e0922dbab9ab5d9a9770ff476c96fb6126 +Subproject commit 4b45e84b15b052bbba897126b9b6a27e4c7cd0e6 diff --git a/tests/sinusoidLayer_test.m b/tests/sinusoidLayer_test.m new file mode 100644 index 00000000..41fe9001 --- /dev/null +++ b/tests/sinusoidLayer_test.m @@ -0,0 +1,49 @@ +%% Test 1: vis.grating default values +azimuth = 0; spatialFreq = 1/15; phase = 0; orientation = 0; +[layer, image] = vis.sinusoidLayer(azimuth, spatialFreq, phase, orientation); +TestAns = [layer.texOffset(1), layer.texAngle, layer.size(1)]; +ExpectedAns = [0 0 15]; +assert(isequal(TestAns,ExpectedAns), 'Test 1 failed.'); + +%% Test 2: Negative Azimuth +azimuth = -90; spatialFreq = 1/15; phase = 0; orientation = 0; +[layer, image] = vis.sinusoidLayer(azimuth, spatialFreq, phase, orientation); +TestAns = [layer.texOffset(1), layer.texAngle, layer.size(1)]; +ExpectedAns = [-90 0 15]; +assert(isequal(TestAns,ExpectedAns), 'Test 2 failed.'); + +%% Test 3: High Spatial Frequency +azimuth = 0; spatialFreq = 2; phase = 0; orientation = 0; +[layer, image] = vis.sinusoidLayer(azimuth, spatialFreq, phase, orientation); +TestAns = round([layer.texOffset(1), layer.texAngle, layer.size(1)],4); +ExpectedAns = [0 0 0.5000]; +assert(isequal(TestAns,ExpectedAns), 'Test 3 failed.'); + + +%% Test 4: Negative Phase +azimuth = 0; spatialFreq = 1/15; phase = -90; orientation = 0; +[layer, image] = vis.sinusoidLayer(azimuth, spatialFreq, phase, orientation); +TestAns = round([layer.texOffset(1), layer.texAngle, layer.size(1)],4); +ExpectedAns = [10.1408 0 15.0000]; +assert(isequal(TestAns,ExpectedAns), 'Test 4 failed.'); + +%% Test 5: Negative Orientation +azimuth = 0; spatialFreq = 1/15; phase = 0; orientation = -90; +[layer, image] = vis.sinusoidLayer(azimuth, spatialFreq, phase, orientation); +TestAns = [layer.texOffset(1), layer.texAngle, layer.size(1)]; +ExpectedAns = [0 -90 15]; +assert(isequal(TestAns,ExpectedAns), 'Test 5 failed.'); + +%% Test 6: Non-zero values for all input args +azimuth = 45; spatialFreq = 7/15; phase = 30; orientation = 60; +[layer, image] = vis.sinusoidLayer(azimuth, spatialFreq, phase, orientation); +TestAns = round([layer.texOffset(1), layer.texAngle, layer.size(1)],4); +ExpectedAns = [24.1600 60.0000 2.1429]; +assert(isequal(TestAns,ExpectedAns), 'Test 6 failed.'); + +%% Test 7: Impossible Spatial Frequency +azimuth = 45; spatialFreq = -1/15; phase = 30; orientation = 60; +[layer, image] = vis.sinusoidLayer(azimuth, spatialFreq, phase, orientation); +TestAns = round([layer.texOffset(1), layer.texAngle, layer.size(1)],4); +ExpectedAns = [10.8803 60.0000 -15.0000]; +assert(isequal(TestAns,ExpectedAns), 'Test 7 failed.'); \ No newline at end of file From 5f1a10e60c01ec7f22656f7582013d4b3b651ed7 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Mon, 25 Feb 2019 16:18:57 +0200 Subject: [PATCH 298/393] Update readme Added coverage badge --- readme.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/readme.md b/readme.md index 6f6e5470..c2fed245 100644 --- a/readme.md +++ b/readme.md @@ -1,6 +1,8 @@ ---------- # Rigbox +[![Coverage Status](https://coveralls.io/repos/github/cortex-lab/Rigbox/badge.svg?branch=master)](https://coveralls.io/github/cortex-lab/Rigbox?branch=master) + Rigbox is a (mostly) object-oriented MATLAB software package for designing and controlling neurophysiological behavioural experiments (principally, the [steering wheel setup](https://www.ucl.ac.uk/cortexlab/tools/wheel) which [we](https://www.ucl.ac.uk/cortexlab) developed to probe mouse behaviour). Rigbox requires two machines, one for stimulus presentation ('the stimulus server') and another for controlling and monitoring the experiment ('mc'). ## Getting Started From aa123dbe2618e48bfac39815f7c8e38f3a48f8a1 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 26 Feb 2019 15:52:17 +0200 Subject: [PATCH 299/393] Started AlyxPanel tests --- +eui/AlyxPanel.m | 4 ++-- alyx-matlab | 2 +- tests/+dat/paths.m | 5 ++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index f8b6da14..1e367cf3 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -211,7 +211,7 @@ function delete(obj) end end - function login(obj) + function login(obj, varargin) % Used both to log in and out of Alyx. Logging means to % generate an Alyx token with which to send/request data. % Logging out does not cause the token to expire, instead the @@ -222,7 +222,7 @@ function login(obj) % Are we logging in or out? if ~obj.AlyxInstance.IsLoggedIn % logging in % attempt login - obj.AlyxInstance = obj.AlyxInstance.login(); % returns an instance if success, empty if you cancel + obj.AlyxInstance = obj.AlyxInstance.login(varargin{:}); % returns an instance if success, empty if you cancel if obj.AlyxInstance.IsLoggedIn % successful % Start log in timer, to automatically log out after 30 % minutes of 'inactivity' (defined as not calling diff --git a/alyx-matlab b/alyx-matlab index dd2ab36d..8b4f4f96 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit dd2ab36de59843edc94c15956967731d81173b74 +Subproject commit 8b4f4f96f7cfe4094cb4b2b8690aeb454f22c7b7 diff --git a/tests/+dat/paths.m b/tests/+dat/paths.m index 95e39049..7d22e839 100644 --- a/tests/+dat/paths.m +++ b/tests/+dat/paths.m @@ -18,9 +18,8 @@ p.rigbox = fileparts(which('addRigboxPaths')); % Repository for local copy of everything generated on this rig p.localRepository = 'C:\LocalExpData'; -p.localAlyxQueue = fullfile(p.rigbox, 'tests', 'data', 'alyx'); -p.databaseURL = 'https://alyx-dev.cortexlab.net'; -% p.databaseURL = 'https://dev.alyx.internationalbrainlab.org/'; +p.localAlyxQueue = fullfile(p.rigbox, 'alyx-matlab', 'tests', 'data'); +p.databaseURL = 'https://test.alyx.internationalbrainlab.org'; p.gitExe = 'C:\Program Files\Git\cmd\git.exe'; % Under the new system of having data grouped by mouse From cf9a2c2a4d940a443e46fa5ebdba62840d7f3923 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 27 Feb 2019 13:06:13 +0200 Subject: [PATCH 300/393] Change to validation params --- tests/expDefinitions/choiceWorld_parameters.mat | Bin 709 -> 701 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/expDefinitions/choiceWorld_parameters.mat b/tests/expDefinitions/choiceWorld_parameters.mat index 35d67dbd3e5e9d754c33dd1387b8e9ef492ba80f..0a70ff65f120214e81f5776ea788184a1a589793 100644 GIT binary patch delta 614 zcmV-s0-62A1-%83G#FQ9WFSUmVjwa%ATcvKFflqbHXt%EF*%V@BavVQk#rD$H39$t zc$~eJzi$&U6vv&*kG2dI14x}%*ils$gpfi>)MVjD+n`G52HeZL<Vmr8*giFc6?Rtm zBRZE2i3K($X4sHe7#Z>1`GW6=3V7g2KA*pNpL@^GFY+cK<n}rt9W$GW1f;OGT{oO@ z!Hk4T%0PourC*C&w!i-{k4SKTEveFnpvlsZqeeO#P~?uNafpsnDc(h;sv<)^l7?4R zQK@?Z73{KPhB_UC5xJ189)}37K#v5kcqWm405a}FB!Ma@JQE2@Bim0AR0Ld)kO7g9 z0M++-B`@QcWZ&(hN@g?yX1tf;5%RG(S$dvOIX=V%I4McplW2;F!bwzr;e^pzJ%~zZ zR=Pb2PaIKGsgX~17sygQt%^N+0(r7JuiHr6N#hu##Y^!KNKtwdp0a~{r*4(U5bvct z(v>{*)5P6NVVh<=CcpZqWA(PPF8e664Kucv^!IKtf41{SJHPFoU3u|s|LnBu#@Rk6 zj@z%91?FwtlWTZ#fd|)rGStgP!QJ==Zo}%&E}ag(ES`6L@@*0SxYu)g=M{Iy#vPu+ zHCM#lGUN5D%swppCeH0s_fPw%_hC?KR`A)!RZs4gC)f1k+JC_Xzi|OsTl4Ra;+)=i z^+#FP|9zf2p4@v6F8kkqZ5G_z;aV%Wp~GFY>)y@qp)$8|{tWv)B1*HXNQDE`)ATqz zz{#<ghuYk0Duj8`hd95C%{3K9Q-Kj22{{{IWA!?(ir=Zl&(=|@v|xn%1n-T@A6JDe A!T<mO delta 622 zcmV-!0+Ic_1;quBG#FHMWgtdnVjwX&ATl#LGBP?eHXt%EF*%V@BavVQk#rD$Jpup# zc$}@3zi$&U6vut}(H}@u3{+xbVMi4#2q8r!QImxqO@k_-8*nf0k|)LXVZT#DSYhu9 ze?;f9A+f;5#0(n}3x5K>%jI%+jS97R(mTsv`{QRnzmvBZV<#Jot=ZN#tj6Zn%=%uz zuUL<`<^l+`S<Gv&?QHz2g##sjNpnL7KrA;yTocMsI~z<{4H0m9A|IhK`ApKtCuX1? z#$gD$B0dML7E4(CnCsyI&R`jbp+vHCyBy_GluC=LW^ofKY2Xm$P#tGtBG5od*cH(P zDIO+k3%kF^3Qb9jEKex*V+0d`qyY-Z*X*E)COiOP2biX|Mw>5Gh44{-Ku<x39SAgV zlbt}I9Hb{LY?ImZ^pIHDtXRrgDapGt6fuo65l4boXl5dwWNA;s%cUszv~Txc$_JCN z=%?3Ij5UOCFO~t7PDHPw;zi`PW&H(P*4J95Hw*nmeI>SOeJ7`Xx908lto@p`A6sWP z-~QS^J8c!?+`B}a+CuYx{vW?{0q>pTJ$A>D`|QZo9k~rhuHnc%V1K`lDz};GcT@Y+ za^+uNKkfdQy-E4r1$=!4f5ZCwcZq$;>o!d7TgxR6w;c1=bmUy>v0=#*&n=$kxX+p+ z_b>UZo#Se3ecint=k@Tx>HZi0!Da8`XMN613tVFb*DG+>vVQwTzW>bF*FF9E9TmjM zD`xyI(l|cy`#3sMQ=jbXXMC7O9SGAm*FJlGFjg4Aq0*D#ZBfnFs`zV__(?yA4JpCc IAFB?~ekw&PWB>pF From 6d78c2c820216e06674792ccb7e4377c5e2cd842 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 28 Feb 2019 16:49:52 +0200 Subject: [PATCH 301/393] Fix for xlim when single water restricted weight --- +eui/AlyxPanel.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index 1e367cf3..53d573fc 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -574,7 +574,7 @@ function viewSubjectHistory(obj, ax) box(ax, 'off'); % Change the plot x axis limits maxDate = max(dates([records.is_water_restricted]|~isnan([records.weighing_at]))); - if numel(dates) > 1 && ~isempty(maxDate) + if numel(dates) > 1 && ~isempty(maxDate) && min(dates) ~= maxDate xlim(ax, [min(dates) maxDate]) else maxDate = now; From 4f0cf69179993cddadf5160d19763f083cb67423 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 5 Mar 2019 14:08:38 +0200 Subject: [PATCH 302/393] Bug fix for differing def fun paths --- tests/inferParamsTest.m | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/inferParamsTest.m b/tests/inferParamsTest.m index a6e0a1bc..1b34cb19 100644 --- a/tests/inferParamsTest.m +++ b/tests/inferParamsTest.m @@ -12,6 +12,12 @@ pars = exp.inferParameters([expDefPath filesep 'advancedChoiceWorld.m']); load(fullfile(expDefPath, 'advancedChoiceWorld_parameters.mat')); +assert(strcmp(pars.defFunction, [expDefPath filesep 'advancedChoiceWorld.m']), ... + 'Incorrect expDef path') + +% Remove defFunction field before comparison +pars = rmfield(pars, 'defFunction'); +parameters = rmfield(parameters, 'defFunction'); assert(isequal(pars, parameters), 'Unexpected parameter struct returned') %% Test 2: choiceWorld @@ -19,6 +25,9 @@ pars = exp.inferParameters([expDefPath filesep 'choiceWorld.m']); load(fullfile(expDefPath, 'choiceWorld_parameters.mat')); +% Remove defFunction field before comparison +pars = rmfield(pars, 'defFunction'); +parameters = rmfield(parameters, 'defFunction'); assert(isequal(pars, parameters), 'Unexpected parameter struct returned') %% Test 3: single global parameter From 35a387060f523134ffddcdf8ff9b5d628df7f76d Mon Sep 17 00:00:00 2001 From: jaib1 <jaib1@mit.edu> Date: Fri, 15 Feb 2019 12:31:15 +0000 Subject: [PATCH 303/393] Rebased 'TestPanelClasses' onto 'dev' at diverge point c9c27a2 after pull request changes Test Panel as Classes modeled off SignalsExp & MControl variable name changes to exp.SignalsExp added 'cprintf.m' updated signals submodule updated signals submodule updates submodule signals updates signals submodule (TestPanelClasses branch) updated submodule signals (TestPanelClasses branch) MControl and mc commits to match dev branch alyx-related commits to match dev branch updated submodule signals (TestPanelClasses branch) updated submodule signals (TestPanelClasses branch) updated signals submodule (to 'TestPanelClasses' branch) and alyx-matlab(to 'dev' branch) and improved 'namedArg' updated signals submodule ('TestPanelClasses') after rebasing that branch in that submodule typo fix in 'hw.devices' made changes for Miles updated signals (TestPanelClasses) submodule Rebased 'TestPanelClasses' onto 'dev' at diverge point after pull request changes Rebased 'TestPanelClasses' onto 'dev' at diverge point Test Panel as Classes modeled off SignalsExp & MControl variable name changes to exp.SignalsExp added 'cprintf.m' updated signals submodule updated signals submodule updates submodule signals updates signals submodule (TestPanelClasses branch) updated submodule signals (TestPanelClasses branch) MControl and mc commits to match dev branch alyx-related commits to match dev branch updated submodule signals (TestPanelClasses branch) updated submodule signals (TestPanelClasses branch) updated signals submodule (to 'TestPanelClasses' branch) and alyx-matlab(to 'dev' branch) and improved 'namedArg' updated signals submodule ('TestPanelClasses') after rebasing that branch in that submodule typo fix in 'hw.devices' made changes for Miles updated signals (TestPanelClasses) submodule updated signals (TestPannelClasses) submodule after incorporating pull request changes undid variable name changes for Miles --- +eui/AlyxPanel.m | 20 ++++++++------ +eui/MControl.m | 2 +- +exp/SignalsExp.m | 2 ++ +exp/inferParameters.m | 55 ++++++++++++------------------------- +hw/devices.m | 6 ++-- alyx-matlab | 2 +- cb-tools/burgbox/namedArg.m | 2 +- cortexlab/+git/changes.m | 6 ---- signals | 2 +- 9 files changed, 39 insertions(+), 58 deletions(-) delete mode 100644 cortexlab/+git/changes.m diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index f8b6da14..ef221f98 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -217,6 +217,8 @@ function login(obj) % Logging out does not cause the token to expire, instead the % token is simply deleted from this object. + % Temporarily disable the Subject Selector + obj.NewExpSubject.UIControl.Enable = 'off'; % Reset headless flag in case user wishes to retry connection obj.AlyxInstance.Headless = false; % Are we logging in or out? @@ -282,6 +284,8 @@ function login(obj) notify(obj, 'Disconnected'); % Notify listeners of logout obj.log('Logged out of Alyx'); end + % Reable the Subject Selector + obj.NewExpSubject.UIControl.Enable = 'on'; obj.dispWaterReq() end @@ -314,7 +318,7 @@ function giveFutureWater(obj) 'enter space-separated numbers, i.e. \n',... '[tomorrow, day after that, day after that.. etc] \n\n',... 'Enter "0" to skip a day\nEnter "-1" to indicate training for that day\n']); - amtStr = inputdlg(prompt,'Future Amounts', [1 50]); + amtStr = newid(prompt,'Future Amounts', [1 50]); if isempty(amtStr)||~obj.AlyxInstance.IsLoggedIn return % user pressed 'Close' or 'x' end @@ -446,10 +450,10 @@ function recordWeight(obj, weight, subject) dlgTitle = 'Manual weight logging'; numLines = 1; defaultAns = {'',''}; - weight = inputdlg(prompt, dlgTitle, numLines, defaultAns); + weight = newid(prompt, dlgTitle, numLines, defaultAns); if isempty(weight); return; end end - % inputdlg returns weight as a cell, otherwise it may now be + % newid returns weight as a cell, otherwise it may now be weight = ensureCell(weight); % ensure it's a cell % convert to double if weight is a string weight = iff(ischar(weight{1}), str2double(weight{1}), weight{1}); @@ -601,13 +605,13 @@ function viewSubjectHistory(obj, ax) axWater = axes('Parent',plotBox); plot(axWater, dates, obj.round([records.given_water_total], 'up'), '.-'); hold(axWater, 'on'); - plot(axWater, dates, obj.round([records.given_water_hydrogel], 'down'), '.-'); - plot(axWater, dates, obj.round([records.given_water_liquid], 'down'), '.-'); + plot(axWater, dates, obj.round([records.given_water_supplement], 'down'), '.-'); + plot(axWater, dates, obj.round([records.given_water_reward], 'down'), '.-'); plot(axWater, dates, obj.round([records.expected_water], 'up'), 'r', 'LineWidth', 2.0); box(axWater, 'off'); xlim(axWater, [min(dates) maxDate]); set(axWater, 'XTickLabel', arrayfun(@(x)datestr(x, 'dd-mmm'), get(axWater, 'XTick'), 'uni', false)) - ylabel(axWater, 'water/hydrogel (mL)'); + ylabel(axWater, 'water (mL)'); % Create table of useful weight and water information, % sorted by date @@ -627,14 +631,14 @@ function viewSubjectHistory(obj, ax) arrayfun(@(x)iff(isnan(x), [], @()sprintf('%.1f', 0.8*(x-iw)+iw)), expected', 'uni', false), ... weightPctByDate'); waterDat = (... - num2cell(horzcat([records.given_water_liquid]', [records.given_water_hydrogel]', ... + num2cell(horzcat([records.given_water_reward]', [records.given_water_supplement]', ... [records.given_water_total]', [records.expected_water]',... [records.given_water_total]'-[records.expected_water]'))); waterDat = cellfun(@(x)sprintf('%.2f', x), waterDat, 'uni', false); waterDat(~[records.is_water_restricted],[1,3]) = {'ad lib'}; dat = horzcat(dat, waterDat); - set(histTable, 'ColumnName', {'date', 'meas. weight', '80% weight', 'weight pct', 'water', 'hydrogel', 'total', 'min water', 'excess'}, ... + set(histTable, 'ColumnName', {'date', 'meas. weight', '80% weight', 'weight pct', 'water', 'supplement', 'total', 'min water', 'excess'}, ... 'Data', dat(end:-1:1,:),... 'ColumnEditable', false(1,5)); histbox.Widths = [ -1 725]; diff --git a/+eui/MControl.m b/+eui/MControl.m index 5b25075d..0c432533 100644 --- a/+eui/MControl.m +++ b/+eui/MControl.m @@ -20,6 +20,7 @@ end properties (SetAccess = private) + AlyxPanel % holds the AlyxPanel object (see buildUI(), eui.AlyxPanel()) LogSubject % Subject selector control NewExpSubject % Experiment selector control NewExpType % Experiment type selector control @@ -33,7 +34,6 @@ properties (Access = private) ParamEditor ParamPanel - AlyxPanel % holds the AlyxPanel object (see buildUI(), eui.AlyxPanel()) BeginExpButton % The 'Start' button that begins an experiment RigOptionsButton % The 'Options' button that opens the rig options dialog NewExpFactory % A struct containing all availiable experiment types and function handles to constructors for their default parameters diff --git a/+exp/SignalsExp.m b/+exp/SignalsExp.m index f616ea70..d2fb2631 100644 --- a/+exp/SignalsExp.m +++ b/+exp/SignalsExp.m @@ -720,6 +720,8 @@ function mainLoop(obj) obj.Data.stimWindowRenderTimes(obj.StimWindowUpdateCount) = renderTime; obj.StimWindowInvalid = false; end + % make sure some minimum time passes before updating signals, to + % improve performance on MC if (obj.Clock.now - t) > 0.1 || obj.IsLooping == false sendSignalUpdates(obj); t = obj.Clock.now; diff --git a/+exp/inferParameters.m b/+exp/inferParameters.m index 945845d1..275c964f 100644 --- a/+exp/inferParameters.m +++ b/+exp/inferParameters.m @@ -5,12 +5,6 @@ % create some signals just to pass to the definition function and track % which parameter names are used -% if ischar(expdef) && file.exists(expdef) -% expdeffun = fileFunction(expdef); -% else -% expdeffun = expdef; -% expdef = which(func2str(expdef)); -% end if ischar(expdef) && file.exists(expdef) expdeffun = fileFunction(expdef); else @@ -18,38 +12,28 @@ expdef = which(func2str(expdef)); end -net = sig.Net; -e = struct; -e.t = net.origin('t'); -e.events = net.subscriptableOrigin('events'); -e.pars = net.subscriptableOrigin('pars'); -e.pars.CacheSubscripts = true; -e.visual = net.subscriptableOrigin('visual'); -e.audio.Devices = @dummyDev; -e.inputs = net.subscriptableOrigin('inputs'); -e.outputs = net.subscriptableOrigin('outputs'); +e = sig.void; +pars = sig.void(true); +audio.Devices = @dummyDev; try + expdeffun(e.t, e.events, pars, e.visual, e.inputs, e.outputs, audio); - expdeffun(e.t, e.events, e.pars, e.visual, e.inputs, e.outputs, e.audio); - - % paramNames will be the strings corresponding to the fields of e.pars + % paramNames will be the strings corresponding to the fields of pars % that the user tried to reference in her expdeffun. - paramNames = e.pars.Subscripts.keys'; - %The paramValues are signals corresponding to those parameters and they - %will all be empty, except when they've been given explicit numerical - %definitions right at the end of the function - and in that case, we'll - %take those values (extracted into matlab datatypes, from the signals, - %using .Node.CurrValue) to be the desired default values. - paramValues = e.pars.Subscripts.values'; - parsStruct = cell2struct(cell(size(paramNames)), paramNames); - for i = 1:size(paramNames,1) - parsStruct.(paramNames{i}) = paramValues{i}.Node.CurrValue; - end + parsStruct = pars.Subscripts; + + % Check for reserved fieldnames + reserved = {'randomiseConditions', 'services', 'expPanelFun', ... + 'numRepeats', 'defFunction', 'waterType', 'isPassive'}; + assert(~any(ismember(fieldnames(parsStruct), reserved)), ... + 'exp:InferParameters:ReservedParameters', ... + 'The following param names are reserved:\n%s', ... + strjoin(intersect(fieldnames(parsStruct), reserved), ', ')) + + szFcn = @(a)iff(ischar(a), @()size(a,1), @()size(a,2)); sz = iff(isempty(fieldnames(parsStruct)), 1,... % if there are no paramters sz = 1 - structfun(@(a)size(a,2), parsStruct)); % otherwise get number of columns - isChar = structfun(@ischar, parsStruct); % we disregard charecter arrays - if any(isChar); sz = sz(~isChar); end + structfun(szFcn, parsStruct)); % otherwise get number of columns % add 'numRepeats' parameter, where total number of trials = 1000 parsStruct.numRepeats = ones(1,max(sz))*floor(1000/max(sz)); parsStruct.defFunction = expdef; @@ -60,12 +44,9 @@ ExpPanel_fn = [path filesep ExpPanel_name ext]; if exist(ExpPanel_fn,'file'); parsStruct.expPanelFun = ExpPanel_name; end catch ex - net.delete(); rethrow(ex) end -net.delete(); - function dev = dummyDev(~) % Returns a dummy audio device structure, regardless of input % Returns a standard structure with values for generating tone @@ -75,4 +56,4 @@ 'DefaultSampleRate', 44100,... 'NrOutputChannels', 2); end -end \ No newline at end of file +end diff --git a/+hw/devices.m b/+hw/devices.m index 2734fe92..1bba5908 100644 --- a/+hw/devices.m +++ b/+hw/devices.m @@ -69,8 +69,8 @@ % Get list of audio devices devs = getOr(rig, 'audioDevices', PsychPortAudio('GetDevices')); % Sanitize the names - names = matlab.lang.makeValidName([{'default'} {devs(2:end).DeviceName}],... - 'ReplacementStyle', 'delete'); + names = matlab.lang.makeValidName({devs.DeviceName}, 'ReplacementStyle', 'delete'); + names = iff(ismember('default', names), names, @()[{'default'} names(2:end)]); for i = 1:length(names); devs(i).DeviceName = names{i}; end rig.audioDevices = devs; end @@ -93,4 +93,4 @@ function configure(deviceName, usedaq) end end -end \ No newline at end of file +end diff --git a/alyx-matlab b/alyx-matlab index dd2ab36d..55e3e2ad 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit dd2ab36de59843edc94c15956967731d81173b74 +Subproject commit 55e3e2adc57c42a1c84dbc0b8570c428f246f65d diff --git a/cb-tools/burgbox/namedArg.m b/cb-tools/burgbox/namedArg.m index 5e5820fb..72264f8a 100644 --- a/cb-tools/burgbox/namedArg.m +++ b/cb-tools/burgbox/namedArg.m @@ -8,7 +8,7 @@ % 2014-02 CB created -defIdx = find(cellfun(@(a) isequal(a, name), args), 1); +defIdx = find(cellfun(@(a) strcmpi(a, name), args), 1); if ~isempty(defIdx) present = true; value = args{defIdx + 1}; diff --git a/cortexlab/+git/changes.m b/cortexlab/+git/changes.m deleted file mode 100644 index d9e9f013..00000000 --- a/cortexlab/+git/changes.m +++ /dev/null @@ -1,6 +0,0 @@ -disp('Updating queued Alyx posts...') -posts = dirPlus(getOr(dat.paths, 'localAlyxQueue', 'C:/localAlyxQueue')); -posts = posts(endsWith(posts, 'put')); -newPosts = cellfun(@(str)[str(1:end-3) 'patch'], posts, 'uni', 0); -status = cellfun(@movefile, posts, newPosts); -assert(all(status), 'Unable to rename queued Alyx files, please do this manually') \ No newline at end of file diff --git a/signals b/signals index 93520307..ea8f85fe 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit 93520307c1cb1a9dc12fa8bda685a01b9ab80401 +Subproject commit ea8f85fe28313ca9562f9a52ff58114b3f7ef6de From 86fcbbbe86861182737cd09501611382a2bd4e75 Mon Sep 17 00:00:00 2001 From: jaib1 <j.bhagat@ucl.ac.uk> Date: Thu, 7 Mar 2019 11:05:17 +0000 Subject: [PATCH 304/393] updated signals (dev) submodule --- signals | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/signals b/signals index 8a56f9e6..01829db3 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit 8a56f9e6b3b5cf0d37c34e5b0b948c2e55bb45d8 +Subproject commit 01829db30c43561a0ec7481b14cfa895e31c59bb From b6b47dba83c449c464fc8f1bc1f79efc0957b3ca Mon Sep 17 00:00:00 2001 From: jaib1 <j.bhagat@ucl.ac.uk> Date: Thu, 7 Mar 2019 12:35:30 +0000 Subject: [PATCH 305/393] updated signals (TestPanelClasses) submodule --- signals | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/signals b/signals index ea8f85fe..38007424 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit ea8f85fe28313ca9562f9a52ff58114b3f7ef6de +Subproject commit 380074249abfd91b9f0be65fb3a9f5eb5b5944d6 From 6238fda95e8c724f6975d7ee3adfa3f2ffc1a5ea Mon Sep 17 00:00:00 2001 From: jaib1 <j.bhagat@ucl.ac.uk> Date: Thu, 7 Mar 2019 12:40:00 +0000 Subject: [PATCH 306/393] updated signals (dev, merged into from TestPanelClasses) submodule --- signals | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/signals b/signals index 38007424..2350bac3 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit 380074249abfd91b9f0be65fb3a9f5eb5b5944d6 +Subproject commit 2350bac34f61f0cecab49e7fcd9fa6c055157262 From 5be5059e4a336468534be169de98e39212d9940e Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 8 Mar 2019 17:06:30 +0200 Subject: [PATCH 307/393] Added tests and bug fix for timer deletion --- +eui/AlyxPanel.m | 3 +- tests/AlyxPanelTest.m | 133 +++++++++++++++++++++++++++++++++ tests/data/viewSubjectData.mat | Bin 0 -> 8840 bytes 3 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 tests/AlyxPanelTest.m create mode 100644 tests/data/viewSubjectData.mat diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index 53d573fc..7e4306d2 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -76,7 +76,8 @@ 'Toolbar', 'none',... 'NumberTitle', 'off',... 'Units', 'normalized',... - 'OuterPosition', [0.1 0.1 0.4 .4]); + 'OuterPosition', [0.1 0.1 0.4 .4],... + 'DeleteFcn', @(~,~)obj.delete); parent = uiextras.VBox('Parent', f,... 'Visible', 'on'); % subject selector diff --git a/tests/AlyxPanelTest.m b/tests/AlyxPanelTest.m new file mode 100644 index 00000000..b220d9cb --- /dev/null +++ b/tests/AlyxPanelTest.m @@ -0,0 +1,133 @@ +classdef AlyxPanelTest < matlab.unittest.TestCase + + properties + % Figure visibility setting before running tests + FigureVisibleDefault + % AlyxPanel instance + Panel + % Figure handle for AlyxPanel + hPanel + % Figure handle for any extra figures opened during tests + Figure + % List of subjects returned by the test database + Subjects = {'ZM_1085'; 'ZM_1087'; 'ZM_1094'; 'ZM_1098'; 'ZM_335'} + % bui.Selector object for setting the subject list in tests + SubjectUI + % Expected Y-axis labels for the viewSubjectHistory plots + Ylabels = {'water (mL)', 'weight as pct (%)', 'weight (g)'} + % The table data for the the viewSubjectHistory table + TableData + % Cell array of graph data for the the viewSubjectHistory plots. One + % cell per plot containing {xData, yData} arrays. + GraphData + end + + methods (TestClassSetup) + function killFigures(testCase) + testCase.FigureVisibleDefault = get(0,'DefaultFigureVisible'); + set(0,'DefaultFigureVisible','off'); + end + + function loadData(testCase) + % Loads validation data + % Graph data is a cell array where each element is the graph number + % (1:3) and within each element is a cell of X- and Y- axis values + % respecively + load('data/viewSubjectData.mat', 'tableData', 'graphData') + testCase.TableData = tableData; + testCase.GraphData = graphData; + end + + function setupPanel(testCase) + % Check paths file + assert(endsWith(which('dat.paths'), fullfile('tests','+dat','paths.m'))); + % Create figure for panel + testCase.hPanel = figure('Name', 'alyx GUI',... + 'MenuBar', 'none',... + 'Toolbar', 'none',... + 'NumberTitle', 'off',... + 'Units', 'normalized',... + 'OuterPosition', [0.1 0.1 0.4 .4]); + parent = uiextras.VBox('Parent', testCase.hPanel, 'Visible', 'on'); + testCase.Panel = eui.AlyxPanel(parent); + % subject selector + sbox = uix.HBox('Parent', parent); + bui.label('Select subject: ', sbox); + % Subject dropdown box + testCase.SubjectUI = bui.Selector(sbox, [{'default'}; testCase.Subjects]); + % set a callback on subject selection so that we can show water + % requirements for new mice as they are selected. This should + % be set by any other GUI that instantiates this object (e.g. + % MControl using this as a panel. + testCase.SubjectUI.addlistener('SelectionChanged', ... + @(src, evt)testCase.Panel.dispWaterReq(src, evt)); + + % Set Alyx Instance and log in + testCase.Panel.login('test_user', 'TapetesBloc18'); + testCase.fatalAssertTrue(testCase.Panel.AlyxInstance.IsLoggedIn,... + 'Failed to log into Alyx'); + end + end + + methods (TestClassTeardown) + function restoreFigures(testCase) + set(0,'DefaultFigureVisible',testCase.FigureVisibleDefault); + close(testCase.hPanel) + end + end + + methods (TestMethodTeardown) + function closeFigure(testCase) + % Close any figures opened during the test + if ~isempty(testCase.Figure); close(testCase.Figure); end + end + end + + methods (Test) + function test_viewSubjectHistory(testCase) + % Post some weights for plotting + + % Set new subject + testCase.SubjectUI.Selected = testCase.SubjectUI.Option{3}; + testCase.Panel.viewSubjectHistory + testCase.Figure = gcf(); + child_handles = testCase.Figure.Children.Children; + % Verify table data + testCase.assertTrue(isa(child_handles(1),'matlab.ui.control.Table')); + tableData = child_handles(1).Data; + testCase.verifyTrue(isequal(size(tableData), [ceil(now-737146) 9]), ... + 'Unexpected table data'); + expected = testCase.TableData; + % Remove empty days + idx = find(strcmp(expected{1,1}, tableData(:,1)),1); + tableData = tableData(idx:end,:); + testCase.verifyTrue(isequal(tableData, expected), 'Unexpected table data'); + + ax_h = child_handles(2).Children; + testCase.assertTrue(isa(ax_h, 'matlab.graphics.axis.Axes')) + testCase.assertTrue(length(ax_h)==3, 'Not all axes created') + + for i = 1:length(ax_h) + label = testCase.Ylabels{i}; + testCase.verifyEqual(ax_h(i).YLabel.String, label); + testCase.verifyEqual(length(ax_h(i).Children), ... + size(testCase.GraphData{i}{1},1)); + xData = vertcat(ax_h(i).Children(:).XData); + yData = vertcat(ax_h(i).Children(:).YData); + testCase.verifyEqual(xData, testCase.GraphData{i}{1}); + testCase.verifyEqual(yData, testCase.GraphData{i}{2}); + end + end + + function test_viewAllSubjects(testCase) + testCase.Panel.viewAllSubjects; + testCase.Figure = gcf(); + child_handle = testCase.Figure.Children.Children; + tableData = child_handle.Data; + expected = {'algernon', '0.00', '<html><body bgcolor=#FFFFFF>0.00</body></html>'}; + testCase.verifyTrue(isequal(tableData, expected)); + end + + end + +end \ No newline at end of file diff --git a/tests/data/viewSubjectData.mat b/tests/data/viewSubjectData.mat new file mode 100644 index 0000000000000000000000000000000000000000..7bd663b95c8630a659a07ceec01f7ad4a4d5371f GIT binary patch literal 8840 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 Q9Ak2j5=>Ti*GKdJ03HqOoB#j- literal 0 HcmV?d00001 From 447eb42415e4046364da2deac6606ce2c4f34a43 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 8 Mar 2019 18:06:04 +0200 Subject: [PATCH 308/393] Last weight used in calculation for subject history plots --- +eui/AlyxPanel.m | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index 7e4306d2..e8b24c6c 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -554,8 +554,10 @@ function viewSubjectHistory(obj, ax) obj.log('No weight data found for subject %s', obj.Subject); return end + weights = [records.weight]; + weights(isnan([records.weighing_at])) = nan; expected = [records.expected_weight]; - expected(expected==0|isnan([records.weighing_at])) = nan; + expected(expected==0|isnan(weights)) = nan; dates = cellfun(@(x)datenum(x), {records.date}); % build the figure to show it @@ -568,13 +570,13 @@ function viewSubjectHistory(obj, ax) ax = axes('Parent', plotBox); end - plot(ax, dates, [records.weighing_at], '.-'); + plot(ax, dates, weights, '.-'); hold(ax, 'on'); plot(ax, dates, ((expected-iw)*0.7)+iw, 'r', 'LineWidth', 2.0); plot(ax, dates, ((expected-iw)*0.8)+iw, 'LineWidth', 2.0, 'Color', [244, 191, 66]/255); box(ax, 'off'); % Change the plot x axis limits - maxDate = max(dates([records.is_water_restricted]|~isnan([records.weighing_at]))); + maxDate = max(dates([records.is_water_restricted]|~isnan(weights))); if numel(dates) > 1 && ~isempty(maxDate) && min(dates) ~= maxDate xlim(ax, [min(dates) maxDate]) else @@ -590,7 +592,7 @@ function viewSubjectHistory(obj, ax) if nargin==1 ax = axes('Parent', plotBox); - plot(ax, dates, ([records.weighing_at]-iw)./(expected-iw), '.-'); + plot(ax, dates, (weights-iw)./(expected-iw), '.-'); hold(ax, 'on'); plot(ax, dates, 0.7*ones(size(dates)), 'r', 'LineWidth', 2.0); plot(ax, dates, 0.8*ones(size(dates)), 'LineWidth', 2.0, 'Color', [244, 191, 66]/255); @@ -602,25 +604,25 @@ function viewSubjectHistory(obj, ax) axWater = axes('Parent',plotBox); plot(axWater, dates, obj.round([records.given_water_total], 'up'), '.-'); hold(axWater, 'on'); - plot(axWater, dates, obj.round([records.given_water_hydrogel], 'down'), '.-'); - plot(axWater, dates, obj.round([records.given_water_liquid], 'down'), '.-'); + plot(axWater, dates, obj.round([records.given_water_supplement], 'down'), '.-'); + plot(axWater, dates, obj.round([records.given_water_reward], 'down'), '.-'); plot(axWater, dates, obj.round([records.expected_water], 'up'), 'r', 'LineWidth', 2.0); box(axWater, 'off'); xlim(axWater, [min(dates) maxDate]); set(axWater, 'XTickLabel', arrayfun(@(x)datestr(x, 'dd-mmm'), get(axWater, 'XTick'), 'uni', false)) - ylabel(axWater, 'water/hydrogel (mL)'); + ylabel(axWater, 'water (mL)'); % Create table of useful weight and water information, % sorted by date histTable = uitable('Parent', histbox,... 'FontName', 'Consolas',... 'RowName', []); - weightsByDate = num2cell([records.weighing_at]); + weightsByDate = num2cell(weights); weightsByDate = cellfun(@(x)sprintf('%.1f', x), weightsByDate, 'uni', false); - weightsByDate(isnan([records.weighing_at])) = {[]}; - weightPctByDate = num2cell(([records.weighing_at]-iw)./(expected-iw)); + weightsByDate(isnan(weights)) = {[]}; + weightPctByDate = num2cell((weights-iw)./(expected-iw)); weightPctByDate = cellfun(@(x)sprintf('%.1f', x*100), weightPctByDate, 'uni', false); - weightPctByDate(isnan([records.weighing_at])|~[records.is_water_restricted]) = {[]}; + weightPctByDate(isnan(weights)|~[records.is_water_restricted]) = {[]}; dat = horzcat(... arrayfun(@(x)datestr(x), dates', 'uni', false), ... @@ -628,14 +630,14 @@ function viewSubjectHistory(obj, ax) arrayfun(@(x)iff(isnan(x), [], @()sprintf('%.1f', 0.8*(x-iw)+iw)), expected', 'uni', false), ... weightPctByDate'); waterDat = (... - num2cell(horzcat([records.given_water_liquid]', [records.given_water_hydrogel]', ... + num2cell(horzcat([records.given_water_reward]', [records.given_water_supplement]', ... [records.given_water_total]', [records.expected_water]',... [records.given_water_total]'-[records.expected_water]'))); waterDat = cellfun(@(x)sprintf('%.2f', x), waterDat, 'uni', false); waterDat(~[records.is_water_restricted],[1,3]) = {'ad lib'}; dat = horzcat(dat, waterDat); - set(histTable, 'ColumnName', {'date', 'meas. weight', '80% weight', 'weight pct', 'water', 'hydrogel', 'total', 'min water', 'excess'}, ... + set(histTable, 'ColumnName', {'date', 'meas. weight', '80% weight', 'weight pct', 'water', 'supplement', 'total', 'min water', 'excess'}, ... 'Data', dat(end:-1:1,:),... 'ColumnEditable', false(1,5)); histbox.Widths = [ -1 725]; From df607ec3ae2d42d6b0ef1543156406d13a90e10e Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Sun, 10 Mar 2019 12:56:02 +0200 Subject: [PATCH 309/393] Revert irrelevent commits --- +eui/ConditionPanel.m | 254 ---------------- +eui/FieldPanel.m | 165 ----------- +eui/MControl.m | 24 +- +eui/ParamEditor.m | 632 +++++++++++++++++++++++++++------------- +eui/ParamEditor_old.m | 516 -------------------------------- +exp/Parameters.m | 10 +- cortexlab/+git/update.m | 2 +- signals | 2 +- 8 files changed, 438 insertions(+), 1167 deletions(-) delete mode 100644 +eui/ConditionPanel.m delete mode 100644 +eui/FieldPanel.m delete mode 100644 +eui/ParamEditor_old.m diff --git a/+eui/ConditionPanel.m b/+eui/ConditionPanel.m deleted file mode 100644 index eacd317f..00000000 --- a/+eui/ConditionPanel.m +++ /dev/null @@ -1,254 +0,0 @@ -classdef ConditionPanel < handle - %UNTITLED Summary of this class goes here - % Detailed explanation goes here - % TODO Document - % TODO Add sort by column - % TODO Add set condition idx - % TODO Use tags for menu items - - properties - ConditionTable - MinWidth = 80 -% MaxWidth = 140 -% Margin = 4 - UIPanel - ButtonPanel - ContextMenus - end - - properties %(Access = protected) - ParamEditor - Listener - NewConditionButton - DeleteConditionButton - MakeGlobalButton - SetValuesButton - SelectedCells %[row, column;...] of each selected cell - end - - methods - function obj = ConditionPanel(f, ParamEditor, varargin) - obj.ParamEditor = ParamEditor; - obj.UIPanel = uix.VBox('Parent', f, 'BackgroundColor', 'white'); - % Create a child menu for the uiContextMenus - c = uicontextmenu; - obj.UIPanel.UIContextMenu = c; - obj.ContextMenus = uimenu(c, 'Label', 'Make Global', 'MenuSelectedFcn', @(~,~)obj.makeGlobal); - fcn = @(s,~)obj.ParamEditor.setRandomized(~strcmp(s.Checked, 'on')); - obj.ContextMenus(2) = uimenu(c, 'Label', 'Randomize conditions', ... - 'MenuSelectedFcn', fcn, 'Checked', 'on', 'Tag', 'randomize button'); - obj.ContextMenus(3) = uimenu(c, 'Label', 'Sort by selected column', ... - 'MenuSelectedFcn', @(~,~)disp('feature not yet implemented'), 'Tag', 'sort by'); - % Create condition table - p = uix.Panel('Parent', obj.UIPanel); - obj.ConditionTable = uitable('Parent', p,... - 'FontName', 'Consolas',... - 'RowName', [],... - 'RearrangeableColumns', true,... - 'Units', 'normalized',... - 'Position',[0 0 1 1],... - 'UIContextMenu', c,... - 'CellEditCallback', @obj.onEdit,... - 'CellSelectionCallback', @obj.onSelect); - % Create button panel to hold condition control buttons - obj.ButtonPanel = uix.HBox('Parent', obj.UIPanel, ... - 'BackgroundColor', 'white'); - % Create callback so that width of button panel is slave to width of - % conditional UIPanel -% b = obj.ButtonPanel; -% fcn = @(s)set(obj.ButtonPanel, 'Position', ... -% [s.Position(1) b.Position(2) s.Position(3) b.Position(4)]); -% obj.Listener = event.listener(obj.UIPanel, 'SizeChanged', @(s,~)fcn(s)); - % Define some common properties - props.BackgroundColor = 'white'; - props.Style = 'pushbutton'; - props.Units = 'normalized'; - props.Parent = obj.ButtonPanel; - % Create out four buttons - obj.NewConditionButton = uicontrol(props,... - 'String', 'New condition',... - ...'Position',[0 0 1/4 1],... - 'TooltipString', 'Add a new condition',... - 'Callback', @(~, ~) obj.newCondition()); - obj.DeleteConditionButton = uicontrol(props,... - 'String', 'Delete condition',... - ...'Position',[1/4 0 1/4 1],... - 'TooltipString', 'Delete the selected condition',... - 'Enable', 'off',... - 'Callback', @(~, ~) obj.deleteSelectedConditions()); - obj.MakeGlobalButton = uicontrol(props,... - 'String', 'Globalise parameter',... - ...'Position',[2/4 0 1/4 1],... - 'TooltipString', sprintf(['Make the selected condition-specific parameter global (i.e. not vary by trial)\n'... - 'This will move it to the global parameters section']),... - 'Enable', 'off',... - 'Callback', @(~, ~) obj.makeGlobal()); - obj.SetValuesButton = uicontrol(props,... - 'String', 'Set values',... - ...'Position',[3/4 0 1/4 1],... - 'TooltipString', 'Set selected values to specified value, range or function',... - 'Enable', 'off',... - 'Callback', @(~, ~) obj.setSelectedValues()); - obj.ButtonPanel.Widths = [-1 -1 -1 -1]; - obj.UIPanel.Heights = [-1 25]; - end - - function onEdit(obj, src, eventData) - disp('updating table cell'); - row = eventData.Indices(1); - col = eventData.Indices(2); - paramName = obj.ConditionTable.ColumnName{col}; - newValue = obj.ParamEditor.update(paramName, eventData.NewData, row); - reformed = obj.ParamEditor.paramValue2Control(newValue); - % If successful update the cell with default formatting - data = get(src, 'Data'); - if iscell(reformed) - % The reformed data type is a cell, this should be a one element - % wrapping cell - if numel(reformed) == 1 - reformed = reformed{1}; - else - error('Cannot handle data reformatted data type'); - end - end - data{row,col} = reformed; - set(src, 'Data', data); - end - - function clear(obj) - set(obj.ConditionTable, 'ColumnName', [], ... - 'Data', [], 'ColumnEditable', false); - end - - function delete(obj) - disp('delete called'); - delete(obj.UIPanel); - end - - function onSelect(obj, ~, eventData) - obj.SelectedCells = eventData.Indices; - if size(eventData.Indices, 1) > 0 - % cells selected, enable buttons - set(obj.MakeGlobalButton, 'Enable', 'on'); - set(obj.DeleteConditionButton, 'Enable', 'on'); - set(obj.SetValuesButton, 'Enable', 'on'); - set(obj.ContextMenus(1), 'Enable', 'on'); - set(obj.ContextMenus(3), 'Enable', 'on'); - else - % nothing selected, disable buttons - set(obj.MakeGlobalButton, 'Enable', 'off'); - set(obj.DeleteConditionButton, 'Enable', 'off'); - set(obj.SetValuesButton, 'Enable', 'off'); - set(obj.ContextMenus(1), 'Enable', 'off'); - set(obj.ContextMenus(3), 'Enable', 'off'); - end - end - - function makeGlobal(obj) - if isempty(obj.SelectedCells) - disp('nothing selected') - return - end - [cols, iu] = unique(obj.SelectedCells(:,2)); - names = obj.ConditionTable.ColumnName(cols); - rows = num2cell(obj.SelectedCells(iu,1)); %get rows of unique selected cols - PE = obj.ParamEditor; - cellfun(@PE.globaliseParamAtCell, names, rows); - end - - function deleteSelectedConditions(obj) - %DELETESELECTEDCONDITIONS Removes the selected conditions from table - % The callback for the 'Delete condition' button. This removes the - % selected conditions from the table and if less than two conditions - % remain, globalizes them. - % TODO: comment function better, index in a clearer fashion - % - % See also EXP.PARAMETERS, GLOBALISESELECTEDPARAMETERS - rows = unique(obj.SelectedCells(:,1)); - names = obj.ConditionTable.ColumnName; - numConditions = size(obj.ConditionTable.Data,2); - % If the number of remaining conditions is 1 or less... - if numConditions-length(rows) <= 1 - remainingIdx = find(all(1:numConditions~=rows,1)); - if isempty(remainingIdx); remainingIdx = 1; end - % change selected cells to be all fields (except numRepeats which - % is assumed to always be the last column) - obj.SelectedCells =[ones(length(names),1)*remainingIdx, (1:length(names))']; - %... globalize them - obj.makeGlobal; - else % Otherwise delete the selected conditions as usual - obj.ParamEditor.Parameters.removeConditions(rows); %FIXME: Should be in ParamEditor - end - % Refresh the table of conditions FIXME: Should be in ParamEditor - obj.ParamEditor.fillConditionTable(); - end - - function setSelectedValues(obj) % Set multiple fields in conditional table - disp('updating table cells'); - cols = obj.SelectedCells(:,2); % selected columns - uCol = unique(obj.SelectedCells(:,2)); - rows = obj.SelectedCells(:,1); % selected rows - % get current values of selected cells - currVals = arrayfun(@(u)obj.ConditionTable.Data(rows(cols==u),u), uCol, 'UniformOutput', 0); - names = obj.ConditionTable.ColumnName(uCol); % selected column names - promt = cellfun(@(a,b) [a ' (' num2str(sum(cols==b)) ')'],... - names, num2cell(uCol), 'UniformOutput', 0); % names of columns & num selected rows - defaultans = cellfun(@(c) c(1), currVals); - answer = inputdlg(promt,'Set values', 1, cellflat(defaultans)); % prompt for input - if isempty(answer) % if user presses cancel - return - end - % set values for each column - cellfun(@(a,b,c) setNewVals(a,b,c), answer, currVals, names, 'UniformOutput', 0); - function newVals = setNewVals(userIn, currVals, paramName) - % check array orientation - currVals = iff(size(currVals,1)>size(currVals,2),currVals',currVals); - if strStartsWith(userIn,'@') % anon function - func_h = str2func(userIn); - % apply function to each cell - currVals = cellfun(@str2double,currVals, 'UniformOutput', 0); % convert from char - newVals = cellfun(func_h, currVals, 'UniformOutput', 0); - elseif any(userIn==':') % array syntax - arr = eval(userIn); - newVals = num2cell(arr); % convert to cell array - elseif any(userIn==','|userIn==';') % 2D arrays - C = strsplit(userIn, ';'); - newVals = cellfun(@(c)textscan(c, '%f',... - 'ReturnOnError', false,... - 'delimiter', {' ', ','}, 'MultipleDelimsAsOne', 1),... - C); - else % single value to copy across all cells - userIn = str2double(userIn); - newVals = num2cell(ones(size(currVals))*userIn); - end - - if length(newVals)>length(currVals) % too many new values - newVals = newVals(1:length(currVals)); % truncate new array - elseif length(newVals)<length(currVals) % too few new values - % populate as many cells as possible - newVals = [newVals ... - cellfun(@(a)ui.ParamEditor.controlValue2Param(2,a),... - currVals(length(newVals)+1:end),'UniformOutput',0)]; - end - ic = strcmp(obj.ConditionTable.ColumnName, paramName); % find edited param names - % update param struct - obj.ParamEditor.Parameters.Struct.(paramName)(:,rows(cols==find(ic))) = cell2mat(newVals); - % update condtion table with strings - obj.ConditionTable.Data(rows(cols==find(ic)),ic)... - = cellfun(@(a)ui.ParamEditor.paramValue2Control(a), newVals', 'UniformOutput', 0); - end - notify(obj.ParamEditor, 'Changed'); - end - - function newCondition(obj) - disp('adding new condition row'); - PE = obj.ParamEditor; - cellfun(@PE.addEmptyConditionToParam, ... - PE.Parameters.TrialSpecificNames); - obj.ParamEditor.fillConditionTable(); - end - - - end - -end \ No newline at end of file diff --git a/+eui/FieldPanel.m b/+eui/FieldPanel.m deleted file mode 100644 index 10980cb6..00000000 --- a/+eui/FieldPanel.m +++ /dev/null @@ -1,165 +0,0 @@ -classdef FieldPanel < handle - %UNTITLED Summary of this class goes here - % Detailed explanation goes here - - properties - MinCtrlWidth = 40 - MaxCtrlWidth = 140 - Margin = 4 - RowSpacing = 1 - ColSpacing = 3 - UIPanel - ContextMenu - end - - properties %(Access = protected) - ParamEditor - MinRowHeight - Listener - Labels - Controls - LabelWidths - end - - events - Changed - end - - methods - function obj = FieldPanel(f, ParamEditor, varargin) - obj.ParamEditor = ParamEditor; - p = uix.Panel('Parent', f); - obj.UIPanel = uipanel('Parent', p, 'BorderType', 'none',... - 'BackgroundColor', 'white', 'Position', [0 0 0.5 1]); - obj.Listener = event.listener(obj.UIPanel, 'SizeChanged', @obj.onResize); - end - - function [label, ctrl] = addField(obj, name, ctrl) - if isempty(obj.ContextMenu) - obj.ContextMenu = uicontextmenu; - uimenu(obj.ContextMenu, 'Label', 'Make Coditional', ... - 'MenuSelectedFcn', @(~,~)obj.makeConditional); - end - props.BackgroundColor = 'white'; - props.HorizontalAlignment = 'left'; - props.UIContextMenu = obj.ContextMenu; - props.Parent = obj.UIPanel; - label = uicontrol('Style', 'text', 'String', name, props); - if nargin < 3 - ctrl = uicontrol('Style', 'edit', props); - end - callback = @(src,~)onEdit(obj, src, name); - set(ctrl, 'Callback', callback); - obj.Labels = [obj.Labels; label]; - obj.Controls = [obj.Controls; ctrl]; - end - - function onEdit(obj, src, id) - disp(id); - switch get(src, 'style') - case 'checkbox' - newValue = logical(get(src, 'value')); - obj.ParamEditor.update(id, newValue); - case 'edit' - % if successful update the control with default formatting and - % modified colour - newValue = obj.ParamEditor.update(id, get(src, 'string')); - set(src, 'String', obj.ParamEditor.paramValue2Control(newValue)); - end - changed = strcmp(id,{obj.Labels.String}); - obj.Labels(changed).ForegroundColor = [1 0 0]; - end - - function clear(obj, idx) % FIXME Rename to clearFields - if nargin == 1 - idx = true(size(obj.Labels)); - end - delete(obj.Labels(idx)) - delete(obj.Controls(idx)) - obj.Labels(idx) = []; - obj.LabelWidths(idx) = []; - obj.Controls(idx) = []; - end - - function makeConditional(obj, name) - if nargin == 1 - selected = obj.UIPanel.Parent.CurrentObject; %FIXME Doesn't work is parent is not figure - if isa(selected, 'matlab.ui.control.UIControl') && ... - strcmp(selected.Style, 'text') - name = selected.String; - else % Assume control - name = obj.Labels([obj.Controls]==selected).String; - end - end - idx = strcmp(name,{obj.Labels.String}); - assert(~ismember(name, {'randomiseConditions'}), ... - '%s can not be made a conditional parameter', name) - - obj.clear(idx); - obj.ParamEditor.Parameters.makeTrialSpecific(name); - obj.ParamEditor.fillConditionTable(); - obj.onResize; - end - - function delete(obj) - disp('delete called'); - delete(obj.UIPanel); - end - - function onResize(obj, ~, ~) - if isempty(obj.Controls) - return - end - if isempty(obj.LabelWidths) || numel(obj.LabelWidths) ~= numel(obj.Labels) - ext = reshape([obj.Labels.Extent], 4, [])'; - obj.LabelWidths = ext(:,3); - l = uicontrol('Parent', obj.UIPanel, 'Style', 'edit', 'String', 'something'); - obj.MinRowHeight = l.Extent(4); - delete(l); - end - -% %%% resize condition table -% w = numel(obj.ConditionTable.ColumnName); -% % nCols = max(cols); -% % globalWidth = (fullColWidth * nCols) + borderwidth; -% if w > 5; w = 0.5; else; w = 0.1 * w; end -% obj.UI(2).Position = [1-w 0 w 1]; -% obj.UI(1).Position = [0 0 1-w 1]; - - %%% general coordinates - pos = getpixelposition(obj.UIPanel); - borderwidth = obj.Margin; - bounds = [pos(3) pos(4)] - 2*borderwidth; - n = numel(obj.Labels); - vspace = obj.RowSpacing; - hspace = obj.ColSpacing; - rowHeight = obj.MinRowHeight + 2*vspace; - rowsPerCol = floor(bounds(2)/rowHeight); - cols = ceil((1:n)/rowsPerCol)'; - ncols = cols(end); - rows = mod(0:n - 1, rowsPerCol)' + 1; - labelColWidth = max(obj.LabelWidths) + 2*hspace; - ctrlWidthAvail = bounds(1)/ncols - labelColWidth; - ctrlColWidth = max(obj.MinCtrlWidth, min(ctrlWidthAvail, obj.MaxCtrlWidth)); - fullColWidth = labelColWidth + ctrlColWidth; - - %%% coordinates of labels - by = bounds(2) - rows*rowHeight + vspace + 1 + borderwidth; - labelPos = [vspace + (cols - 1)*fullColWidth + 1 + borderwidth... - by... - obj.LabelWidths... - repmat(rowHeight - 2*vspace, n, 1)]; - - %%% coordinates of edits - editPos = [labelColWidth + hspace + (cols - 1)*fullColWidth + 1 + borderwidth ... - by... - repmat(ctrlColWidth - 2*hspace, n, 1)... - repmat(rowHeight - 2*vspace, n, 1)]; - set(obj.Labels, {'Position'}, num2cell(labelPos, 2)); - set(obj.Controls, {'Position'}, num2cell(editPos, 2)); - - end - end - -end - diff --git a/+eui/MControl.m b/+eui/MControl.m index 69c8628d..5b25075d 100644 --- a/+eui/MControl.m +++ b/+eui/MControl.m @@ -247,10 +247,10 @@ function saveParamProfile(obj) % Called by 'Save...' button press, save a new pa end function loadParamProfile(obj, profile) - set(obj.ParamProfileLabel, 'String', 'loading...', 'ForegroundColor', [1 0 0]); % Red 'Loading...' while new set loads if ~isempty(obj.ParamEditor) - % Clear existing parameters control - clear(obj.ParamEditor) + %delete existing parameters control + delete(obj.ParamEditor); + set(obj.ParamProfileLabel, 'String', 'loading...', 'ForegroundColor', [1 0 0]); % Red 'Loading...' while new set loads end factory = obj.NewExpFactory; % Find which 'world' we are in @@ -305,18 +305,12 @@ function loadParamProfile(obj, profile) paramStruct = rmfield(paramStruct, 'services'); end obj.Parameters.Struct = paramStruct; - if isempty(paramStruct); return; end - % Now parameters are loaded, pass to ParamEditor for display, etc. - if isempty(obj.ParamEditor) - panel = uipanel('Parent', obj.ParamPanel, 'Position', [0 0 1 1]); -% panel = uiextras.Panel('Parent', obj.ParamPanel); - obj.ParamEditor = eui.ParamEditor(obj.Parameters, panel); % Build parameter list in Global panel by calling eui.ParamEditor - else - obj.ParamEditor.buildUI(obj.Parameters); - end - obj.ParamEditor.addlistener('Changed', @(src,~) obj.paramChanged); - if strcmp(obj.RemoteRigs.Selected.Status, 'idle') - set(obj.BeginExpButton, 'Enable', 'on') % Re-enable start button + if ~isempty(paramStruct) % Now parameters are loaded, pass to ParamEditor for display, etc. + obj.ParamEditor = eui.ParamEditor(obj.Parameters, obj.ParamPanel); % Build parameter list in Global panel by calling eui.ParamEditor + obj.ParamEditor.addlistener('Changed', @(src,~) obj.paramChanged); + if strcmp(obj.RemoteRigs.Selected.Status, 'idle') + set(obj.BeginExpButton, 'Enable', 'on') % Re-enable start button + end end end diff --git a/+eui/ParamEditor.m b/+eui/ParamEditor.m index 4c375b7e..75aca882 100644 --- a/+eui/ParamEditor.m +++ b/+eui/ParamEditor.m @@ -1,130 +1,155 @@ classdef ParamEditor < handle - %UNTITLED2 Summary of this class goes here - % Detailed explanation goes here + %EUI.PARAMEDITOR UI control for configuring experiment parameters + % TODO. See also EXP.PARAMETERS. + % + % Part of Rigbox + + % 2012-11 CB created + % 2017-03 MW/NS Made global panel scrollable & improved performance of + % buildGlobalUI. + % 2017-03 MW Added set values button properties + GlobalVSpacing = 20 Parameters end - properties %(Access = private) - UIPanel - GlobalUI - ConditionalUI - Parent - Listener - end - properties (Dependent) Enable end + properties (Access = private) + Root + GlobalGrid + ConditionTable + TableColumnParamNames = {} + NewConditionButton + DeleteConditionButton + MakeGlobalButton + SetValuesButton + SelectedCells %[row, column;...] of each selected cell + GlobalControls + end + events Changed end methods - function obj = ParamEditor(pars, f) - if nargin == 0; pars = []; end - if nargin < 2 - f = figure('Name', 'Parameters', 'NumberTitle', 'off',... - 'Toolbar', 'none', 'Menubar', 'none', 'DeleteFcn', @(~,~)obj.delete); - obj.Listener = event.listener(f, 'SizeChanged', @(~,~)obj.onResize); + function obj = ParamEditor(params, parent) + if nargin < 2 % Can call this function to display parameters is new window + parent = figure('Name', 'Parameters', 'NumberTitle', 'off',... + 'Toolbar', 'none', 'Menubar', 'none'); end - obj.Parent = f; - obj.UIPanel = uix.HBox('Parent', f); - obj.GlobalUI = eui.FieldPanel(obj.UIPanel, obj); - obj.ConditionalUI = eui.ConditionPanel(obj.UIPanel, obj); - obj.buildUI(pars); + obj.Parameters = params; + obj.build(parent); end function delete(obj) - delete(obj.GlobalUI); - delete(obj.ConditionalUI); - end - - function set.Enable(obj, value) - cUI = obj.ConditionalUI; - fig = obj.Parent; - if value == true - arrayfun(@(prop) set(prop, 'Enable', 'on'), findobj(fig,'Enable','off')); - if isempty(cUI.SelectedCells) - set(cUI.MakeGlobalButton, 'Enable', 'off'); - set(cUI.DeleteConditionButton, 'Enable', 'off'); - set(cUI.SetValuesButton, 'Enable', 'off'); - end - obj.Enable = true; - else - arrayfun(@(prop) set(prop, 'Enable', 'off'), findobj(fig,'Enable','on')); - obj.Enable = false; + disp('ParamEditor destructor called'); + if obj.Root.isvalid + obj.Root.delete(); end end - function clear(obj) - clear(obj.GlobalUI); - clear(obj.ConditionalUI); + function value = get.Enable(obj) + value = obj.Root.Enable; end - function buildUI(obj, pars) - obj.Parameters = pars; - obj.clear() - c = obj.GlobalUI; - names = pars.GlobalNames; - for nm = names' - if strcmp(nm, 'randomiseConditions'); continue; end - if islogical(pars.Struct.(nm{:})) % If parameter is logical, make checkbox - ctrl = uicontrol('Parent', c.UIPanel, 'Style', 'checkbox', ... - 'Value', pars.Struct.(nm{:}), 'BackgroundColor', 'white'); - addField(c, nm{:}, ctrl); - else - [~, ctrl] = addField(c, nm{:}); - ctrl.String = obj.paramValue2Control(pars.Struct.(nm{:})); - end - end + function set.Enable(obj, value) + obj.Root.Enable = value; + end + end + + methods %(Access = protected) + function build(obj, parent) % Build parameters panel + obj.Root = uiextras.HBox('Parent', parent, 'Padding', 5, 'Spacing', 5); % Add horizontal container for Global and Conditional panels +% globalPanel = uiextras.Panel('Parent', obj.Root,... % Make 'Global' parameters panel +% 'Title', 'Global', 'Padding', 5); + globPanel = uiextras.Panel('Parent', obj.Root,... % Make 'Global' parameters panel + 'Title', 'Global', 'Padding', 5); + globalPanel = uix.ScrollingPanel('Parent', globPanel,... % Make 'Global' scroll panel + 'Padding', 5); + + obj.GlobalGrid = uiextras.Grid('Parent', globalPanel, 'Padding', 4); % Make grid for parameter fields + obj.buildGlobalUI; % Populate Global panel + globalPanel.Heights = sum(obj.GlobalGrid.RowSizes)+45; + + conditionPanel = uiextras.Panel('Parent', obj.Root,... + 'Title', 'Conditional', 'Padding', 5); % Make 'Conditional' parameters panel + conditionVBox = uiextras.VBox('Parent', conditionPanel); + obj.ConditionTable = uitable('Parent', conditionVBox,... + 'FontName', 'Consolas',... + 'RowName', [],... + 'CellEditCallback', @obj.cellEditCallback,... + 'CellSelectionCallback', @obj.cellSelectionCallback); obj.fillConditionTable(); - %%% Special parameters - if ismember('randomiseConditions', obj.Parameters.Names) && ~pars.Struct.randomiseConditions - obj.ConditionalUI.ConditionTable.RowName = 'numbered'; - set(obj.ConditionalUI.ContextMenus(2), 'Checked', 'off'); - end - obj.GlobalUI.onResize(); + conditionButtonBox = uiextras.HBox('Parent', conditionVBox); + conditionVBox.Sizes = [-1 25]; + obj.NewConditionButton = uicontrol('Parent', conditionButtonBox,... + 'Style', 'pushbutton',... + 'String', 'New condition',... + 'TooltipString', 'Add a new condition',... + 'Callback', @(~, ~) obj.newCondition()); + obj.DeleteConditionButton = uicontrol('Parent', conditionButtonBox,... + 'Style', 'pushbutton',... + 'String', 'Delete condition',... + 'TooltipString', 'Delete the selected condition',... + 'Enable', 'off',... + 'Callback', @(~, ~) obj.deleteSelectedConditions()); + obj.MakeGlobalButton = uicontrol('Parent', conditionButtonBox,... + 'Style', 'pushbutton',... + 'String', 'Globalise parameter',... + 'TooltipString', sprintf(['Make the selected condition-specific parameter global (i.e. not vary by trial)\n'... + 'This will move it to the global parameters section']),... + 'Enable', 'off',... + 'Callback', @(~, ~) obj.globaliseSelectedParameters()); + obj.SetValuesButton = uicontrol('Parent', conditionButtonBox,... + 'Style', 'pushbutton',... + 'String', 'Set values',... + 'TooltipString', 'Set selected values to specified value, range or function',... + 'Enable', 'off',... + 'Callback', @(~, ~) obj.setSelectedValues()); + + obj.Root.Sizes = [sum(obj.GlobalGrid.ColumnSizes) + 32, -1]; end - function setRandomized(obj, value) - % If randomiseConditions doesn't exist and new value is false, add - % the parameter and set it to false - if ~ismember('randomiseConditions', obj.Parameters.Names) && value == false - description = 'Whether to randomise the conditional paramters or present them in order'; - obj.Parameters.set('randomiseConditions', false, description, 'logical') - elseif ismember('randomiseConditions', obj.Parameters.Names) - obj.update('randomiseConditions', logical(value)); - end - menu = obj.ConditionalUI.ContextMenus(2); - if value == false - obj.ConditionalUI.ConditionTable.RowName = 'numbered'; - menu.Checked = 'off'; - else - obj.ConditionalUI.ConditionTable.RowName = []; - menu.Checked = 'on'; + function buildGlobalUI(obj) % Function to essemble global parameters + globalParamNames = fieldnames(obj.Parameters.assortForExperiment); % assortForExperiment divides params into global and trial-specific parameter structures + obj.GlobalControls = gobjects(length(globalParamNames),3); % Initialize object array (faster than assigning to end of array which results in two calls to constructor) + for i=1:length(globalParamNames) % using for loop (sorry Chris!) to populate object array 2017-02-14 MW + [obj.GlobalControls(i,1), obj.GlobalControls(i,2), obj.GlobalControls(i,3)]... % [editors, labels, buttons] + = obj.addParamUI(globalParamNames{i}); end + % Above code replaces the following as after 2014a, MATLAB doesn't no + % longer uses numrical handles but instead uses object arrays +% [editors, labels, buttons] = cellfun(... +% @(n) obj.addParamUI(n), fieldnames(globalParams), 'UniformOutput', false); +% editors = cell2mat(editors); +% labels = cell2mat(labels); +% buttons = cell2mat(buttons); +% obj.GlobalControls = [labels, editors, buttons]; +% obj.GlobalGrid.Children = obj.GlobalControls(:); + +% obj.GlobalGrid.Children = +% blah = cat(1,obj.GlobalControls(:,1),obj.GlobalControls(:,2),obj.GlobalControls(:,3)); +% Doesn't work for some reason - MW 2017-02-15 + + child_handles = allchild(obj.GlobalGrid); % Get child handles for GlobalGrid + child_handles = [child_handles(end-1:-3:1); child_handles(end:-3:1); child_handles(end-2:-3:1)]; % Reorder them so all labels come first, then ctrls, then buttons +% child_handles = [child_handles(2:3:end); child_handles(3:3:end); child_handles(1:3:end)]; % Reorder them so all labels come first, then ctrls, then buttons + obj.GlobalGrid.Contents = child_handles; % Set children to new order + % uistack + + obj.GlobalGrid.ColumnSizes = [180, 200, 40]; % Set column sizes + obj.GlobalGrid.Spacing = 1; + obj.GlobalGrid.RowSizes = repmat(obj.GlobalVSpacing, 1, size(obj.GlobalControls, 1)); end - function fillConditionTable(obj) - % Build the condition table - titles = obj.Parameters.TrialSpecificNames; - [~, trialParams] = obj.Parameters.assortForExperiment; - if isempty(titles) - obj.ConditionalUI.ButtonPanel.Visible = 'off'; - obj.ConditionalUI.UIPanel.Visible = 'off'; - obj.GlobalUI.UIPanel.Position(3) = 1; - else - obj.ConditionalUI.ButtonPanel.Visible = 'on'; - obj.ConditionalUI.UIPanel.Visible = 'on'; - data = reshape(struct2cell(trialParams), numel(titles), [])'; - data = mapToCell(@(e) obj.paramValue2Control(e), data); - set(obj.ConditionalUI.ConditionTable, 'ColumnName', titles, 'Data', data,... - 'ColumnEditable', true(1, numel(titles))); - end - end +% function swapConditions(obj, idx1, idx2) % Function started, never +% finished - MW 2017-02-15 +% % params = obj.Parameters.trial +% end function addEmptyConditionToParam(obj, name) assert(obj.Parameters.isTrialSpecific(name),... @@ -158,134 +183,217 @@ function addEmptyConditionToParam(obj, name) obj.Parameters.Struct.(name) = cat(2, obj.Parameters.Struct.(name), newValue); end - function newValue = update(obj, name, value, row) - % FIXME change name to updateGlobal - if nargin < 4; row = 1; end - currValue = obj.Parameters.Struct.(name)(:,row); - if iscell(currValue) - % cell holders are allowed to be different types of value - newValue = obj.controlValue2Param(currValue{1}, value, true); - obj.Parameters.Struct.(name){:,row} = newValue; + function cellSelectionCallback(obj, src, eventData) + obj.SelectedCells = eventData.Indices; + if size(eventData.Indices, 1) > 0 + %cells selected, enable buttons + set(obj.MakeGlobalButton, 'Enable', 'on'); + set(obj.DeleteConditionButton, 'Enable', 'on'); + set(obj.SetValuesButton, 'Enable', 'on'); else - newValue = obj.controlValue2Param(currValue, value); - obj.Parameters.Struct.(name)(:,row) = newValue; + %nothing selected, disable buttons + set(obj.MakeGlobalButton, 'Enable', 'off'); + set(obj.DeleteConditionButton, 'Enable', 'off'); + set(obj.SetValuesButton, 'Enable', 'off'); end - notify(obj, 'Changed'); end - function globaliseParamAtCell(obj, name, row) - % Make parameter 'name' a global parameter and set it's value to be - % that of the specified row. + function newCondition(obj) + disp('adding new condition row'); + cellfun(@obj.addEmptyConditionToParam, obj.Parameters.TrialSpecificNames); + obj.fillConditionTable(); + end + + function deleteSelectedConditions(obj) + %DELETESELECTEDCONDITIONS Removes the selected conditions from table + % The callback for the 'Delete condition' button. This removes the + % selected conditions from the table and if less than two conditions + % remain, globalizes them. + % TODO: comment function better, index in a clearer fashion % - % See also EXP.PARAMETERS/MAKEGLOBAL, UI.CONDITIONPANEL/MAKEGLOBAL + % See also EXP.PARAMETERS, GLOBALISESELECTEDPARAMETERS + rows = unique(obj.SelectedCells(:,1)); + % If the number of remaining conditions is 1 or less... + names = obj.Parameters.TrialSpecificNames; + numConditions = size(obj.Parameters.Struct.(names{1}),2); + if numConditions-length(rows) <= 1 + remainingIdx = find(all(1:numConditions~=rows,1)); + if isempty(remainingIdx); remainingIdx = 1; end + % change selected cells to be all fields (except numRepeats which + % is assumed to always be the last column) + obj.SelectedCells =[ones(length(names)-1,1)*remainingIdx, (1:length(names)-1)']; + %... globalize them + obj.globaliseSelectedParameters; + obj.Parameters.removeConditions(rows) +% for i = 1:numel(names) +% newValue = iff(any(remainingIdx), obj.Struct.(names{i})(:,remainingIdx), obj.Struct.(names{i})(1)); +% % If the parameter is Num repeats, set the value +% if strcmp(names{i}, 'numRepeats') +% obj.Struct.(names{i}) = newValue; +% else +% obj.makeGlobal(names{i}, newValue); +% end +% end + else % Otherwise delete the selected conditions as usual + obj.Parameters.removeConditions(rows); + end + obj.fillConditionTable(); %refresh the table of conditions + end + + function globaliseSelectedParameters(obj) + [cols, iu] = unique(obj.SelectedCells(:,2)); + names = obj.TableColumnParamNames(cols); + rows = obj.SelectedCells(iu,1); %get rows of unique selected cols + arrayfun(@obj.globaliseParamAtCell, rows, cols); + obj.fillConditionTable(); %refresh the table of conditions + %now add global controls for parameters + newGlobals = gobjects(length(names),3); % Initialize object array (faster than assigning to end of array which results in two calls to constructor) + for i=length(names):-1:1 % using for loop (sorry Chris!) to initialize and populate object array 2017-02-15 MW + [newGlobals(i,1), newGlobals(i,2), newGlobals(i,3)]... % [editors, labels, buttons] + = obj.addParamUI(names{i}); + end + +% [editors, labels, buttons] = arrayfun(@obj.addParamUI, names); % +% 2017-02-15 MW can no longer use arrayfun with object outputs + idx = size(obj.GlobalControls, 1); % Calculate number of current Global params + new = numel(newGlobals); + obj.GlobalControls = [obj.GlobalControls; newGlobals]; % Add new globals to object + ggHandles = obj.GlobalGrid.Contents; + ggHandles = [ggHandles(1:idx); ggHandles((end-new+2):3:end);... + ggHandles(idx+1:idx*2); ggHandles((end-new+1):3:end);... + ggHandles(idx*2+1:idx*3); ggHandles((end-new+3):3:end)]; % Reorder them so all labels come first, then ctrls, then buttons + obj.GlobalGrid.Contents = ggHandles; % Set children to new order + + % Reset sizes + obj.GlobalGrid.RowSizes = repmat(obj.GlobalVSpacing, 1, size(obj.GlobalControls, 1)); + set(get(obj.GlobalGrid, 'Parent'),... + 'Heights', sum(obj.GlobalGrid.RowSizes)+45); % Reset height of globalPanel + obj.GlobalGrid.ColumnSizes = [180, 200, 40]; + obj.GlobalGrid.Spacing = 1; + end + + function globaliseParamAtCell(obj, row, col) + name = obj.TableColumnParamNames{col}; value = obj.Parameters.Struct.(name)(:,row); obj.Parameters.makeGlobal(name, value); - % Refresh the table of conditions - obj.fillConditionTable; - % Add new global parameter to field panel - if islogical(value) % If parameter is logical, make checkbox - ctrl = uicontrol('Parent', obj.GlobalUI.UIPanel, 'Style', 'checkbox', ... - 'Value', value, 'BackgroundColor', 'white'); - addField(obj.GlobalUI, name, ctrl); - else - [~, ctrl] = addField(obj.GlobalUI, name); - ctrl.String = obj.paramValue2Control(value); - end - obj.GlobalUI.onResize(); - obj.notify('Changed'); end - - function onResize(obj) - %%% resize condition table - notify(obj.ConditionalUI.ButtonPanel, 'SizeChanged'); - cUI = obj.ConditionalUI.UIPanel; - gUI = obj.GlobalUI.UIPanel; - - pos = obj.GlobalUI.Controls(end).Position; - colExtent = pos(1) + pos(3) + obj.GlobalUI.Margin; - colWidth = pos(3) + obj.GlobalUI.Margin + obj.GlobalUI.ColSpacing; % FIXME: inaccurate - pos = getpixelposition(gUI); - gUIExtent = pos(3); - pos = getpixelposition(cUI); - cUIExtent = pos(3); - - extent = get(obj.ConditionalUI.ConditionTable, 'Extent'); - panelWidth = cUI.Position(3); - if colExtent > gUIExtent && cUIExtent > obj.ConditionalUI.MinWidth - % If global UI controls are cut off and there is no dead space in - % the table but the minimum table width hasn't been reached, reduce - % the conditional UI width: table has scroll bar and global panel - % does not - % FIXME calculate how much space required for min control width -% obj.GlobalUI.MinCtrlWidth - % Calculate conditional UI width in normalized units - requiredWidth = (cUI.Position(3) / cUIExtent) * (colExtent - gUIExtent); - minConditionalWidth = (cUI.Position(3) / cUIExtent) * obj.ConditionalUI.MinWidth; - if requiredWidth < minConditionalWidth - % If the required width is smaller that the minimum table width, - % use minimum table width - cUI.Position(3) = minConditionalWidth; - else % Otherwise use this width - cUI.Position(3) = requiredWidth; + + function setSelectedValues(obj) % Set multiple fields in conditional table + disp('updating table cells'); + cols = obj.SelectedCells(:,2); % selected columns + uCol = unique(obj.SelectedCells(:,2)); + rows = obj.SelectedCells(:,1); % selected rows + % get current values of selected cells + currVals = arrayfun(@(u)obj.ConditionTable.Data(rows(cols==u),u), uCol, 'UniformOutput', 0); + names = obj.TableColumnParamNames(uCol); % selected column names + promt = cellfun(@(a,b) [a ' (' num2str(sum(cols==b)) ')'],... + names, num2cell(uCol), 'UniformOutput', 0); % names of columns & num selected rows + defaultans = cellfun(@(c) c(1), currVals); + answer = inputdlg(promt,'Set values', 1, cellflat(defaultans)); % prompt for input + if isempty(answer) % if user presses cancel + return + end + % set values for each column + cellfun(@(a,b,c) setNewVals(a,b,c), answer, currVals, names, 'UniformOutput', 0); + function newVals = setNewVals(userIn, currVals, paramName) + % check array orientation + currVals = iff(size(currVals,1)>size(currVals,2),currVals',currVals); + if strStartsWith(userIn,'@') % anon function + func_h = str2func(userIn); + % apply function to each cell + currVals = cellfun(@str2double,currVals, 'UniformOutput', 0); % convert from char + newVals = cellfun(func_h, currVals, 'UniformOutput', 0); + elseif any(userIn==':') % array syntax + arr = eval(userIn); + newVals = num2cell(arr); % convert to cell array + elseif any(userIn==','|userIn==';') % 2D arrays + C = strsplit(userIn, ';'); + newVals = cellfun(@(c)textscan(c, '%f',... + 'ReturnOnError', false,... + 'delimiter', {' ', ','}, 'MultipleDelimsAsOne', 1),... + C); + else % single value to copy across all cells + userIn = str2double(userIn); + newVals = num2cell(ones(size(currVals))*userIn); + end + + if length(newVals)>length(currVals) % too many new values + newVals = newVals(1:length(currVals)); % truncate new array + elseif length(newVals)<length(currVals) % too few new values + % populate as many cells as possible + newVals = [newVals ... + cellfun(@(a)obj.controlValue2Param(2,a),... + currVals(length(newVals)+1:end),'UniformOutput',0)]; + end + ic = strcmp(obj.TableColumnParamNames,paramName); % find edited param names + % update param struct + obj.Parameters.Struct.(paramName)(:,rows(cols==find(ic))) = cell2mat(newVals); + % update condtion table with strings + obj.ConditionTable.Data(rows(cols==find(ic)),ic)... + = cellfun(@(a)obj.paramValue2Control(a), newVals', 'UniformOutput', 0); end - cUI.Position(1) = 1-cUI.Position(3); - gUI.Position(3) = 1-cUI.Position(3); - elseif extent(3) < 1 && colWidth < obj.GlobalUI.MaxCtrlWidth - % If there is dead table space and the global UI columns are cut - % off or squashed, reduce the conditional panel - cUI.Position(3) = cUI.Position(3) - (panelWidth - (panelWidth * extent(3))); - cUI.Position(1) = cUI.Position(1) + (panelWidth - (panelWidth * extent(3))); - gUI.Position(3) = cUI.Position(1); - elseif extent(3) < 1 && colExtent < gUIExtent - % Plenty of space! Increase conditional UI a bit - deadspace = gUIExtent - colExtent; % Spece between panels in pixels - % Convert global UI pixels to relative units - gUI.Position(3) = (gUI.Position(3) / gUIExtent) * (gUIExtent - (deadspace/2)); - cUI.Position(1) = gUI.Position(3); - cUI.Position(3) = 1-gUI.Position(3); - elseif extent(3) >= 1 && colExtent < gUIExtent - % If the table space is cut off and there is dead space in the - % global UI panel, reduce the global UI panel - % If the extra space is minimum, return - if floor(gUIExtent - colExtent) <= 2; return; end - deadspace = gUIExtent - colExtent; % Spece between panels in pixels - gUI.Position(3) = (gUI.Position(3) / gUIExtent) * (gUIExtent - deadspace); - cUI.Position(3) = 1-gUI.Position(3); - cUI.Position(1) = gUI.Position(3); + notify(obj, 'Changed'); + end + + function cellEditCallback(obj, src, eventData) + disp('updating table cell'); + row = eventData.Indices(1); + col = eventData.Indices(2); + paramName = obj.TableColumnParamNames{col}; + currValue = obj.Parameters.Struct.(paramName)(:,row); + if iscell(currValue) + % cell holders are allowed to be different types of value + newParam = obj.controlValue2Param(currValue{1}, eventData.NewData, true); + obj.Parameters.Struct.(paramName){:,row} = newParam; else - % Compromise by having both panels take up half the figure -% [cUI.Position([1,3]), gUI.Position(3)] = deal(0.5); + newParam = obj.controlValue2Param(currValue, eventData.NewData); + obj.Parameters.Struct.(paramName)(:,row) = newParam; end - notify(obj.ConditionalUI.ButtonPanel, 'SizeChanged'); + % if successful update the cell with default formatting + data = get(src, 'Data'); + reformed = obj.paramValue2Control(newParam); + if iscell(reformed) + % the reformed data type is a cell, this should be a one element + % wrapping cell + if numel(reformed) == 1 + reformed = reformed{1}; + else + error('Cannot handle data reformatted data type'); + end + end + data{row,col} = reformed; + set(src, 'Data', data); + %notify listeners of change + notify(obj, 'Changed'); end - end - - methods (Static) - function data = paramValue2Control(data) - % convert from parameter value to control value, i.e. a value class - % that can be easily displayed and edited by the user. Everything - % except logicals are converted to charecter arrays. - switch class(data) - case 'function_handle' - % convert a function handle to it's string name - data = func2str(data); - case 'logical' - data = data ~= 0; % If logical do nothing, basically. - case 'string' - data = char(data); % Strings not allowed in condition table data - otherwise - if isnumeric(data) - % format numeric types as string number list - strlist = mapToCell(@num2str, data); - data = strJoin(strlist, ', '); - elseif iscellstr(data) - data = strJoin(data, ', '); - end + + function updateGlobal(obj, param, src) + currParamValue = obj.Parameters.Struct.(param); + switch get(src, 'style') + case 'checkbox' + newValue = logical(get(src, 'value')); + obj.Parameters.Struct.(param) = newValue; + case 'edit' + newValue = obj.controlValue2Param(currParamValue, get(src, 'string')); + obj.Parameters.Struct.(param) = newValue; + % if successful update the control with default formatting and + % modified colour + set(src, 'String', obj.paramValue2Control(newValue),... + 'ForegroundColor', [1 0 0]); %red indicating it has changed + %notify listeners of change + notify(obj, 'Changed'); end - % all other data types stay as they are + end + + function [data, paramNames, titles] = tableData(obj) + [~, trialParams] = obj.Parameters.assortForExperiment; + paramNames = fieldnames(trialParams); + titles = obj.Parameters.title(paramNames); + data = reshape(struct2cell(trialParams), numel(paramNames), [])'; + data = mapToCell(@(e) obj.paramValue2Control(e), data); end - function data = controlValue2Param(currParam, data, allowTypeChange) + function data = controlValue2Param(obj, currParam, data, allowTypeChange) % Convert the values displayed in the UI ('control values') to % parameter values. String representations of numrical arrays and % functions are converted back to their 'native' classes. @@ -324,7 +432,111 @@ function onResize(obj) end end end + + function data = paramValue2Control(obj, data) + % convert from parameter value to control value, i.e. a value class + % that can be easily displayed and edited by the user. Everything + % except logicals are converted to charecter arrays. + switch class(data) + case 'function_handle' + % convert a function handle to it's string name + data = func2str(data); + case 'logical' + data = data ~= 0; % If logical do nothing, basically. + case 'string' + data = char(data); % Strings not allowed in condition table data + otherwise + if isnumeric(data) + % format numeric types as string number list + strlist = mapToCell(@num2str, data); + data = strJoin(strlist, ', '); + elseif iscellstr(data) + data = strJoin(data, ', '); + end + end + % all other data types stay as they are + end + + function fillConditionTable(obj) + [data, params, titles] = obj.tableData; + set(obj.ConditionTable, 'ColumnName', titles, 'Data', data,... + 'ColumnEditable', true(1, numel(titles))); + obj.TableColumnParamNames = params; + end + + function makeTrialSpecific(obj, paramName, ctrls) + [uirow, ~] = find(obj.GlobalControls == ctrls{1}); + assert(numel(uirow) == 1, 'Unexpected number of matching global controls'); + cellfun(@(c) delete(c), ctrls); + obj.GlobalControls(uirow,:) = []; + obj.GlobalGrid.RowSizes(uirow) = []; + obj.Parameters.makeTrialSpecific(paramName); + obj.fillConditionTable(); + set(get(obj.GlobalGrid, 'Parent'),... + 'Heights', sum(obj.GlobalGrid.RowSizes)+45); % Reset height of globalPanel + end + function [ctrl, label, buttons] = addParamUI(obj, name) % Adds ui element for each parameter + parent = obj.GlobalGrid; % Made by build function above + ctrl = []; + label = []; + buttons = []; + if iscell(name) % 2017-02-14 MW function now called with arrayFun (instead of cellFun) + name = name{1,1}; + end + value = obj.paramValue2Control(obj.Parameters.Struct.(name)); % convert from parameter value to control value (everything but logical values become strings) + title = obj.Parameters.title(name); + description = obj.Parameters.description(name); + + if islogical(value) % If parameter is logical, make checkbox + for i = 1:length(value) + ctrl(end+1) = uicontrol('Parent', parent,... + 'Style', 'checkbox',... + 'TooltipString', description,... + 'Value', value(i),... % Added 2017-02-15 MW set checkbox to what ever the parameter value is + 'Callback', @(src, e) obj.updateGlobal(name, src)); + end + elseif ischar(value) + ctrl = uicontrol('Parent', parent,... + 'BackgroundColor', [1 1 1],... + 'Style', 'edit',... + 'String', value,... + 'TooltipString', description,... + 'UserData', name,... % save the name of the parameter in userdata + 'HorizontalAlignment', 'left',... + 'Callback', @(src, e) obj.updateGlobal(name, src)); +% elseif iscellstr(value) +% lines = mkStr(value, [], sprintf('\n'), []); +% ctrl = uicontrol('Parent', parent,... +% 'BackgroundColor', [1 1 1],... +% 'Style', 'edit',... +% 'Max', 2,... %make it multiline +% 'String', lines,... +% 'TooltipString', description,... +% 'HorizontalAlignment', 'left',... +% 'UserData', name,... % save the name of the parameter in userdata +% 'Callback', @(src, e) obj.updateGlobal(name, src)); + end + + if ~isempty(ctrl) % If control box is made, add label and conditional button + label = uicontrol('Parent', parent,... + 'Style', 'text', 'String', title, 'HorizontalAlignment', 'left',... + 'TooltipString', description); % Why not use bui.label? MW 2017-02-15 + bbox = uiextras.HBox('Parent', parent); % Make HBox for button + % UIContainer no longer present in GUILayoutToolbox, it used to + % call uipanel with the following args: + % 'Units', 'Normalized'; 'BorderType', 'none') +% buttons = bbox.UIContainer; + buttons = uicontrol('Parent', bbox, 'Style', 'pushbutton',... % Make 'conditional parameter' button + 'String', '[...]',... + 'TooltipString', sprintf(['Make this a condition parameter (i.e. vary by trial).\n'... + 'This will move it to the trial conditions table.']),... + 'FontSize', 7,... + 'Callback', @(~,~) obj.makeTrialSpecific(name, {ctrl, label, bbox})); + bbox.Sizes = 29; % Resize button height to 29px + end + end end + end diff --git a/+eui/ParamEditor_old.m b/+eui/ParamEditor_old.m deleted file mode 100644 index 05855aef..00000000 --- a/+eui/ParamEditor_old.m +++ /dev/null @@ -1,516 +0,0 @@ -classdef ParamEditor < handle - %EUI.PARAMEDITOR UI control for configuring experiment parameters - % TODO. See also EXP.PARAMETERS. - % - % Part of Rigbox - - % 2012-11 CB created - % 2017-03 MW/NS Made global panel scrollable & improved performance of - % buildGlobalUI. - % 2017-03 MW Added set values button - - properties - GlobalVSpacing = 20 - Parameters - end - - properties (Dependent) - Enable - end - - properties (Access = private) - Root - GlobalGrid - ConditionTable - TableColumnParamNames = {} - NewConditionButton - DeleteConditionButton - MakeGlobalButton - SetValuesButton - SelectedCells %[row, column;...] of each selected cell - GlobalControls - end - - events - Changed - end - - methods - function obj = ParamEditor(params, parent) - if nargin < 2 % Can call this function to display parameters is new window - parent = figure('Name', 'Parameters', 'NumberTitle', 'off',... - 'Toolbar', 'none', 'Menubar', 'none'); - end - obj.Parameters = params; - obj.build(parent); - end - - function delete(obj) - disp('ParamEditor destructor called'); - if obj.Root.isvalid - obj.Root.delete(); - end - end - - function value = get.Enable(obj) - value = obj.Root.Enable; - end - - function set.Enable(obj, value) - obj.Root.Enable = value; - end - end - - methods %(Access = protected) - function build(obj, parent) % Build parameters panel - obj.Root = uiextras.HBox('Parent', parent, 'Padding', 5, 'Spacing', 5); % Add horizontal container for Global and Conditional panels -% globalPanel = uiextras.Panel('Parent', obj.Root,... % Make 'Global' parameters panel -% 'Title', 'Global', 'Padding', 5); - globPanel = uiextras.Panel('Parent', obj.Root,... % Make 'Global' parameters panel - 'Title', 'Global', 'Padding', 5); - globalPanel = uix.ScrollingPanel('Parent', globPanel,... % Make 'Global' scroll panel - 'Padding', 5); - - obj.GlobalGrid = uiextras.Grid('Parent', globalPanel, 'Padding', 4); % Make grid for parameter fields - obj.buildGlobalUI; % Populate Global panel - globalPanel.Heights = sum(obj.GlobalGrid.RowSizes)+45; - - conditionPanel = uiextras.Panel('Parent', obj.Root,... - 'Title', 'Conditional', 'Padding', 5); % Make 'Conditional' parameters panel - conditionVBox = uiextras.VBox('Parent', conditionPanel); - obj.ConditionTable = uitable('Parent', conditionVBox,... - 'FontName', 'Consolas',... - 'RowName', [],... - 'CellEditCallback', @obj.cellEditCallback,... - 'CellSelectionCallback', @obj.cellSelectionCallback); - obj.fillConditionTable(); - conditionButtonBox = uiextras.HBox('Parent', conditionVBox); - conditionVBox.Sizes = [-1 25]; - obj.NewConditionButton = uicontrol('Parent', conditionButtonBox,... - 'Style', 'pushbutton',... - 'String', 'New condition',... - 'TooltipString', 'Add a new condition',... - 'Callback', @(~, ~) obj.newCondition()); - obj.DeleteConditionButton = uicontrol('Parent', conditionButtonBox,... - 'Style', 'pushbutton',... - 'String', 'Delete condition',... - 'TooltipString', 'Delete the selected condition',... - 'Enable', 'off',... - 'Callback', @(~, ~) obj.deleteSelectedConditions()); - obj.MakeGlobalButton = uicontrol('Parent', conditionButtonBox,... - 'Style', 'pushbutton',... - 'String', 'Globalise parameter',... - 'TooltipString', sprintf(['Make the selected condition-specific parameter global (i.e. not vary by trial)\n'... - 'This will move it to the global parameters section']),... - 'Enable', 'off',... - 'Callback', @(~, ~) obj.globaliseSelectedParameters()); - obj.SetValuesButton = uicontrol('Parent', conditionButtonBox,... - 'Style', 'pushbutton',... - 'String', 'Set values',... - 'TooltipString', 'Set selected values to specified value, range or function',... - 'Enable', 'off',... - 'Callback', @(~, ~) obj.setSelectedValues()); - - obj.Root.Sizes = [sum(obj.GlobalGrid.ColumnSizes) + 32, -1]; - end - - function buildGlobalUI(obj) % Function to essemble global parameters - globalParamNames = fieldnames(obj.Parameters.assortForExperiment); % assortForExperiment divides params into global and trial-specific parameter structures - obj.GlobalControls = gobjects(length(globalParamNames),3); % Initialize object array (faster than assigning to end of array which results in two calls to constructor) - for i=1:length(globalParamNames) % using for loop (sorry Chris!) to populate object array 2017-02-14 MW - [obj.GlobalControls(i,1), obj.GlobalControls(i,2), obj.GlobalControls(i,3)]... % [editors, labels, buttons] - = obj.addParamUI(globalParamNames{i}); - end - - child_handles = allchild(obj.GlobalGrid); % Get child handles for GlobalGrid - child_handles = [child_handles(end-1:-3:1); child_handles(end:-3:1); child_handles(end-2:-3:1)]; % Reorder them so all labels come first, then ctrls, then buttons - obj.GlobalGrid.Contents = child_handles; % Set children to new order - - obj.GlobalGrid.ColumnSizes = [180, 200, 40]; % Set column sizes - obj.GlobalGrid.Spacing = 1; - obj.GlobalGrid.RowSizes = repmat(obj.GlobalVSpacing, 1, size(obj.GlobalControls, 1)); - end - -% function swapConditions(obj, idx1, idx2) % Function started, never -% finished - MW 2017-02-15 -% % params = obj.Parameters.trial -% end - - function addEmptyConditionToParam(obj, name) - assert(obj.Parameters.isTrialSpecific(name),... - 'Tried to add a new condition to global parameter ''%s''', name); - % work out what the right 'empty' is for the parameter - currValue = obj.Parameters.Struct.(name); - if isnumeric(currValue) - newValue = zeros(size(currValue, 1), 1, class(currValue)); - elseif islogical(currValue) - newValue = false(size(currValue, 1), 1); - elseif iscell(currValue) - if numel(currValue) > 0 - if iscellstr(currValue) - % if all elements are strings, default to a blank string - newValue = {''}; - elseif isa(currValue{1}, 'function_handle') - % first element is a function handle, so create with a @nop - % handle - newValue = {@nop}; - else - % misc cell case - default to empty element - newValue = {[]}; - end - else - % misc case - default to empty element - newValue = {[]}; - end - else - error('Adding empty condition for ''%s'' type not implemented', class(currValue)); - end - obj.Parameters.Struct.(name) = cat(2, obj.Parameters.Struct.(name), newValue); - end - - function cellSelectionCallback(obj, src, eventData) - obj.SelectedCells = eventData.Indices; - if size(eventData.Indices, 1) > 0 - %cells selected, enable buttons - set(obj.MakeGlobalButton, 'Enable', 'on'); - set(obj.DeleteConditionButton, 'Enable', 'on'); - set(obj.SetValuesButton, 'Enable', 'on'); - else - %nothing selected, disable buttons - set(obj.MakeGlobalButton, 'Enable', 'off'); - set(obj.DeleteConditionButton, 'Enable', 'off'); - set(obj.SetValuesButton, 'Enable', 'off'); - end - end - - function newCondition(obj) - disp('adding new condition row'); - cellfun(@obj.addEmptyConditionToParam, obj.Parameters.TrialSpecificNames); - obj.fillConditionTable(); - end - - function deleteSelectedConditions(obj) - %DELETESELECTEDCONDITIONS Removes the selected conditions from table - % The callback for the 'Delete condition' button. This removes the - % selected conditions from the table and if less than two conditions - % remain, globalizes them. - % TODO: comment function better, index in a clearer fashion - % - % See also EXP.PARAMETERS, GLOBALISESELECTEDPARAMETERS - rows = unique(obj.SelectedCells(:,1)); - % If the number of remaining conditions is 1 or less... - names = obj.Parameters.TrialSpecificNames; - numConditions = size(obj.Parameters.Struct.(names{1}),2); - if numConditions-length(rows) <= 1 - remainingIdx = find(all(1:numConditions~=rows,1)); - if isempty(remainingIdx); remainingIdx = 1; end - % change selected cells to be all fields (except numRepeats which - % is assumed to always be the last column) - obj.SelectedCells =[ones(length(names)-1,1)*remainingIdx, (1:length(names)-1)']; - %... globalize them - obj.globaliseSelectedParameters; - obj.Parameters.removeConditions(rows) - else % Otherwise delete the selected conditions as usual - obj.Parameters.removeConditions(rows); - end - obj.fillConditionTable(); %refresh the table of conditions - end - - function globaliseSelectedParameters(obj) - [cols, iu] = unique(obj.SelectedCells(:,2)); - names = obj.TableColumnParamNames(cols); - rows = obj.SelectedCells(iu,1); %get rows of unique selected cols - arrayfun(@obj.globaliseParamAtCell, rows, cols); - obj.fillConditionTable(); %refresh the table of conditions - %now add global controls for parameters - newGlobals = gobjects(length(names),3); % Initialize object array (faster than assigning to end of array which results in two calls to constructor) - for i=length(names):-1:1 % using for loop (sorry Chris!) to initialize and populate object array 2017-02-15 MW - [newGlobals(i,1), newGlobals(i,2), newGlobals(i,3)]... % [editors, labels, buttons] - = obj.addParamUI(names{i}); - end - - idx = size(obj.GlobalControls, 1); % Calculate number of current Global params - new = numel(newGlobals); - obj.GlobalControls = [obj.GlobalControls; newGlobals]; % Add new globals to object - ggHandles = obj.GlobalGrid.Contents; - ggHandles = [ggHandles(1:idx); ggHandles((end-new+2):3:end);... - ggHandles(idx+1:idx*2); ggHandles((end-new+1):3:end);... - ggHandles(idx*2+1:idx*3); ggHandles((end-new+3):3:end)]; % Reorder them so all labels come first, then ctrls, then buttons - obj.GlobalGrid.Contents = ggHandles; % Set children to new order - - % Reset sizes - obj.GlobalGrid.RowSizes = repmat(obj.GlobalVSpacing, 1, size(obj.GlobalControls, 1)); - set(get(obj.GlobalGrid, 'Parent'),... - 'Heights', sum(obj.GlobalGrid.RowSizes)+45); % Reset height of globalPanel - obj.GlobalGrid.ColumnSizes = [180, 200, 40]; - obj.GlobalGrid.Spacing = 1; - end - - function globaliseParamAtCell(obj, row, col) - name = obj.TableColumnParamNames{col}; - value = obj.Parameters.Struct.(name)(:,row); - obj.Parameters.makeGlobal(name, value); - end - - function setSelectedValues(obj) % Set multiple fields in conditional table - disp('updating table cells'); - cols = obj.SelectedCells(:,2); % selected columns - uCol = unique(obj.SelectedCells(:,2)); - rows = obj.SelectedCells(:,1); % selected rows - % get current values of selected cells - currVals = arrayfun(@(u)obj.ConditionTable.Data(rows(cols==u),u), uCol, 'UniformOutput', 0); - names = obj.TableColumnParamNames(uCol); % selected column names - promt = cellfun(@(a,b) [a ' (' num2str(sum(cols==b)) ')'],... - names, num2cell(uCol), 'UniformOutput', 0); % names of columns & num selected rows - defaultans = cellfun(@(c) c(1), currVals); - answer = inputdlg(promt,'Set values', 1, cellflat(defaultans)); % prompt for input - if isempty(answer) % if user presses cancel - return - end - % set values for each column - cellfun(@(a,b,c) setNewVals(a,b,c), answer, currVals, names, 'UniformOutput', 0); - function newVals = setNewVals(userIn, currVals, paramName) - % check array orientation - currVals = iff(size(currVals,1)>size(currVals,2),currVals',currVals); - if strStartsWith(userIn,'@') % anon function - func_h = str2func(userIn); - % apply function to each cell - currVals = cellfun(@str2double,currVals, 'UniformOutput', 0); % convert from char - newVals = cellfun(func_h, currVals, 'UniformOutput', 0); - elseif any(userIn==':') % array syntax - arr = eval(userIn); - newVals = num2cell(arr); % convert to cell array - elseif any(userIn==','|userIn==';') % 2D arrays - C = strsplit(userIn, ';'); - newVals = cellfun(@(c)textscan(c, '%f',... - 'ReturnOnError', false,... - 'delimiter', {' ', ','}, 'MultipleDelimsAsOne', 1),... - C); - else % single value to copy across all cells - userIn = str2double(userIn); - newVals = num2cell(ones(size(currVals))*userIn); - end - - if length(newVals)>length(currVals) % too many new values - newVals = newVals(1:length(currVals)); % truncate new array - elseif length(newVals)<length(currVals) % too few new values - % populate as many cells as possible - newVals = [newVals ... - cellfun(@(a)obj.controlValue2Param(2,a),... - currVals(length(newVals)+1:end),'UniformOutput',0)]; - end - ic = strcmp(obj.TableColumnParamNames,paramName); % find edited param names - % update param struct - obj.Parameters.Struct.(paramName)(:,rows(cols==find(ic))) = cell2mat(newVals); - % update condtion table with strings - obj.ConditionTable.Data(rows(cols==find(ic)),ic)... - = cellfun(@(a)obj.paramValue2Control(a), newVals', 'UniformOutput', 0); - end - notify(obj, 'Changed'); - end - - function cellEditCallback(obj, src, eventData) - disp('updating table cell'); - row = eventData.Indices(1); - col = eventData.Indices(2); - paramName = obj.TableColumnParamNames{col}; - currValue = obj.Parameters.Struct.(paramName)(:,row); - if iscell(currValue) - % cell holders are allowed to be different types of value - newParam = obj.controlValue2Param(currValue{1}, eventData.NewData, true); - obj.Parameters.Struct.(paramName){:,row} = newParam; - else - newParam = obj.controlValue2Param(currValue, eventData.NewData); - obj.Parameters.Struct.(paramName)(:,row) = newParam; - end - % if successful update the cell with default formatting - data = get(src, 'Data'); - reformed = obj.paramValue2Control(newParam); - if iscell(reformed) - % the reformed data type is a cell, this should be a one element - % wrapping cell - if numel(reformed) == 1 - reformed = reformed{1}; - else - error('Cannot handle data reformatted data type'); - end - end - data{row,col} = reformed; - set(src, 'Data', data); - %notify listeners of change - notify(obj, 'Changed'); - end - - function updateGlobal(obj, param, src) - currParamValue = obj.Parameters.Struct.(param); - switch get(src, 'style') - case 'checkbox' - newValue = logical(get(src, 'value')); - obj.Parameters.Struct.(param) = newValue; - case 'edit' - newValue = obj.controlValue2Param(currParamValue, get(src, 'string')); - obj.Parameters.Struct.(param) = newValue; - % if successful update the control with default formatting and - % modified colour - set(src, 'String', obj.paramValue2Control(newValue),... - 'ForegroundColor', [1 0 0]); %red indicating it has changed - %notify listeners of change - notify(obj, 'Changed'); - end - end - - function [data, paramNames, titles] = tableData(obj) - [~, trialParams] = obj.Parameters.assortForExperiment; - paramNames = fieldnames(trialParams); - titles = obj.Parameters.title(paramNames); - data = reshape(struct2cell(trialParams), numel(paramNames), [])'; - data = mapToCell(@(e) obj.paramValue2Control(e), data); - end - - function data = controlValue2Param(obj, currParam, data, allowTypeChange) - % Convert the values displayed in the UI ('control values') to - % parameter values. String representations of numrical arrays and - % functions are converted back to their 'native' classes. - if nargin < 4 - allowTypeChange = false; - end - switch class(currParam) - case 'function_handle' - data = str2func(data); - case 'logical' - data = data ~= 0; - case 'char' - % do nothing - strings stay as strings - otherwise - if isnumeric(currParam) - % parse string as numeric vector - try - C = textscan(data, '%f',... - 'ReturnOnError', false,... - 'delimiter', {' ', ','}, 'MultipleDelimsAsOne', 1); - data = C{1}; - catch ex - % if a type change is allowed, then a numeric can become a - % string, otherwise rethrow the parse error - if ~allowTypeChange - rethrow(ex); - end - end - elseif iscellstr(currParam) - C = textscan(data, '%s',... - 'ReturnOnError', false,... - 'delimiter', {' ', ','}, 'MultipleDelimsAsOne', 1); - data = C{1};%deblank(num2cell(data, 2)); - else - error('Cannot update unimplemented type ''%s''', class(currParam)); - end - end - end - - function data = paramValue2Control(obj, data) - % convert from parameter value to control value, i.e. a value class - % that can be easily displayed and edited by the user. Everything - % except logicals are converted to charecter arrays. - switch class(data) - case 'function_handle' - % convert a function handle to it's string name - data = func2str(data); - case 'logical' - data = data ~= 0; % If logical do nothing, basically. - case 'string' - data = char(data); % Strings not allowed in condition table data - otherwise - if isnumeric(data) - % format numeric types as string number list - strlist = mapToCell(@num2str, data); - data = strJoin(strlist, ', '); - elseif iscellstr(data) - data = strJoin(data, ', '); - end - end - % all other data types stay as they are - end - - function fillConditionTable(obj) - [data, params, titles] = obj.tableData; - set(obj.ConditionTable, 'ColumnName', titles, 'Data', data,... - 'ColumnEditable', true(1, numel(titles))); - obj.TableColumnParamNames = params; - end - - function makeTrialSpecific(obj, paramName, ctrls) - [uirow, ~] = find(obj.GlobalControls == ctrls{1}); - assert(numel(uirow) == 1, 'Unexpected number of matching global controls'); - cellfun(@(c) delete(c), ctrls); - obj.GlobalControls(uirow,:) = []; - obj.GlobalGrid.RowSizes(uirow) = []; - obj.Parameters.makeTrialSpecific(paramName); - obj.fillConditionTable(); - set(get(obj.GlobalGrid, 'Parent'),... - 'Heights', sum(obj.GlobalGrid.RowSizes)+45); % Reset height of globalPanel - end - - function [ctrl, label, buttons] = addParamUI(obj, name) % Adds ui element for each parameter - parent = obj.GlobalGrid; % Made by build function above - ctrl = []; - label = []; - buttons = []; - if iscell(name) % 2017-02-14 MW function now called with arrayFun (instead of cellFun) - name = name{1,1}; - end - value = obj.paramValue2Control(obj.Parameters.Struct.(name)); % convert from parameter value to control value (everything but logical values become strings) - title = obj.Parameters.title(name); - description = obj.Parameters.description(name); - - if islogical(value) % If parameter is logical, make checkbox - for i = 1:length(value) - ctrl(end+1) = uicontrol('Parent', parent,... - 'Style', 'checkbox',... - 'TooltipString', description,... - 'Value', value(i),... % Added 2017-02-15 MW set checkbox to what ever the parameter value is - 'Callback', @(src, e) obj.updateGlobal(name, src)); - end - elseif ischar(value) - ctrl = uicontrol('Parent', parent,... - 'BackgroundColor', [1 1 1],... - 'Style', 'edit',... - 'String', value,... - 'TooltipString', description,... - 'UserData', name,... % save the name of the parameter in userdata - 'HorizontalAlignment', 'left',... - 'Callback', @(src, e) obj.updateGlobal(name, src)); -% elseif iscellstr(value) -% lines = mkStr(value, [], sprintf('\n'), []); -% ctrl = uicontrol('Parent', parent,... -% 'BackgroundColor', [1 1 1],... -% 'Style', 'edit',... -% 'Max', 2,... %make it multiline -% 'String', lines,... -% 'TooltipString', description,... -% 'HorizontalAlignment', 'left',... -% 'UserData', name,... % save the name of the parameter in userdata -% 'Callback', @(src, e) obj.updateGlobal(name, src)); - end - - if ~isempty(ctrl) % If control box is made, add label and conditional button - label = uicontrol('Parent', parent,... - 'Style', 'text', 'String', title, 'HorizontalAlignment', 'left',... - 'TooltipString', description); % Why not use bui.label? MW 2017-02-15 - bbox = uiextras.HBox('Parent', parent); % Make HBox for button - % UIContainer no longer present in GUILayoutToolbox, it used to - % call uipanel with the following args: - % 'Units', 'Normalized'; 'BorderType', 'none') -% buttons = bbox.UIContainer; - buttons = uicontrol('Parent', bbox, 'Style', 'pushbutton',... % Make 'conditional parameter' button - 'String', '[...]',... - 'TooltipString', sprintf(['Make this a condition parameter (i.e. vary by trial).\n'... - 'This will move it to the trial conditions table.']),... - 'FontSize', 7,... - 'Callback', @(~,~) obj.makeTrialSpecific(name, {ctrl, label, bbox})); - bbox.Sizes = 29; % Resize button height to 29px - end - end - end - -end - diff --git a/+exp/Parameters.m b/+exp/Parameters.m index a48e3d09..4fa2d9ce 100644 --- a/+exp/Parameters.m +++ b/+exp/Parameters.m @@ -71,9 +71,9 @@ function set(obj, name, value, description, units) n = numel(obj.pNames); obj.IsTrialSpecific = struct; isTrialSpecificDefault = @(n) ... - ~strcmp(n, 'randomiseConditions') &&... % randomiseConditions always global - ((ischar(obj.pStruct.(n)) && size(obj.pStruct.(n), 1) > 1) ||... % Number of rows > 1 for chars - (~ischar(obj.pStruct.(n)) && size(obj.pStruct.(n), 2) > 1)); % Number of columns > 1 for all others + strcmp(n, 'numRepeats') ||... % numRepeats always trail specific + (ischar(obj.pStruct.(n)) && size(obj.pStruct.(n), 1) > 1) ||... % Number of rows > 1 for chars + (~ischar(obj.pStruct.(n)) && size(obj.pStruct.(n), 2) > 1); % Number of columns > 1 for all others for i = 1:n name = obj.pNames{i}; obj.IsTrialSpecific.(name) = isTrialSpecificDefault(name); @@ -182,8 +182,8 @@ function makeGlobal(obj, name, newValue) 'UniformOutput', false); % concatenate trial parameter trialParamValues = cat(1, trialParamValues{:}); - if isempty(trialParamValues) % Removed MW 30.01.19 - trialParamValues = {}; + if isempty(trialParamValues) + trialParamValues = {1}; end trialParams = cell2struct(trialParamValues, trialParamNames, 1)'; globalParams = cell2struct(globalParamValues, globalParamNames, 1); diff --git a/cortexlab/+git/update.m b/cortexlab/+git/update.m index cd939a83..200ba269 100644 --- a/cortexlab/+git/update.m +++ b/cortexlab/+git/update.m @@ -28,7 +28,7 @@ function update(scheduled) % than an hour ago. if (scheduled && (weekday(now) ~= scheduled) && now - lastFetch < 7) || ... (scheduled && (weekday(now) == scheduled) && now - lastFetch < 1) || ... - (~scheduled && now - lastFetch < 1/24) + (~scheduled && now - lastFetch < 1/24) return end disp('Updating code...') diff --git a/signals b/signals index 23f93fb3..0c9a2bea 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit 23f93fb365c441d803e7ff43b5d8f17801a409e9 +Subproject commit 0c9a2bea861352758c77a03978a874cd286835c9 From f93f73b17a48dc6bd10e91fc5cf1517e799d211d Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Mon, 11 Mar 2019 13:57:34 +0200 Subject: [PATCH 310/393] Updates to signals --- signals | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/signals b/signals index 2350bac3..bb6086a0 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit 2350bac34f61f0cecab49e7fcd9fa6c055157262 +Subproject commit bb6086a019e0382a937cbe4ccf0925c1fcd8d6e5 From 9babd51a4c8529a381f8880c056c2ca0f50b310a Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Mon, 11 Mar 2019 16:30:59 +0200 Subject: [PATCH 311/393] Added config file for todo bot --- .github/config.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .github/config.yml diff --git a/.github/config.yml b/.github/config.yml new file mode 100644 index 00000000..c41e8225 --- /dev/null +++ b/.github/config.yml @@ -0,0 +1,2 @@ +todo: + keyword: ['@todo','TODO','@fixme','FIXME'] From 09deaf1b4ac045413d2383ebee96484a17dd0e85 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Mon, 11 Mar 2019 16:35:05 +0200 Subject: [PATCH 312/393] Hack for resize issue and fix for makeConditional() --- +eui/ConditionPanel.m | 6 ++++-- +eui/FieldPanel.m | 2 +- +eui/ParamEditor.m | 22 +++++++++++++++------- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/+eui/ConditionPanel.m b/+eui/ConditionPanel.m index eacd317f..d060abcd 100644 --- a/+eui/ConditionPanel.m +++ b/+eui/ConditionPanel.m @@ -33,12 +33,14 @@ % Create a child menu for the uiContextMenus c = uicontextmenu; obj.UIPanel.UIContextMenu = c; - obj.ContextMenus = uimenu(c, 'Label', 'Make Global', 'MenuSelectedFcn', @(~,~)obj.makeGlobal); + obj.ContextMenus = uimenu(c, 'Label', 'Make Global', ... + 'MenuSelectedFcn', @(~,~)obj.makeGlobal, 'Enable', 'off'); fcn = @(s,~)obj.ParamEditor.setRandomized(~strcmp(s.Checked, 'on')); obj.ContextMenus(2) = uimenu(c, 'Label', 'Randomize conditions', ... 'MenuSelectedFcn', fcn, 'Checked', 'on', 'Tag', 'randomize button'); obj.ContextMenus(3) = uimenu(c, 'Label', 'Sort by selected column', ... - 'MenuSelectedFcn', @(~,~)disp('feature not yet implemented'), 'Tag', 'sort by'); + 'MenuSelectedFcn', @(~,~)disp('feature not yet implemented'), ... + 'Tag', 'sort by', 'Enable', 'off'); % Create condition table p = uix.Panel('Parent', obj.UIPanel); obj.ConditionTable = uitable('Parent', p,... diff --git a/+eui/FieldPanel.m b/+eui/FieldPanel.m index 10980cb6..c0c38627 100644 --- a/+eui/FieldPanel.m +++ b/+eui/FieldPanel.m @@ -83,7 +83,7 @@ function clear(obj, idx) % FIXME Rename to clearFields function makeConditional(obj, name) if nargin == 1 - selected = obj.UIPanel.Parent.CurrentObject; %FIXME Doesn't work is parent is not figure + selected = obj.ParamEditor.Root.CurrentObject; if isa(selected, 'matlab.ui.control.UIControl') && ... strcmp(selected.Style, 'text') name = selected.String; diff --git a/+eui/ParamEditor.m b/+eui/ParamEditor.m index 4c375b7e..8a760e08 100644 --- a/+eui/ParamEditor.m +++ b/+eui/ParamEditor.m @@ -11,6 +11,7 @@ GlobalUI ConditionalUI Parent + Root Listener end @@ -23,18 +24,25 @@ end methods - function obj = ParamEditor(pars, f) + function obj = ParamEditor(pars, parent) if nargin == 0; pars = []; end if nargin < 2 - f = figure('Name', 'Parameters', 'NumberTitle', 'off',... + parent = figure('Name', 'Parameters', 'NumberTitle', 'off',... 'Toolbar', 'none', 'Menubar', 'none', 'DeleteFcn', @(~,~)obj.delete); obj.Listener = event.listener(f, 'SizeChanged', @(~,~)obj.onResize); end - obj.Parent = f; - obj.UIPanel = uix.HBox('Parent', f); + obj.Root = parent; + while ~isa(obj.Root, 'matlab.ui.Figure'); obj.Root = obj.Root.Parent; end + + obj.Parent = parent; + obj.UIPanel = uix.HBox('Parent', parent); obj.GlobalUI = eui.FieldPanel(obj.UIPanel, obj); obj.ConditionalUI = eui.ConditionPanel(obj.UIPanel, obj); obj.buildUI(pars); + % FIXME Current hack for drawing params first time + pos = obj.Root.Position; + obj.Root.Position = pos+0.01; + obj.Root.Position = pos; end function delete(obj) @@ -44,9 +52,9 @@ function delete(obj) function set.Enable(obj, value) cUI = obj.ConditionalUI; - fig = obj.Parent; + parent = obj.UIPanel; if value == true - arrayfun(@(prop) set(prop, 'Enable', 'on'), findobj(fig,'Enable','off')); + arrayfun(@(prop) set(prop, 'Enable', 'on'), findobj(parent,'Enable','off')); if isempty(cUI.SelectedCells) set(cUI.MakeGlobalButton, 'Enable', 'off'); set(cUI.DeleteConditionButton, 'Enable', 'off'); @@ -54,7 +62,7 @@ function delete(obj) end obj.Enable = true; else - arrayfun(@(prop) set(prop, 'Enable', 'off'), findobj(fig,'Enable','on')); + arrayfun(@(prop) set(prop, 'Enable', 'off'), findobj(parent,'Enable','on')); obj.Enable = false; end end From 29266f0f00c5b6db89da5d98d19317147090c35a Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 12 Mar 2019 14:57:22 +0200 Subject: [PATCH 313/393] Added some tests and minor modifications --- +eui/ConditionPanel.m | 48 +++++++------ +eui/FieldPanel.m | 39 +++++++---- +eui/MControl.m | 6 +- +eui/ParamEditor.m | 149 +++++++++++++++++++++------------------- +exp/Parameters.m | 6 +- tests/ParamEditorTest.m | 137 ++++++++++++++++++++++++++++++++++++ tests/ParametersTest.m | 91 ++++++++++++++++++++++++ 7 files changed, 368 insertions(+), 108 deletions(-) create mode 100644 tests/ParamEditorTest.m create mode 100644 tests/ParametersTest.m diff --git a/+eui/ConditionPanel.m b/+eui/ConditionPanel.m index d060abcd..a674f901 100644 --- a/+eui/ConditionPanel.m +++ b/+eui/ConditionPanel.m @@ -1,5 +1,5 @@ classdef ConditionPanel < handle - %UNTITLED Summary of this class goes here + %UNTITLED Deals with formatting trial conditions UI table % Detailed explanation goes here % TODO Document % TODO Add sort by column @@ -16,7 +16,7 @@ ContextMenus end - properties %(Access = protected) + properties (Access = protected) ParamEditor Listener NewConditionButton @@ -29,7 +29,8 @@ methods function obj = ConditionPanel(f, ParamEditor, varargin) obj.ParamEditor = ParamEditor; - obj.UIPanel = uix.VBox('Parent', f, 'BackgroundColor', 'white'); + obj.UIPanel = uix.VBox('Parent', f); +% obj.UIPanel.BackgroundColor = 'white'; % Create a child menu for the uiContextMenus c = uicontextmenu; obj.UIPanel.UIContextMenu = c; @@ -42,7 +43,7 @@ 'MenuSelectedFcn', @(~,~)disp('feature not yet implemented'), ... 'Tag', 'sort by', 'Enable', 'off'); % Create condition table - p = uix.Panel('Parent', obj.UIPanel); + p = uix.Panel('Parent', obj.UIPanel, 'BorderType', 'none'); obj.ConditionTable = uitable('Parent', p,... 'FontName', 'Consolas',... 'RowName', [],... @@ -53,48 +54,37 @@ 'CellEditCallback', @obj.onEdit,... 'CellSelectionCallback', @obj.onSelect); % Create button panel to hold condition control buttons - obj.ButtonPanel = uix.HBox('Parent', obj.UIPanel, ... - 'BackgroundColor', 'white'); - % Create callback so that width of button panel is slave to width of - % conditional UIPanel -% b = obj.ButtonPanel; -% fcn = @(s)set(obj.ButtonPanel, 'Position', ... -% [s.Position(1) b.Position(2) s.Position(3) b.Position(4)]); -% obj.Listener = event.listener(obj.UIPanel, 'SizeChanged', @(s,~)fcn(s)); + obj.ButtonPanel = uix.HBox('Parent', obj.UIPanel); % Define some common properties - props.BackgroundColor = 'white'; +% props.BackgroundColor = 'white'; props.Style = 'pushbutton'; props.Units = 'normalized'; props.Parent = obj.ButtonPanel; % Create out four buttons obj.NewConditionButton = uicontrol(props,... 'String', 'New condition',... - ...'Position',[0 0 1/4 1],... 'TooltipString', 'Add a new condition',... 'Callback', @(~, ~) obj.newCondition()); obj.DeleteConditionButton = uicontrol(props,... 'String', 'Delete condition',... - ...'Position',[1/4 0 1/4 1],... 'TooltipString', 'Delete the selected condition',... 'Enable', 'off',... 'Callback', @(~, ~) obj.deleteSelectedConditions()); obj.MakeGlobalButton = uicontrol(props,... 'String', 'Globalise parameter',... - ...'Position',[2/4 0 1/4 1],... 'TooltipString', sprintf(['Make the selected condition-specific parameter global (i.e. not vary by trial)\n'... 'This will move it to the global parameters section']),... 'Enable', 'off',... 'Callback', @(~, ~) obj.makeGlobal()); obj.SetValuesButton = uicontrol(props,... 'String', 'Set values',... - ...'Position',[3/4 0 1/4 1],... 'TooltipString', 'Set selected values to specified value, range or function',... 'Enable', 'off',... 'Callback', @(~, ~) obj.setSelectedValues()); obj.ButtonPanel.Widths = [-1 -1 -1 -1]; obj.UIPanel.Heights = [-1 25]; end - + function onEdit(obj, src, eventData) disp('updating table cell'); row = eventData.Indices(1); @@ -229,7 +219,7 @@ function setSelectedValues(obj) % Set multiple fields in conditional table elseif length(newVals)<length(currVals) % too few new values % populate as many cells as possible newVals = [newVals ... - cellfun(@(a)ui.ParamEditor.controlValue2Param(2,a),... + cellfun(@(a)obj.ParamEditor.controlValue2Param(2,a),... currVals(length(newVals)+1:end),'UniformOutput',0)]; end ic = strcmp(obj.ConditionTable.ColumnName, paramName); % find edited param names @@ -242,6 +232,25 @@ function setSelectedValues(obj) % Set multiple fields in conditional table notify(obj.ParamEditor, 'Changed'); end + function fillConditionTable(obj) + % Build the condition table + titles = obj.ParamEditor.Parameters.TrialSpecificNames; + [~, trialParams] = obj.ParamEditor.Parameters.assortForExperiment; + if isempty(titles) + obj.ButtonPanel.Visible = 'off'; + obj.UIPanel.Visible = 'off'; + obj.ParamEditor.Parent.Widths = [-1, 1]; + else + obj.ButtonPanel.Visible = 'on'; + obj.UIPanel.Visible = 'on'; + obj.ParamEditor.Parent.Widths = [-1, -1]; + data = reshape(struct2cell(trialParams), numel(titles), [])'; + data = mapToCell(@(e) obj.ParamEditor.paramValue2Control(e), data); + set(obj.ConditionTable, 'ColumnName', titles, 'Data', data,... + 'ColumnEditable', true(1, numel(titles))); + end + end + function newCondition(obj) disp('adding new condition row'); PE = obj.ParamEditor; @@ -250,7 +259,6 @@ function newCondition(obj) obj.ParamEditor.fillConditionTable(); end - end end \ No newline at end of file diff --git a/+eui/FieldPanel.m b/+eui/FieldPanel.m index c0c38627..5e6c0558 100644 --- a/+eui/FieldPanel.m +++ b/+eui/FieldPanel.m @@ -1,18 +1,18 @@ classdef FieldPanel < handle - %UNTITLED Summary of this class goes here + %UNTITLED Deals with formatting global parameter UI elements % Detailed explanation goes here properties MinCtrlWidth = 40 MaxCtrlWidth = 140 - Margin = 4 + Margin = 14 RowSpacing = 1 ColSpacing = 3 UIPanel ContextMenu end - properties %(Access = protected) + properties (Access = ?eui.ParamEditor) ParamEditor MinRowHeight Listener @@ -26,21 +26,22 @@ end methods - function obj = FieldPanel(f, ParamEditor, varargin) + function obj = FieldPanel(f, ParamEditor) obj.ParamEditor = ParamEditor; - p = uix.Panel('Parent', f); + p = uix.Panel('Parent', f, 'BorderType', 'none'); obj.UIPanel = uipanel('Parent', p, 'BorderType', 'none',... - 'BackgroundColor', 'white', 'Position', [0 0 0.5 1]); + 'Position', [0 0 0.5 1]); obj.Listener = event.listener(obj.UIPanel, 'SizeChanged', @obj.onResize); end function [label, ctrl] = addField(obj, name, ctrl) + % TODO Maybe use exp.Parameters/ui if isempty(obj.ContextMenu) obj.ContextMenu = uicontextmenu; - uimenu(obj.ContextMenu, 'Label', 'Make Coditional', ... + uimenu(obj.ContextMenu, 'Label', 'Make Conditional', ... 'MenuSelectedFcn', @(~,~)obj.makeConditional); end - props.BackgroundColor = 'white'; +% props.BackgroundColor = 'white'; props.HorizontalAlignment = 'left'; props.UIContextMenu = obj.ContextMenu; props.Parent = obj.UIPanel; @@ -59,18 +60,18 @@ function onEdit(obj, src, id) switch get(src, 'style') case 'checkbox' newValue = logical(get(src, 'value')); - obj.ParamEditor.update(id, newValue); + obj.ParamEditor.updateGlobal(id, newValue); case 'edit' % if successful update the control with default formatting and % modified colour - newValue = obj.ParamEditor.update(id, get(src, 'string')); + newValue = obj.ParamEditor.updateGlobal(id, get(src, 'string')); set(src, 'String', obj.ParamEditor.paramValue2Control(newValue)); end changed = strcmp(id,{obj.Labels.String}); obj.Labels(changed).ForegroundColor = [1 0 0]; end - function clear(obj, idx) % FIXME Rename to clearFields + function clear(obj, idx) if nargin == 1 idx = true(size(obj.Labels)); end @@ -82,8 +83,20 @@ function clear(obj, idx) % FIXME Rename to clearFields end function makeConditional(obj, name) + % MAKECONDITIONAL Make field parameter into a trial condition + % This function removes the selected field from the global UI panel + % and calls Condition UI to add a column to the trial condition + % table. It also makes a change to the ParamEditor's Parameters via + % the makeTrialSpecific method. + % + % While this function can be called with a parameter name, the + % FieldPanel object is normally a protected property of the + % ParamEditor class, and the only calls to this function are via the + % context menu callback function + % + % See also eui.Parameters/makeTrialSpecific, eui.ConditionPanel/fillConditionTable if nargin == 1 - selected = obj.ParamEditor.Root.CurrentObject; + selected = obj.ParamEditor.getSelected(); if isa(selected, 'matlab.ui.control.UIControl') && ... strcmp(selected.Style, 'text') name = selected.String; @@ -97,7 +110,7 @@ function makeConditional(obj, name) obj.clear(idx); obj.ParamEditor.Parameters.makeTrialSpecific(name); - obj.ParamEditor.fillConditionTable(); + obj.ParamEditor.ConditionalUI.fillConditionTable(); obj.onResize; end diff --git a/+eui/MControl.m b/+eui/MControl.m index 952c25a9..d3032e83 100644 --- a/+eui/MControl.m +++ b/+eui/MControl.m @@ -247,6 +247,7 @@ function saveParamProfile(obj) % Called by 'Save...' button press, save a new pa end function loadParamProfile(obj, profile) + tic set(obj.ParamProfileLabel, 'String', 'loading...', 'ForegroundColor', [1 0 0]); % Red 'Loading...' while new set loads if ~isempty(obj.ParamEditor) % Clear existing parameters control @@ -308,9 +309,7 @@ function loadParamProfile(obj, profile) if isempty(paramStruct); return; end % Now parameters are loaded, pass to ParamEditor for display, etc. if isempty(obj.ParamEditor) - panel = uipanel('Parent', obj.ParamPanel, 'Position', [0 0 1 1]); -% panel = uiextras.Panel('Parent', obj.ParamPanel); - obj.ParamEditor = eui.ParamEditor(obj.Parameters, panel); % Build parameter list in Global panel by calling eui.ParamEditor + obj.ParamEditor = eui.ParamEditor(obj.Parameters, obj.ParamPanel); % Build parameter list in Global panel by calling eui.ParamEditor else obj.ParamEditor.buildUI(obj.Parameters); end @@ -318,6 +317,7 @@ function loadParamProfile(obj, profile) if strcmp(obj.RemoteRigs.Selected.Status, 'idle') set(obj.BeginExpButton, 'Enable', 'on') % Re-enable start button end + disp(toc) end function paramChanged(obj) diff --git a/+eui/ParamEditor.m b/+eui/ParamEditor.m index 8a760e08..e5e614b6 100644 --- a/+eui/ParamEditor.m +++ b/+eui/ParamEditor.m @@ -1,12 +1,12 @@ classdef ParamEditor < handle %UNTITLED2 Summary of this class goes here - % Detailed explanation goes here + % ParamEditor deals with setting the paramters via a GUI properties Parameters end - properties %(Access = private) + properties (Access = {?eui.ConditionPanel, ?eui.FieldPanel}) UIPanel GlobalUI ConditionalUI @@ -29,15 +29,13 @@ if nargin < 2 parent = figure('Name', 'Parameters', 'NumberTitle', 'off',... 'Toolbar', 'none', 'Menubar', 'none', 'DeleteFcn', @(~,~)obj.delete); - obj.Listener = event.listener(f, 'SizeChanged', @(~,~)obj.onResize); end obj.Root = parent; while ~isa(obj.Root, 'matlab.ui.Figure'); obj.Root = obj.Root.Parent; end - - obj.Parent = parent; - obj.UIPanel = uix.HBox('Parent', parent); - obj.GlobalUI = eui.FieldPanel(obj.UIPanel, obj); - obj.ConditionalUI = eui.ConditionPanel(obj.UIPanel, obj); +% obj.Listener = event.listener(parent, 'SizeChanged', @(~,~)obj.onResize); + obj.Parent = uix.HBox('Parent', parent); + obj.GlobalUI = eui.FieldPanel(obj.Parent, obj); + obj.ConditionalUI = eui.ConditionPanel(obj.Parent, obj); obj.buildUI(pars); % FIXME Current hack for drawing params first time pos = obj.Root.Position; @@ -45,6 +43,17 @@ obj.Root.Position = pos; end + function selected = getSelected(obj) + % GETSELECTED Return the object currently in focus + % Returns handle to the object currently in focus in the figure, + % that is, the object last clicked on by the user. This is used by + % the FieldPanel context menu to determine which parameter was + % selected. + % + % See also EUI.FIELDPANEL + selected = obj.Root.CurrentObject; + end + function delete(obj) delete(obj.GlobalUI); delete(obj.ConditionalUI); @@ -52,7 +61,7 @@ function delete(obj) function set.Enable(obj, value) cUI = obj.ConditionalUI; - parent = obj.UIPanel; + parent = obj.Parent; % FIXME: use tags instead? if value == true arrayfun(@(prop) set(prop, 'Enable', 'on'), findobj(parent,'Enable','off')); if isempty(cUI.SelectedCells) @@ -74,21 +83,25 @@ function clear(obj) function buildUI(obj, pars) obj.Parameters = pars; - obj.clear() - c = obj.GlobalUI; - names = pars.GlobalNames; + obj.clear() % Clear the current parameter UI elements + if isempty(pars); return; end % Nothing to build + c = obj.GlobalUI; % Handle to FieldPanel object + names = pars.GlobalNames; % Names of the global parameters for nm = names' + % RandomiseConditions is a special parameter represented in the + % context menu, don't create global param field if strcmp(nm, 'randomiseConditions'); continue; end if islogical(pars.Struct.(nm{:})) % If parameter is logical, make checkbox ctrl = uicontrol('Parent', c.UIPanel, 'Style', 'checkbox', ... - 'Value', pars.Struct.(nm{:}), 'BackgroundColor', 'white'); + 'Value', pars.Struct.(nm{:})); addField(c, nm{:}, ctrl); - else + else % Otherwise create the default field; a text box [~, ctrl] = addField(c, nm{:}); ctrl.String = obj.paramValue2Control(pars.Struct.(nm{:})); end end - obj.fillConditionTable(); + % Populate the trial conditions table + obj.ConditionalUI.fillConditionTable(); %%% Special parameters if ismember('randomiseConditions', obj.Parameters.Names) && ~pars.Struct.randomiseConditions obj.ConditionalUI.ConditionTable.RowName = 'numbered'; @@ -104,7 +117,7 @@ function setRandomized(obj, value) description = 'Whether to randomise the conditional paramters or present them in order'; obj.Parameters.set('randomiseConditions', false, description, 'logical') elseif ismember('randomiseConditions', obj.Parameters.Names) - obj.update('randomiseConditions', logical(value)); + obj.updateGlobal('randomiseConditions', logical(value)); end menu = obj.ConditionalUI.ContextMenus(2); if value == false @@ -115,26 +128,10 @@ function setRandomized(obj, value) menu.Checked = 'on'; end end - - function fillConditionTable(obj) - % Build the condition table - titles = obj.Parameters.TrialSpecificNames; - [~, trialParams] = obj.Parameters.assortForExperiment; - if isempty(titles) - obj.ConditionalUI.ButtonPanel.Visible = 'off'; - obj.ConditionalUI.UIPanel.Visible = 'off'; - obj.GlobalUI.UIPanel.Position(3) = 1; - else - obj.ConditionalUI.ButtonPanel.Visible = 'on'; - obj.ConditionalUI.UIPanel.Visible = 'on'; - data = reshape(struct2cell(trialParams), numel(titles), [])'; - data = mapToCell(@(e) obj.paramValue2Control(e), data); - set(obj.ConditionalUI.ConditionTable, 'ColumnName', titles, 'Data', data,... - 'ColumnEditable', true(1, numel(titles))); - end - end - + function addEmptyConditionToParam(obj, name) + % Add a new trial specific condition to the table + % See also EUI.CONDITIONPANEL/NEWCONDITION assert(obj.Parameters.isTrialSpecific(name),... 'Tried to add a new condition to global parameter ''%s''', name); % work out what the right 'empty' is for the parameter @@ -166,8 +163,7 @@ function addEmptyConditionToParam(obj, name) obj.Parameters.Struct.(name) = cat(2, obj.Parameters.Struct.(name), newValue); end - function newValue = update(obj, name, value, row) - % FIXME change name to updateGlobal + function newValue = updateGlobal(obj, name, value, row) if nargin < 4; row = 1; end currValue = obj.Parameters.Struct.(name)(:,row); if iscell(currValue) @@ -189,7 +185,7 @@ function globaliseParamAtCell(obj, name, row) value = obj.Parameters.Struct.(name)(:,row); obj.Parameters.makeGlobal(name, value); % Refresh the table of conditions - obj.fillConditionTable; + obj.ConditionalUI.fillConditionTable; % Add new global parameter to field panel if islogical(value) % If parameter is logical, make checkbox ctrl = uicontrol('Parent', obj.GlobalUI.UIPanel, 'Style', 'checkbox', ... @@ -204,65 +200,78 @@ function globaliseParamAtCell(obj, name, row) end function onResize(obj) - %%% resize condition table - notify(obj.ConditionalUI.ButtonPanel, 'SizeChanged'); + % ONRESIZE Resize widths of the two panels + % To maximize space resize the widths of the Conditional and Global + % panels + % + % FIXME Resize buggy due to tolerences + + % If there are no conditional params assume Condition table is hidden + % and GlobalUI takes up all space + if numel(obj.Parameters.TrialSpecificNames) == 0; return; end cUI = obj.ConditionalUI.UIPanel; gUI = obj.GlobalUI.UIPanel; + % The position of the end column in the Global panel (in pixels) pos = obj.GlobalUI.Controls(end).Position; colExtent = pos(1) + pos(3) + obj.GlobalUI.Margin; colWidth = pos(3) + obj.GlobalUI.Margin + obj.GlobalUI.ColSpacing; % FIXME: inaccurate + % The amount of space the Global panel takes up in pixels pos = getpixelposition(gUI); gUIExtent = pos(3); + % The amount of space the Conditional panel takes up in pixels pos = getpixelposition(cUI); cUIExtent = pos(3); - + % The actual extent of the table in pixels extent = get(obj.ConditionalUI.ConditionTable, 'Extent'); - panelWidth = cUI.Position(3); - if colExtent > gUIExtent && cUIExtent > obj.ConditionalUI.MinWidth + requiredTableWidth = extent(3); + + if floor(gUIExtent - colExtent) <= 2 && cUIExtent < obj.ConditionalUI.MinWidth + % No space at all. Compromise by having both panels take up half + % the figure + obj.Parent.Widths = [-1 -1]; + elseif floor(gUIExtent - colExtent) <= 2 && cUIExtent > obj.ConditionalUI.MinWidth % If global UI controls are cut off and there is no dead space in % the table but the minimum table width hasn't been reached, reduce % the conditional UI width: table has scroll bar and global panel % does not - % FIXME calculate how much space required for min control width -% obj.GlobalUI.MinCtrlWidth - % Calculate conditional UI width in normalized units - requiredWidth = (cUI.Position(3) / cUIExtent) * (colExtent - gUIExtent); - minConditionalWidth = (cUI.Position(3) / cUIExtent) * obj.ConditionalUI.MinWidth; - if requiredWidth < minConditionalWidth + minRequiredGlobal = cUIExtent - (colExtent - gUIExtent); + if minRequiredGlobal < obj.ConditionalUI.MinWidth && sign(minRequiredGlobal) == 1 + % If the extra space taken by Global doesn't reduce Conditional + % beyond its minimum, use the Global's minimum + obj.Parent.Widths = [colExtent -1]; + elseif minRequiredGlobal > requiredTableWidth && sign(minRequiredGlobal) == 1 + % If the required width is smaller that the required table width, + % use required table width + obj.Parent.Widths = [-1 requiredTableWidth]; + else % If the required width is smaller that the minimum table width, % use minimum table width - cUI.Position(3) = minConditionalWidth; - else % Otherwise use this width - cUI.Position(3) = requiredWidth; + obj.Parent.Widths = [-1 obj.ConditionalUI.MinWidth]; end - cUI.Position(1) = 1-cUI.Position(3); - gUI.Position(3) = 1-cUI.Position(3); - elseif extent(3) < 1 && colWidth < obj.GlobalUI.MaxCtrlWidth + elseif requiredTableWidth < cUIExtent && colWidth < obj.GlobalUI.MaxCtrlWidth % If there is dead table space and the global UI columns are cut % off or squashed, reduce the conditional panel - cUI.Position(3) = cUI.Position(3) - (panelWidth - (panelWidth * extent(3))); - cUI.Position(1) = cUI.Position(1) + (panelWidth - (panelWidth * extent(3))); - gUI.Position(3) = cUI.Position(1); - elseif extent(3) < 1 && colExtent < gUIExtent + % If the extra space is minimum, return + if floor(cUIExtent - requiredTableWidth) <= 2 && ... + sign(floor(cUIExtent - requiredTableWidth)) == 1 + return + end + obj.Parent.Widths = [colExtent -1];%[-1 requiredTableWidth]; + elseif requiredTableWidth < cUIExtent && colExtent < gUIExtent % Plenty of space! Increase conditional UI a bit deadspace = gUIExtent - colExtent; % Spece between panels in pixels - % Convert global UI pixels to relative units - gUI.Position(3) = (gUI.Position(3) / gUIExtent) * (gUIExtent - (deadspace/2)); - cUI.Position(1) = gUI.Position(3); - cUI.Position(3) = 1-gUI.Position(3); - elseif extent(3) >= 1 && colExtent < gUIExtent + obj.Parent.Widths = [-1 cUIExtent + deadspace/2]; + elseif requiredTableWidth >= cUIExtent && colExtent < gUIExtent % If the table space is cut off and there is dead space in the % global UI panel, reduce the global UI panel % If the extra space is minimum, return if floor(gUIExtent - colExtent) <= 2; return; end + % Get total space deadspace = gUIExtent - colExtent; % Spece between panels in pixels - gUI.Position(3) = (gUI.Position(3) / gUIExtent) * (gUIExtent - deadspace); - cUI.Position(3) = 1-gUI.Position(3); - cUI.Position(1) = gUI.Position(3); - else - % Compromise by having both panels take up half the figure -% [cUI.Position([1,3]), gUI.Position(3)] = deal(0.5); + obj.Parent.Widths = [-1 cUIExtent + (deadspace/2)]; +% cUI.Position(3) = 1-gUI.Position(3); +% cUI.Position(1) = gUI.Position(3); end notify(obj.ConditionalUI.ButtonPanel, 'SizeChanged'); end diff --git a/+exp/Parameters.m b/+exp/Parameters.m index a48e3d09..a44896fd 100644 --- a/+exp/Parameters.m +++ b/+exp/Parameters.m @@ -122,8 +122,10 @@ function removeConditions(obj, indices) function makeTrialSpecific(obj, name) assert(~obj.isTrialSpecific(name), '''%s'' is already trial-specific', name); - currValue = obj.Struct.(name); - n = numTrialConditions(obj); + currValue = obj.Struct.(name); % Current value of parameter + n = numTrialConditions(obj); % Number of trial conditions (table rows) + if n < 1; n = 2; end % If there are none, let's add two conditions + % Repeat value accross all trial conditions if isnumeric(currValue) || islogical(currValue) newValue = repmat(currValue, 1, n); else diff --git a/tests/ParamEditorTest.m b/tests/ParamEditorTest.m new file mode 100644 index 00000000..bbe00db4 --- /dev/null +++ b/tests/ParamEditorTest.m @@ -0,0 +1,137 @@ +classdef ParamEditorTest < matlab.unittest.TestCase + + properties + % Figure visibility setting before running tests + FigureVisibleDefault + % ParamEditor instance + ParamEditor + % Figure handle for ParamEditor + Figure + end + + properties %(MethodSetupParameter) + Parameters + end + + methods (TestClassSetup) + function killFigures(testCase) + testCase.FigureVisibleDefault = get(0,'DefaultFigureVisible'); +% set(0,'DefaultFigureVisible','off'); + end + + function loadData(testCase) + % Loads validation data + % Graph data is a cell array where each element is the graph number + % (1:3) and within each element is a cell of X- and Y- axis values + % respecively +% load('data/parameters.mat', 'parameters') + testCase.Parameters = exp.choiceWorldParams; + end + + function setupEditor(testCase) + % Check paths file + assert(endsWith(which('dat.paths'), fullfile('tests','+dat','paths.m'))); + % Create stand-alone panel + testCase.ParamEditor = eui.ParamEditor; + testCase.Figure = gcf(); + testCase.fatalAssertTrue(isa(testCase.ParamEditor, 'eui.ParamEditor')) + end + end + + methods (TestClassTeardown) + function restoreFigures(testCase) + set(0,'DefaultFigureVisible',testCase.FigureVisibleDefault); + close(testCase.Figure) + end + end + + methods (TestMethodSetup) + function buildParams(testCase) + % Re-build the parameters before each test so that changes in + % previous test don't persist + PE = testCase.ParamEditor; + pars = exp.Parameters(testCase.Parameters); + PE.buildUI(pars); + % Number of global parameters: find all text labels + nGlobalLabels = numel(findobj(testCase.Figure, 'Style', 'text')); + nGlobalInput = numel(findobj(testCase.Figure, 'Style', 'checkbox', '-or', 'Style', 'edit')); + % Find Condition Table + conditionTable = findobj(testCase.Figure, '-property', 'ColumnName'); + % Ensure all global params have UI input and label + testCase.fatalAssertTrue(nGlobalLabels == numel(PE.Parameters.GlobalNames)) + testCase.fatalAssertTrue(nGlobalInput == numel(PE.Parameters.GlobalNames)) + % Ensure all conditional params have column in table + testCase.fatalAssertTrue(isequal(size(conditionTable.Data), ... + [PE.Parameters.numTrialConditions, numel(PE.Parameters.TrialSpecificNames)])) + end + end + + methods (Test) + function test_makeConditional(testCase) + % Make some global params trial conditions. This test checks that + % the UI elements are re-rendered after making a parameter + % conditional, and that the underlying Parameters object is also + % affected + PE = testCase.ParamEditor; + % Number of global parameters: find all text labels + gLabels = @()findobj(testCase.Figure, 'Style', 'text'); + gInputs = @()findobj(testCase.Figure, 'Style', 'checkbox', '-or', 'Style', 'edit'); + nGlobalLabels = numel(gLabels()); + nGlobalInputs = numel(gInputs()); + % Find Condition Table + conditionTable = findobj(testCase.Figure, '-property', 'ColumnName'); + tableSz = size(conditionTable.Data); + + % Retrieve context menu function handle + c = findobj(testCase.Figure, 'Text', 'Make Conditional'); + % Set the focused object to one of the parameter labels + set(testCase.Figure, 'CurrentObject', ... + findobj(testCase.Figure, 'String', 'rewardVolume')) + testCase.verifyWarningFree(c.MenuSelectedFcn, ... + 'Problem making parameter conditional'); + % Verify change in UI elements + testCase.verifyTrue(numel(gLabels()) == nGlobalLabels-1, ... + 'Global parameter UI element not removed') + testCase.verifyTrue(numel(gInputs()) == nGlobalInputs-1, ... + 'Global parameter UI element not removed') + testCase.verifyTrue(size(conditionTable.Data,2) == tableSz(2)+1, ... + 'Incorrect condition table') + % Verify change in Parameters object for global + testCase.assertTrue(numel(gLabels()) == numel(PE.Parameters.GlobalNames)) + % Verify change in Parameters object for conditional + testCase.assertTrue(isequal(size(conditionTable.Data), ... + [PE.Parameters.numTrialConditions, numel(PE.Parameters.TrialSpecificNames)])) + % Verify table values are correct + testCase.verifyTrue(isequal(conditionTable.Data(:,1), repmat({'3'}, ... + size(conditionTable.Data,1), 1)), 'Unexpected table values') + + % Test behaviour when all params made conditional + for param = gLabels()' + % Set the focused object to one of the parameter labels + set(testCase.Figure, 'CurrentObject', param) + testCase.verifyWarningFree(c.MenuSelectedFcn, ... + 'Problem making parameter conditional'); + end + end + + function test_paramValue2Control(testCase) + end + + function test_newCondition(testCase) + end + + function test_deleteCondition(testCase) + end + + function test_setValues(testCase) + end + + function test_globaliseParam(testCase) + end + + function test_paramEdits(testCase) + end + + end + +end \ No newline at end of file diff --git a/tests/ParametersTest.m b/tests/ParametersTest.m new file mode 100644 index 00000000..f8d144d0 --- /dev/null +++ b/tests/ParametersTest.m @@ -0,0 +1,91 @@ +classdef ParametersTest < matlab.unittest.TestCase + + properties + % Parameters object + Parameters + end + + properties %(MethodSetupParameter) + % Parameters structure + ParamStruct + end + + methods (TestClassSetup) + + function loadData(testCase) + % Loads validation data + % Graph data is a cell array where each element is the graph number + % (1:3) and within each element is a cell of X- and Y- axis values + % respecively +% load('data/parameters.mat', 'parameters') + testCase.ParamStruct = exp.choiceWorldParams; + end + + function setupClass(testCase) + % Check paths file + assert(endsWith(which('dat.paths'), fullfile('tests','+dat','paths.m'))); + % Create stand-alone panel + testCase.Parameters = exp.Parameters(); + testCase.fatalAssertTrue(isa(testCase.ParamEditor, 'exp.Parameters')) + end + end + + methods (TestMethodSetup) + function buildParams(testCase) + % Re-build the parameters before each test so that changes in + % previous test don't persist + PE = testCase.ParamEditor; + pars = exp.Parameters(testCase.Parameters); + PE.buildUI(pars); + % Number of global parameters: find all text labels + nGlobalLabels = numel(findobj(testCase.Figure, 'Style', 'text')); + nGlobalInput = numel(findobj(testCase.Figure, 'Style', 'checkbox', '-or', 'Style', 'edit')); + % Find Condition Table + conditionTable = findobj(testCase.Figure, '-property', 'ColumnName'); + % Ensure all global params have UI input and label + testCase.fatalAssertTrue(nGlobalLabels == numel(PE.Parameters.GlobalNames)) + testCase.fatalAssertTrue(nGlobalInput == numel(PE.Parameters.GlobalNames)) + % Ensure all conditional params have column in table + testCase.fatalAssertTrue(isequal(size(conditionTable.Data), ... + [PE.Parameters.numTrialConditions, numel(PE.Parameters.TrialSpecificNames)])) + end + end + + methods (Test) + + function test_isTrialSpecific(testCase) + end + + function test_numTrialConditions(testCase) + end + + function test_title(testCase) + end + + function test_assortForExperiment(testCase) + end + + function test_makeGlobal(testCase) + end + + function test_removeConditions(testCase) + end + + function test_description(testCase) + end + + function test_toConditionServer(testCase) + end + + function test_makeTrialSpecific(testCase) + end + + function test_set(testCase) + end + + function test_ui(testCase) + end + + end + +end \ No newline at end of file From ad16e6f302c991b5a9124ad37369ec1d13b0b9d3 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 12 Mar 2019 21:37:48 +0200 Subject: [PATCH 314/393] Complete Parameters test; parameters deals with strings better; added test for repelems --- +exp/Parameters.m | 18 +++- cb-tools/burgbox/repelems.m | 2 + tests/ParamEditorTest.m | 30 +++--- tests/ParametersTest.m | 197 +++++++++++++++++++++++++++++++----- tests/repelems_test.m | 20 ++++ 5 files changed, 225 insertions(+), 42 deletions(-) create mode 100644 tests/repelems_test.m diff --git a/+exp/Parameters.m b/+exp/Parameters.m index a44896fd..64a81016 100644 --- a/+exp/Parameters.m +++ b/+exp/Parameters.m @@ -81,6 +81,17 @@ function set(obj, name, value, description, units) end function str = title(obj, name) + % TITLE Turns param struct field name into title for UI label + % Input name must be a fieldname in Struct or cell array thereof. + % The returned str is the fieldname with a space inserted between + % upper case letters, and the first letter capitalized. If units + % field is present, the unit is added to the string in brackets + % + % Example: + % obj.title('numRepeats') % returns 'Num repeats' + % obj.title({'numRepeats', 'rewardVolume'}) + % % returns {'Num repeats', 'Reward volume (ul)'} + % See also INDIVTITLE if iscell(name) str = mapToCell(@obj.indivTitle, name); else @@ -126,7 +137,7 @@ function makeTrialSpecific(obj, name) n = numTrialConditions(obj); % Number of trial conditions (table rows) if n < 1; n = 2; end % If there are none, let's add two conditions % Repeat value accross all trial conditions - if isnumeric(currValue) || islogical(currValue) + if isnumeric(currValue) || islogical(currValue) || isstring(currValue) newValue = repmat(currValue, 1, n); else newValue = repmat({currValue}, 1, n); @@ -167,7 +178,7 @@ function makeGlobal(obj, name, newValue) end function [globalParams, trialParams] = assortForExperiment(obj) - % divide into global and trial-specific parameter structures + % Divide into global and trial-specific parameter structures %% group trial-specific parameters into a struct with length of parameters % the second dimension (number of columns) specifies parameters for @@ -219,6 +230,9 @@ function makeGlobal(obj, name, newValue) end function [ctrl, label] = ui(obj, name, parent) + % FIXME method not used at all(?) + % Seems to reuse code put into indivTitle method + % Doesn't return uicontrols if units aren't '�' or 's' ctrl = []; label = []; unitField = [name 'Units']; diff --git a/cb-tools/burgbox/repelems.m b/cb-tools/burgbox/repelems.m index f1a661fa..2d2b5c3e 100644 --- a/cb-tools/burgbox/repelems.m +++ b/cb-tools/burgbox/repelems.m @@ -4,6 +4,8 @@ % specified by the corresponding element of 'n'. e.g. % REPELEMS([0 1 2], [2 1 3]) % returns [0 0 1 2 2 2] % +% See also exp.Parameters/toConditionServer +% % Part of Burgbox % 2013-06 CB created diff --git a/tests/ParamEditorTest.m b/tests/ParamEditorTest.m index bbe00db4..0ed19edf 100644 --- a/tests/ParamEditorTest.m +++ b/tests/ParamEditorTest.m @@ -14,44 +14,40 @@ end methods (TestClassSetup) - function killFigures(testCase) + function setup(testCase) + % Hide figures and add teardown function to restore settings testCase.FigureVisibleDefault = get(0,'DefaultFigureVisible'); % set(0,'DefaultFigureVisible','off'); - end - - function loadData(testCase) + testCase.addTearDown(@set, 0, 'DefaultFigureVisible', testCase.FigureVisibleDefault); + testCase.addTearDown(@close, testCase.Figure); + % Loads validation data % Graph data is a cell array where each element is the graph number % (1:3) and within each element is a cell of X- and Y- axis values % respecively -% load('data/parameters.mat', 'parameters') testCase.Parameters = exp.choiceWorldParams; - end - - function setupEditor(testCase) + % Check paths file assert(endsWith(which('dat.paths'), fullfile('tests','+dat','paths.m'))); % Create stand-alone panel + testCase.startMeasuring(); testCase.ParamEditor = eui.ParamEditor; + testCase.stopMeasuring(); testCase.Figure = gcf(); testCase.fatalAssertTrue(isa(testCase.ParamEditor, 'eui.ParamEditor')) end + end - - methods (TestClassTeardown) - function restoreFigures(testCase) - set(0,'DefaultFigureVisible',testCase.FigureVisibleDefault); - close(testCase.Figure) - end - end - + methods (TestMethodSetup) function buildParams(testCase) % Re-build the parameters before each test so that changes in % previous test don't persist PE = testCase.ParamEditor; pars = exp.Parameters(testCase.Parameters); + testCase.startMeasuring(); PE.buildUI(pars); + testCase.stopMeasuring(); % Number of global parameters: find all text labels nGlobalLabels = numel(findobj(testCase.Figure, 'Style', 'text')); nGlobalInput = numel(findobj(testCase.Figure, 'Style', 'checkbox', '-or', 'Style', 'edit')); @@ -87,8 +83,10 @@ function test_makeConditional(testCase) % Set the focused object to one of the parameter labels set(testCase.Figure, 'CurrentObject', ... findobj(testCase.Figure, 'String', 'rewardVolume')) + testCase.startMeasuring(); testCase.verifyWarningFree(c.MenuSelectedFcn, ... 'Problem making parameter conditional'); + testCase.stopMeasuring(); % Verify change in UI elements testCase.verifyTrue(numel(gLabels()) == nGlobalLabels-1, ... 'Global parameter UI element not removed') diff --git a/tests/ParametersTest.m b/tests/ParametersTest.m index f8d144d0..677a7046 100644 --- a/tests/ParametersTest.m +++ b/tests/ParametersTest.m @@ -3,6 +3,7 @@ properties % Parameters object Parameters + nConditional = 6 end properties %(MethodSetupParameter) @@ -13,78 +14,226 @@ methods (TestClassSetup) function loadData(testCase) - % Loads validation data - % Graph data is a cell array where each element is the graph number - % (1:3) and within each element is a cell of X- and Y- axis values - % respecively -% load('data/parameters.mat', 'parameters') - testCase.ParamStruct = exp.choiceWorldParams; + % Sets a test parameter structure + % testCase.ParamStruct = exp.choiceWorldParams; + n = testCase.nConditional; + testCase.ParamStruct = struct(... + 'numRepeats', repmat(100,1,n),... + 'numRepeatsUnits', '#',... + 'numRepeatsDescription', 'No. of repeats of each condition',... + 'charParam', 'test',... + 'charParamUnits', 'normalised',... + 'charParamDescription', 'test char array parameter',... + 'strParam', "testStr",... + 'arrayParam', magic(n),... + 'arrayParamUnits', 'mW',... + 'logicalArrayParam', true(1,n),... + 'logicalParam', false,... + 'logicalParamUnits', 'logical',... + 'doubleParam', 3,... + 'functionParam', @(pars,rig)exp.configureChoiceExperiment(exp.ChoiceWorld,pars,rig)); end function setupClass(testCase) % Check paths file assert(endsWith(which('dat.paths'), fullfile('tests','+dat','paths.m'))); % Create stand-alone panel + testCase.startMeasuring('gen_empty_obj'); testCase.Parameters = exp.Parameters(); - testCase.fatalAssertTrue(isa(testCase.ParamEditor, 'exp.Parameters')) + testCase.stopMeasuring('gen_empty_obj'); + testCase.fatalAssertTrue(isa(testCase.Parameters, 'exp.Parameters')) end end methods (TestMethodSetup) function buildParams(testCase) - % Re-build the parameters before each test so that changes in + % Re-load the param structure before each test so that changes in % previous test don't persist - PE = testCase.ParamEditor; - pars = exp.Parameters(testCase.Parameters); - PE.buildUI(pars); - % Number of global parameters: find all text labels - nGlobalLabels = numel(findobj(testCase.Figure, 'Style', 'text')); - nGlobalInput = numel(findobj(testCase.Figure, 'Style', 'checkbox', '-or', 'Style', 'edit')); - % Find Condition Table - conditionTable = findobj(testCase.Figure, '-property', 'ColumnName'); - % Ensure all global params have UI input and label - testCase.fatalAssertTrue(nGlobalLabels == numel(PE.Parameters.GlobalNames)) - testCase.fatalAssertTrue(nGlobalInput == numel(PE.Parameters.GlobalNames)) - % Ensure all conditional params have column in table - testCase.fatalAssertTrue(isequal(size(conditionTable.Data), ... - [PE.Parameters.numTrialConditions, numel(PE.Parameters.TrialSpecificNames)])) + testCase.startMeasuring('setStruct'); + testCase.Parameters.Struct = testCase.ParamStruct; + testCase.stopMeasuring('setStruct'); + testCase.fatalAssertTrue(length(testCase.Parameters.TrialSpecificNames) == 3) + testCase.fatalAssertTrue(length(testCase.Parameters.GlobalNames) == 5) + testCase.fatalAssertTrue(length(testCase.Parameters.Names) == 8) end end methods (Test) function test_isTrialSpecific(testCase) + p = testCase.Parameters; + % Verfy that array with n columns > 1 is trial specific + testCase.verifyTrue(p.isTrialSpecific('numRepeats')) + % Verify that array with n columns == 1 is not trial specific + testCase.verifyTrue(~p.isTrialSpecific('doubleParam')) + testCase.verifyTrue(~p.isTrialSpecific('charParam')) end function test_numTrialConditions(testCase) + p = testCase.Parameters; + testCase.verifyTrue(p.numTrialConditions == testCase.nConditional) + testCase.verifyTrue(p.numTrialConditions == size(p.Struct.numRepeats,2)) end function test_title(testCase) + p = testCase.Parameters; + % Test single param title + testCase.verifyEqual(p.title('numRepeats'), 'Num repeats') + % Test array of param titles, including those with units + testCase.startMeasuring('title'); + str = p.title({'numRepeats', 'charParam', 'arrayParam', 'logicalParam'}); + testCase.stopMeasuring('title'); + expected = {'Num repeats', 'Char param', 'Array param (mW)', 'Logical param'}; + testCase.verifyTrue(isequal(str, expected)) end function test_assortForExperiment(testCase) + p = testCase.Parameters; + testCase.startMeasuring('assort'); + [globalParams, trialParams] = testCase.verifyWarningFree(@()p.assortForExperiment); + testCase.stopMeasuring('assort'); + testCase.verifyTrue(isstruct(globalParams)) + testCase.verifyTrue(isequal(fieldnames(globalParams), p.GlobalNames)) + testCase.verifyTrue(isequal(size(trialParams), [1 testCase.nConditional])) + testCase.verifyTrue(isequal(fieldnames(trialParams), p.TrialSpecificNames)) end function test_makeGlobal(testCase) + p = testCase.Parameters; + testCase.startMeasuring('makeGlobal'); + p.makeGlobal('numRepeats') + testCase.stopMeasuring('makeGlobal'); + testCase.verifyTrue(~p.isTrialSpecific('numRepeats')) + testCase.verifyTrue(numel(p.Struct.numRepeats)==1) + p.makeGlobal('arrayParam') + expected = magic(testCase.nConditional); + expected = expected(:,1); + testCase.verifyTrue(isequal(p.Struct.arrayParam, expected)) + p.makeGlobal('logicalArrayParam', false) + testCase.verifyEqual(p.Struct.logicalArrayParam, false) + try + p.makeGlobal('numRepeats') + catch ex + testCase.verifyEqual(ex.message, '''numRepeats'' is already global') + end end function test_removeConditions(testCase) + p = testCase.Parameters; + testCase.startMeasuring('removeConditions'); + p.removeConditions([2,4,6]); + testCase.stopMeasuring('removeConditions'); + % Expected result for arrayParam + expected = magic(testCase.nConditional); + expected = expected(:,[1,3,5]); + + testCase.verifyTrue(p.numTrialConditions == 3) + testCase.verifyTrue(isequal(p.Struct.arrayParam, expected)) end function test_description(testCase) + p = testCase.Parameters; + testCase.verifyEqual(p.description('numRepeats'), 'No. of repeats of each condition') + testCase.verifyTrue(isempty(p.description('arrayParam'))) + testCase.verifyTrue(numel(p.description({'numRepeats', 'charParam'}))==2) end function test_toConditionServer(testCase) + % Test behaviour when numRepeats is conditional + p = testCase.Parameters; + testCase.startMeasuring('toConditionServer'); + [cs, globalParams, trialParams] = p.toConditionServer; + testCase.stopMeasuring('toConditionServer'); + testCase.verifyTrue(isa(cs, 'exp.PresetConditionServer')) + testCase.verifyTrue(isstruct(globalParams)) + testCase.verifyTrue(isequal(fieldnames(globalParams), p.GlobalNames)) + testCase.verifyTrue(isequal(size(trialParams), [1 sum(p.Struct.numRepeats)])) + % Verify randomised + noneRandom = magic(testCase.nConditional); + noneRandom = [repmat(noneRandom(:,1),1,p.Struct.numRepeats(1)) ... + repmat(noneRandom(:,2),1,p.Struct.numRepeats(2))]; + result = [trialParams.arrayParam]; + testCase.verifyTrue(~isequal(result(:,1:size(noneRandom,2)), noneRandom), ... + 'Trial conditions likely not randomised by default') + % Verify that numRepeats was removed + testCase.verifyTrue(strcmp('numRepeats', setdiff(p.TrialSpecificNames,fieldnames(trialParams)))) + + % Test behaviour when numRepeats is global + p.makeGlobal('numRepeats', 20) + [~, globalParams, trialParams] = p.toConditionServer; + testCase.verifyTrue(isequal(size(trialParams), [1 20*testCase.nConditional])) + testCase.verifyTrue(strcmp('numRepeats', setdiff(p.GlobalNames,fieldnames(globalParams)))) + + % Test behaviour when randomiseConditions is set + p.set('randomiseConditions', false, ... + 'Flag for whether to randomise trial conditions', 'logical') + [~, ~, trialParams] = p.toConditionServer; + result = [trialParams.arrayParam]; + noneRandom = repmat(magic(testCase.nConditional), 1, 20); + testCase.verifyTrue(isequal(result, noneRandom), ... + 'Trial conditions randomised') + % Test param overide + [~, ~, trialParams] = p.toConditionServer(true); + result = [trialParams.arrayParam]; + testCase.verifyTrue(~isequal(result, noneRandom), ... + 'Expected trial conditions to be randomised') end function test_makeTrialSpecific(testCase) + p = testCase.Parameters; + % Test making simply numerical param trial specific + testCase.startMeasuring('makeTrialSpecific'); + p.makeTrialSpecific('doubleParam') + testCase.stopMeasuring('makeTrialSpecific'); + testCase.verifyTrue(p.isTrialSpecific('doubleParam')) + testCase.verifyTrue(isequal(p.Struct.doubleParam, repmat(3,1,testCase.nConditional))) + testCase.verifyTrue(ismember('doubleParam', p.TrialSpecificNames)) + % Test making char array trial specific + p.makeTrialSpecific('charParam') + testCase.verifyTrue(p.isTrialSpecific('charParam')) + testCase.verifyEqual(numel(p.Struct.charParam), testCase.nConditional) + testCase.verifyTrue(iscell(p.Struct.charParam)) + % Test making string trial specific + p.makeTrialSpecific('strParam') + testCase.verifyTrue(p.isTrialSpecific('strParam')) + testCase.verifyTrue(isequal(size(p.Struct.strParam), [1 6])) + testCase.verifyTrue(isstring(p.Struct.strParam)) + try + p.makeTrialSpecific('numRepeats') + catch ex + testCase.verifyEqual(ex.message, '''numRepeats'' is already trial-specific') + end end function test_set(testCase) + p = testCase.Parameters; + % Test setting simple param with units and description + testCase.startMeasuring('setPar'); + p.set('randomiseConditions', true, ... + 'Flag for whether to randomise trial conditions', 'logical') + testCase.stopMeasuring('setPar'); + testCase.verifyTrue(ismember('randomiseConditions', p.GlobalNames)) + testCase.verifyTrue(p.Struct.randomiseConditions == true) + testCase.verifyTrue(isfield(p.Struct, 'randomiseConditionsDescription')) + testCase.verifyTrue(isfield(p.Struct, 'randomiseConditionsUnits')) + + % Test setting a conditional parameter + p.set('conditionPar', eye(testCase.nConditional)) + testCase.verifyTrue(ismember('conditionPar', p.TrialSpecificNames)) + testCase.verifyTrue(isfield(p.Struct, 'conditionParDescription') ... + && isempty(p.Struct.conditionParDescription)) + testCase.verifyTrue(~isfield(p.Struct, 'conditionParUnits')) + + % Test setting various arrays + p.set('globalPar', true(testCase.nConditional, 1)) + testCase.verifyTrue(ismember('globalPar', p.GlobalNames)) + p.set('globalPar', false(1, testCase.nConditional+1)) + testCase.verifyTrue(ismember('globalPar', p.TrialSpecificNames)) end - function test_ui(testCase) - end +% function test_ui(testCase) +% p = testCase.Parameters; +% end end diff --git a/tests/repelems_test.m b/tests/repelems_test.m new file mode 100644 index 00000000..60c3fc3f --- /dev/null +++ b/tests/repelems_test.m @@ -0,0 +1,20 @@ +%% repelems_test +% Currently outputs are unexpected for matrices and when the size of the +% second input doesn't match the first. This function was designed for use +% by exp.Parameters/toConditionServer + +%% Test simple 1-by-n array +assert(isequal(repelems([0 1 2], [2 1 3]), [0 0 1 2 2 2])) + +%% Test simple 1-by-n array +assert(isequal(repelems([0 1 2], [2 0 3]), [0 0 2 2 2])) + +%% Test function with none-scalar struct +% This is a similar form to how it is used by exp.Parameters +array.field = [1;1]; +array(2).field = [2;2]; +rarr = repelems(array, [2 5]); +assert(isequal([rarr.field], [1 1 2 2 2 2 2; 1 1 2 2 2 2 2])) + +% %% Test n-by-n array +% assert(isequal(repelems([0 1 2; 3 4 5], [2 0 2; 2 0 2]), [0 0 1 2 2 2])) \ No newline at end of file From 57ba45c710f8e39e226e493d2b50613c056485ba Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 13 Mar 2019 14:23:31 +0200 Subject: [PATCH 315/393] Bug fixes for numRepeats behaviour, more tests --- +eui/ConditionPanel.m | 51 ++++---- +eui/FieldPanel.m | 4 +- +eui/ParamEditor.m | 36 ++++-- +exp/Parameters.m | 27 +--- tests/ParamEditorTest.m | 278 +++++++++++++++++++++++++++++++++++++--- tests/ParametersTest.m | 38 ++---- 6 files changed, 323 insertions(+), 111 deletions(-) diff --git a/+eui/ConditionPanel.m b/+eui/ConditionPanel.m index a674f901..74e5dfba 100644 --- a/+eui/ConditionPanel.m +++ b/+eui/ConditionPanel.m @@ -41,7 +41,7 @@ 'MenuSelectedFcn', fcn, 'Checked', 'on', 'Tag', 'randomize button'); obj.ContextMenus(3) = uimenu(c, 'Label', 'Sort by selected column', ... 'MenuSelectedFcn', @(~,~)disp('feature not yet implemented'), ... - 'Tag', 'sort by', 'Enable', 'off'); + 'Tag', 'sort by', 'Enable', 'off'); % TODO Implement sort by column % Create condition table p = uix.Panel('Parent', obj.UIPanel, 'BorderType', 'none'); obj.ConditionTable = uitable('Parent', p,... @@ -118,25 +118,19 @@ function delete(obj) end function onSelect(obj, ~, eventData) - obj.SelectedCells = eventData.Indices; - if size(eventData.Indices, 1) > 0 - % cells selected, enable buttons - set(obj.MakeGlobalButton, 'Enable', 'on'); - set(obj.DeleteConditionButton, 'Enable', 'on'); - set(obj.SetValuesButton, 'Enable', 'on'); - set(obj.ContextMenus(1), 'Enable', 'on'); - set(obj.ContextMenus(3), 'Enable', 'on'); - else - % nothing selected, disable buttons - set(obj.MakeGlobalButton, 'Enable', 'off'); - set(obj.DeleteConditionButton, 'Enable', 'off'); - set(obj.SetValuesButton, 'Enable', 'off'); - set(obj.ContextMenus(1), 'Enable', 'off'); - set(obj.ContextMenus(3), 'Enable', 'off'); - end + % If at least one cell is selected, ensure buttons and menu items are + % enabled, otherwise disable them. + if nargin > 2; obj.SelectedCells = eventData.Indices; end + controls = ... + [obj.MakeGlobalButton, ... + obj.DeleteConditionButton, ... + obj.SetValuesButton, ... + obj.ContextMenus([1,3])]; + set(controls, 'Enable', iff(size(obj.SelectedCells, 1) > 0, 'on', 'off')); end function makeGlobal(obj) + % FIXME Don't allow only numRepeats to remain if isempty(obj.SelectedCells) disp('nothing selected') return @@ -146,6 +140,11 @@ function makeGlobal(obj) rows = num2cell(obj.SelectedCells(iu,1)); %get rows of unique selected cols PE = obj.ParamEditor; cellfun(@PE.globaliseParamAtCell, names, rows); + % If only numRepeats remains, globalise it + if isequal(PE.Parameters.TrialSpecificNames, {'numRepeats'}) + PE.Parameters.Struct.numRepeats(1,1) = sum(PE.Parameters.Struct.numRepeats); + PE.globaliseParamAtCell('numRepeats', 1) + end end function deleteSelectedConditions(obj) @@ -158,7 +157,7 @@ function deleteSelectedConditions(obj) % See also EXP.PARAMETERS, GLOBALISESELECTEDPARAMETERS rows = unique(obj.SelectedCells(:,1)); names = obj.ConditionTable.ColumnName; - numConditions = size(obj.ConditionTable.Data,2); + numConditions = size(obj.ConditionTable.Data,1); % If the number of remaining conditions is 1 or less... if numConditions-length(rows) <= 1 remainingIdx = find(all(1:numConditions~=rows,1)); @@ -169,10 +168,10 @@ function deleteSelectedConditions(obj) %... globalize them obj.makeGlobal; else % Otherwise delete the selected conditions as usual - obj.ParamEditor.Parameters.removeConditions(rows); %FIXME: Should be in ParamEditor + obj.ParamEditor.Parameters.removeConditions(rows); end - % Refresh the table of conditions FIXME: Should be in ParamEditor - obj.ParamEditor.fillConditionTable(); + % Refresh the table of conditions + obj.fillConditionTable(); end function setSelectedValues(obj) % Set multiple fields in conditional table @@ -244,11 +243,11 @@ function fillConditionTable(obj) obj.ButtonPanel.Visible = 'on'; obj.UIPanel.Visible = 'on'; obj.ParamEditor.Parent.Widths = [-1, -1]; - data = reshape(struct2cell(trialParams), numel(titles), [])'; - data = mapToCell(@(e) obj.ParamEditor.paramValue2Control(e), data); - set(obj.ConditionTable, 'ColumnName', titles, 'Data', data,... - 'ColumnEditable', true(1, numel(titles))); end + data = reshape(struct2cell(trialParams), numel(titles), [])'; + data = mapToCell(@(e) obj.ParamEditor.paramValue2Control(e), data); + set(obj.ConditionTable, 'ColumnName', titles, 'Data', data,... + 'ColumnEditable', true(1, numel(titles))); end function newCondition(obj) @@ -256,7 +255,7 @@ function newCondition(obj) PE = obj.ParamEditor; cellfun(@PE.addEmptyConditionToParam, ... PE.Parameters.TrialSpecificNames); - obj.ParamEditor.fillConditionTable(); + obj.fillConditionTable(); end end diff --git a/+eui/FieldPanel.m b/+eui/FieldPanel.m index 5e6c0558..f35525af 100644 --- a/+eui/FieldPanel.m +++ b/+eui/FieldPanel.m @@ -60,11 +60,11 @@ function onEdit(obj, src, id) switch get(src, 'style') case 'checkbox' newValue = logical(get(src, 'value')); - obj.ParamEditor.updateGlobal(id, newValue); + obj.ParamEditor.update(id, newValue); case 'edit' % if successful update the control with default formatting and % modified colour - newValue = obj.ParamEditor.updateGlobal(id, get(src, 'string')); + newValue = obj.ParamEditor.update(id, get(src, 'string')); set(src, 'String', obj.ParamEditor.paramValue2Control(newValue)); end changed = strcmp(id,{obj.Labels.String}); diff --git a/+eui/ParamEditor.m b/+eui/ParamEditor.m index e5e614b6..dbe625a8 100644 --- a/+eui/ParamEditor.m +++ b/+eui/ParamEditor.m @@ -60,19 +60,21 @@ function delete(obj) end function set.Enable(obj, value) + % Disable all UI elements + % Render the GUI view-only by disabling all UI elements. Used for + % viewing parameters during an active experiment when the parameters + % can no longer be adjusted. + % See also EUI.EXPPANEL, EUI.CONDITIONPANEL/ONSELECT cUI = obj.ConditionalUI; + contextMenus = [cUI.ContextMenus obj.GlobalUI.ContextMenu.Children]'; parent = obj.Parent; % FIXME: use tags instead? if value == true - arrayfun(@(prop) set(prop, 'Enable', 'on'), findobj(parent,'Enable','off')); - if isempty(cUI.SelectedCells) - set(cUI.MakeGlobalButton, 'Enable', 'off'); - set(cUI.DeleteConditionButton, 'Enable', 'off'); - set(cUI.SetValuesButton, 'Enable', 'off'); - end - obj.Enable = true; + arrayfun(@(prop) set(prop, 'Enable', 'on'), ... + [contextMenus; findobj(parent,'Enable','off')]); + cUI.onSelect() % Re-disable buttons if no cells were selected else - arrayfun(@(prop) set(prop, 'Enable', 'off'), findobj(parent,'Enable','on')); - obj.Enable = false; + arrayfun(@(prop) set(prop, 'Enable', 'off'), ... + [contextMenus; findobj(parent,'Enable','on')]); end end @@ -117,7 +119,7 @@ function setRandomized(obj, value) description = 'Whether to randomise the conditional paramters or present them in order'; obj.Parameters.set('randomiseConditions', false, description, 'logical') elseif ismember('randomiseConditions', obj.Parameters.Names) - obj.updateGlobal('randomiseConditions', logical(value)); + obj.update('randomiseConditions', logical(value)); end menu = obj.ConditionalUI.ContextMenus(2); if value == false @@ -163,7 +165,7 @@ function addEmptyConditionToParam(obj, name) obj.Parameters.Struct.(name) = cat(2, obj.Parameters.Struct.(name), newValue); end - function newValue = updateGlobal(obj, name, value, row) + function newValue = update(obj, name, value, row) if nargin < 4; row = 1; end currValue = obj.Parameters.Struct.(name)(:,row); if iscell(currValue) @@ -188,8 +190,8 @@ function globaliseParamAtCell(obj, name, row) obj.ConditionalUI.fillConditionTable; % Add new global parameter to field panel if islogical(value) % If parameter is logical, make checkbox - ctrl = uicontrol('Parent', obj.GlobalUI.UIPanel, 'Style', 'checkbox', ... - 'Value', value, 'BackgroundColor', 'white'); + ctrl = uicontrol('Parent', obj.GlobalUI.UIPanel, ... + 'Style', 'checkbox', 'Value', value); addField(obj.GlobalUI, name, ctrl); else [~, ctrl] = addField(obj.GlobalUI, name); @@ -306,7 +308,8 @@ function onResize(obj) % Convert the values displayed in the UI ('control values') to % parameter values. String representations of numrical arrays and % functions are converted back to their 'native' classes. - if nargin < 4 + % TODO Implement string support + if nargin < 3 allowTypeChange = false; end switch class(currParam) @@ -340,6 +343,11 @@ function onResize(obj) error('Cannot update unimplemented type ''%s''', class(currParam)); end end + % If necessary, assert that type didn't change + if ~allowTypeChange + assert(strcmp(class(currParam), class(data)), ... + sprintf('Type change from %s to %s not allowed', class(currParam), class(data))) + end end end diff --git a/+exp/Parameters.m b/+exp/Parameters.m index 64a81016..80579cd2 100644 --- a/+exp/Parameters.m +++ b/+exp/Parameters.m @@ -220,7 +220,9 @@ function makeGlobal(obj, name, newValue) % We also remove the numRepeats parameter from the globalParams % since the trial elements are literally repeated now nreps = globalParams.numRepeats; - trialParams = repmat(trialParams, 1, nreps); + trialParams = iff(isempty(trialParams), ... + @()repelems(struct, nreps),... + @()repmat(trialParams, 1, nreps)); globalParams = rmfield(globalParams, 'numRepeats'); end if randomOrder @@ -229,29 +231,6 @@ function makeGlobal(obj, name, newValue) cs = exp.PresetConditionServer(globalParams, trialParams); end - function [ctrl, label] = ui(obj, name, parent) - % FIXME method not used at all(?) - % Seems to reuse code put into indivTitle method - % Doesn't return uicontrols if units aren't '�' or 's' - ctrl = []; - label = []; - unitField = [name 'Units']; - if isfield(obj.Params, unitField) - value = obj.Params.(name); - units = obj.Params.(unitField); - words = lower(regexprep(name, '([a-z])([A-Z])', '$1 $2')); - words(1) = upper(words(1)); - title = sprintf('%s (%s)', words, units); - description = obj.Params.([name 'Description']); - if any(strcmp(units, {'�', 's'})) - label = uicontrol('Parent', parent, 'Style', 'text', 'String', title); - ctrl = uicontrol('Parent', parent,... - 'Style', 'edit',... - 'String', num2str(value),... - 'TooltipString', description); - end - end - end end methods (Access = protected) diff --git a/tests/ParamEditorTest.m b/tests/ParamEditorTest.m index 0ed19edf..9c22e3ff 100644 --- a/tests/ParamEditorTest.m +++ b/tests/ParamEditorTest.m @@ -7,6 +7,8 @@ ParamEditor % Figure handle for ParamEditor Figure + % Handle to trial conditions UI Table + Table end properties %(MethodSetupParameter) @@ -18,8 +20,7 @@ function setup(testCase) % Hide figures and add teardown function to restore settings testCase.FigureVisibleDefault = get(0,'DefaultFigureVisible'); % set(0,'DefaultFigureVisible','off'); - testCase.addTearDown(@set, 0, 'DefaultFigureVisible', testCase.FigureVisibleDefault); - testCase.addTearDown(@close, testCase.Figure); + testCase.addTeardown(@set, 0, 'DefaultFigureVisible', testCase.FigureVisibleDefault); % Loads validation data % Graph data is a cell array where each element is the graph number @@ -30,11 +31,14 @@ function setup(testCase) % Check paths file assert(endsWith(which('dat.paths'), fullfile('tests','+dat','paths.m'))); % Create stand-alone panel - testCase.startMeasuring(); testCase.ParamEditor = eui.ParamEditor; - testCase.stopMeasuring(); testCase.Figure = gcf(); + testCase.addTeardown(@close, testCase.Figure); testCase.fatalAssertTrue(isa(testCase.ParamEditor, 'eui.ParamEditor')) + % Find Condition Table + testCase.Table = findobj(testCase.Figure, '-property', 'ColumnName'); + testCase.fatalAssertTrue(isa(testCase.Table, 'matlab.ui.control.Table'), ... + 'Failed to find handle to condition table') end end @@ -45,19 +49,15 @@ function buildParams(testCase) % previous test don't persist PE = testCase.ParamEditor; pars = exp.Parameters(testCase.Parameters); - testCase.startMeasuring(); PE.buildUI(pars); - testCase.stopMeasuring(); % Number of global parameters: find all text labels nGlobalLabels = numel(findobj(testCase.Figure, 'Style', 'text')); nGlobalInput = numel(findobj(testCase.Figure, 'Style', 'checkbox', '-or', 'Style', 'edit')); - % Find Condition Table - conditionTable = findobj(testCase.Figure, '-property', 'ColumnName'); % Ensure all global params have UI input and label testCase.fatalAssertTrue(nGlobalLabels == numel(PE.Parameters.GlobalNames)) testCase.fatalAssertTrue(nGlobalInput == numel(PE.Parameters.GlobalNames)) % Ensure all conditional params have column in table - testCase.fatalAssertTrue(isequal(size(conditionTable.Data), ... + testCase.fatalAssertTrue(isequal(size(testCase.Table.Data), ... [PE.Parameters.numTrialConditions, numel(PE.Parameters.TrialSpecificNames)])) end end @@ -74,34 +74,30 @@ function test_makeConditional(testCase) gInputs = @()findobj(testCase.Figure, 'Style', 'checkbox', '-or', 'Style', 'edit'); nGlobalLabels = numel(gLabels()); nGlobalInputs = numel(gInputs()); - % Find Condition Table - conditionTable = findobj(testCase.Figure, '-property', 'ColumnName'); - tableSz = size(conditionTable.Data); + tableSz = size(testCase.Table.Data); % Retrieve context menu function handle c = findobj(testCase.Figure, 'Text', 'Make Conditional'); % Set the focused object to one of the parameter labels set(testCase.Figure, 'CurrentObject', ... findobj(testCase.Figure, 'String', 'rewardVolume')) - testCase.startMeasuring(); testCase.verifyWarningFree(c.MenuSelectedFcn, ... 'Problem making parameter conditional'); - testCase.stopMeasuring(); % Verify change in UI elements testCase.verifyTrue(numel(gLabels()) == nGlobalLabels-1, ... 'Global parameter UI element not removed') testCase.verifyTrue(numel(gInputs()) == nGlobalInputs-1, ... 'Global parameter UI element not removed') - testCase.verifyTrue(size(conditionTable.Data,2) == tableSz(2)+1, ... + testCase.verifyTrue(size(testCase.Table.Data,2) == tableSz(2)+1, ... 'Incorrect condition table') % Verify change in Parameters object for global testCase.assertTrue(numel(gLabels()) == numel(PE.Parameters.GlobalNames)) % Verify change in Parameters object for conditional - testCase.assertTrue(isequal(size(conditionTable.Data), ... + testCase.assertTrue(isequal(size(testCase.Table.Data), ... [PE.Parameters.numTrialConditions, numel(PE.Parameters.TrialSpecificNames)])) % Verify table values are correct - testCase.verifyTrue(isequal(conditionTable.Data(:,1), repmat({'3'}, ... - size(conditionTable.Data,1), 1)), 'Unexpected table values') + testCase.verifyTrue(isequal(testCase.Table.Data(:,1), repmat({'3'}, ... + size(testCase.Table.Data,1), 1)), 'Unexpected table values') % Test behaviour when all params made conditional for param = gLabels()' @@ -113,21 +109,267 @@ function test_makeConditional(testCase) end function test_paramValue2Control(testCase) + % Test paramValue2Control and controlValue2Param + PE = testCase.ParamEditor; + % Test function handle + testCase.verifyEqual(PE.paramValue2Control(@nop), 'nop') + testCase.verifyEqual(PE.controlValue2Param(@nop, 'identity'), @identity) + + % Test logical array + testCase.verifyEqual(PE.paramValue2Control(true(1,2)), true(1,2)) + testCase.verifyEqual(PE.controlValue2Param(true(1,2), false(1,2)), false(1,2)) + testCase.verifyEqual(PE.controlValue2Param(true(1,2), zeros(1,2)), false(1,2)) + + % Test char data + testCase.verifyEqual(PE.paramValue2Control('hello'), 'hello') + testCase.verifyEqual(PE.controlValue2Param('hello', 'goodbye'), 'goodbye') + + % Test type changes + testCase.verifyEqual(PE.controlValue2Param('hello', 12, true), 12) + try + PE.controlValue2Param('hello', 12); + testCase.verifyTrue(false, 'Failed to throw error on type change') + catch ex + testCase.verifyEqual(ex.message, 'Type change from char to double not allowed') + end + + % Test string data + % TODO Outcome will change in near future + testCase.verifyEqual(PE.paramValue2Control("hello"), 'hello') +% testCase.verifyEqual(PE.controlValue2Param("hello", "Goodbye"), "Goodbye") + + % Test numeric data + testCase.verifyEqual(PE.paramValue2Control(pi), '3.1416') + testCase.verifyEqual(PE.paramValue2Control([14, 2, 6]), '14, 2, 6') + testCase.verifyEqual(PE.paramValue2Control([1, 3; 2, 4]), '1, 2, 3, 4') + testCase.verifyEqual(PE.paramValue2Control({'1', '2', '3'}), '1, 2, 3') + + testCase.verifyEqual(PE.controlValue2Param([1 2 3], '4, 5, 6'), [4;5;6]) + testCase.verifyEqual(PE.controlValue2Param([1 2 3], '4, 5, 6', true), [4;5;6]) + testCase.verifyEqual(PE.controlValue2Param([1 2 3], {'4', '5', '6'}, true), {'4', '5', '6'}) + testCase.verifyEqual(PE.controlValue2Param({'1' '2'}, '4,5 6'), {'4'; '5'; '6'}) end function test_newCondition(testCase) + PE = testCase.ParamEditor; + + tableRows = size(testCase.Table.Data, 1); + + % Make function handle param conditional to test default value + % Set the focused object to one of the parameter labels + set(testCase.Figure, 'CurrentObject', ... + findobj(testCase.Figure, 'String', 'experimentFun')) + feval(pick(findobj(testCase.Figure, 'Text', 'Make Conditional'), 'MenuSelectedFcn')) + + % Retrieve function handle for new condition + fn = pick(findobj(testCase.Figure, 'String', 'New condition'), 'Callback'); + testCase.verifyWarningFree(fn, 'Warning encountered adding trial condition') + + % Verify change in table data + testCase.verifyEqual(size(testCase.Table.Data, 1), tableRows+1, ... + 'Unexpected number of trial conditions') + + % Verify change in Parameters object for conditional + testCase.assertEqual(size(testCase.Table.Data), ... + [PE.Parameters.numTrialConditions, numel(PE.Parameters.TrialSpecificNames)]) + + % Verify default conditions + [~, trialParams] = PE.Parameters.assortForExperiment; + testCase.verifyTrue(isequal(struct2cell(trialParams(end)), ... + {@nop; zeros(2,1); zeros(3,1); false; 0})) end function test_deleteCondition(testCase) + PE = testCase.ParamEditor; + + tableRows = size(testCase.Table.Data, 1); + % Select some cells to delete + event.Indices = [(1:5)' ones(5,1)]; + selection_fn = testCase.Table.CellSelectionCallback; + selection_fn([],event) + + % Retrieve function handle for new condition + callback_fn = pick(findobj(testCase.Figure, 'String', 'Delete condition'), 'Callback'); + testCase.verifyWarningFree(callback_fn, 'Warning encountered deleting trial conditions') + + % Verify change in table data + testCase.verifyEqual(size(testCase.Table.Data, 1), tableRows-5, ... + 'Unexpected number of trial conditions') + + % Verify change in Parameters object for conditional + testCase.assertEqual(size(testCase.Table.Data), ... + [PE.Parameters.numTrialConditions, numel(PE.Parameters.TrialSpecificNames)]) + + % Test behaviour when < 2 conditions remain + event.Indices = [(1:PE.Parameters.numTrialConditions-1)' ... + ones(PE.Parameters.numTrialConditions-1,1)]; + selection_fn([],event) + testCase.verifyWarningFree(callback_fn, 'Warning encountered deleting trial conditions') + + % Verify change in table data + testCase.verifyEmpty(testCase.Table.Data, ... + 'Unexpected number of trial conditions') + % Verify change in Parameters object for conditional + testCase.verifyEqual(size(testCase.Table.Data), ... + [PE.Parameters.numTrialConditions, numel(PE.Parameters.TrialSpecificNames)]) end function test_setValues(testCase) + % TODO Add test for the set values button. For now let's fail this + PE = testCase.ParamEditor; + testCase.assertTrue(false, 'Test not implemented') end function test_globaliseParam(testCase) + PE = testCase.ParamEditor; + + tableCols = size(testCase.Table.Data, 2); + % Globalize one param + event.Indices = [1, 2]; + selection_fn = testCase.Table.CellSelectionCallback; + selection_fn([],event) + + % Retrieve function handle for new condition + callback_fn = pick(findobj(testCase.Figure, 'String', 'Globalise parameter'), 'Callback'); + testCase.verifyWarningFree(callback_fn, 'Warning encountered globalising params') + + % Verify change in table data + testCase.verifyEqual(size(testCase.Table.Data,2), tableCols-1, ... + 'Unexpected number of conditional parameters') + % Verify change in Parameters object for conditional + testCase.verifyEqual(size(testCase.Table.Data), ... + [PE.Parameters.numTrialConditions, numel(PE.Parameters.TrialSpecificNames)]) + + % Test removal of all but numRepeats: numRepeats should automatically + % globalize + n = numel(PE.Parameters.TrialSpecificNames)-1; + event.Indices = [ones(n,1), (1:n)']; + numRepeatsTotal = sum(PE.Parameters.Struct.numRepeats); + selection_fn([],event) + testCase.verifyWarningFree(callback_fn, 'Warning encountered globalising params') + + % Verify numRepeats is global + testCase.verifyTrue(~PE.Parameters.isTrialSpecific('numRepeats'), ... + 'numRepeats not globalized') + % Verify total number of repeats conserved + testCase.verifyEqual(PE.Parameters.Struct.numRepeats, numRepeatsTotal,... + 'Unexpected numRepeats value') + % Verify change in table data + testCase.verifyEmpty(testCase.Table.Data, 'Unexpected number of trial conditions') + % Verify change in Parameters object for conditional + testCase.verifyEqual(size(testCase.Table.Data), ... + [PE.Parameters.numTrialConditions, numel(PE.Parameters.TrialSpecificNames)]) + + % Test removal of all but one param that isn't numRepeats + % Reset table + PE.buildUI(exp.Parameters(testCase.Parameters)) + % Globalize all but one param + n = numel(PE.Parameters.TrialSpecificNames); + event.Indices = [ones(n-1,1), (2:n)']; + selection_fn([],event) + testCase.verifyWarningFree(callback_fn, 'Warning encountered globalising params') + + % Verify change in table data + testCase.verifyEqual(size(testCase.Table.Data,2), 1, ... + 'Unexpected number of conditional parameters') + % Verify change in Parameters object for conditional + testCase.verifyEqual(size(testCase.Table.Data), ... + [PE.Parameters.numTrialConditions, numel(PE.Parameters.TrialSpecificNames)]) end function test_paramEdits(testCase) + % Test basic edits to Global UI controls and Condition table + PE = testCase.ParamEditor; + + % Retreive all global parameters labels and input controls + gLabels = findobj(testCase.Figure, 'Style', 'text'); + gInputs = findobj(testCase.Figure, 'Style', 'checkbox', '-or', 'Style', 'edit'); + + % Test editing global param, 'edit' UI + idx = find(strcmp({gInputs.Style}, 'edit'), 1); + % Change string + gInputs(idx).String = '666'; + % Trigger callback + callback_fcn = gInputs(idx).Callback; + callback_fcn(gInputs(idx)); + + % Verify change in ui string + testCase.verifyEqual(gInputs(idx).String, '666') + % Verify change in label color + testCase.verifyEqual(gLabels(idx).ForegroundColor, [1 0 0], ... + 'Unexpected label colour') + % Verify change in underlying param struct + par = strcmpi(PE.Parameters.GlobalNames, strrep(gLabels(idx).String, ' ', '')); + testCase.verifyEqual(PE.Parameters.Struct.(PE.Parameters.GlobalNames{par}), 666, ... + 'UI edit failed to update parameters struct') + + % Test editing global param, 'checkbox' UI + idx = find(strcmp({gInputs.Style}, 'checkbox'), 1); + % Change value + gInputs(idx).Value = ~gInputs(idx).Value; + % Trigger callback + callback_fcn = gInputs(idx).Callback; + callback_fcn(gInputs(idx)); + + % Verify change in label color + testCase.verifyEqual(gLabels(idx).ForegroundColor, [1 0 0], ... + 'Unexpected label colour') + % Verify change in underlying param struct + par = strcmpi(PE.Parameters.GlobalNames, strrep(gLabels(idx).String, ' ', '')); + testCase.verifyEqual(... + PE.Parameters.Struct.(PE.Parameters.GlobalNames{par}), gInputs(idx).Value==true, ... + 'UI checkbox failed to update parameters struct') + + % Test edits to conditions table + callback_fcn = testCase.Table.CellEditCallback; + event.Indices = [1, 1]; + event.NewData = '0,5'; + callback_fcn(testCase.Table, event) + + % Verify change to table value + testCase.verifyEqual(testCase.Table.Data{1,1}, '0, 5', ... + 'Unexpected table data') + % Verify change in underlying param struct + value = PE.Parameters.Struct.(PE.Parameters.TrialSpecificNames{1}); + testCase.verifyEqual(value(:,1), [0;5], ... + 'Table UI failed to update parameters struct') + end + + function test_interactivity(testCase) + PE = testCase.ParamEditor; + % Test buttons grey out when nothing selected + % Deselect table rows + callback_fcn = testCase.Table.CellSelectionCallback; + event.Indices = []; % Nothing selected + callback_fcn([], event) % Run selection callback (onSelect) + + % Find all disabled controls + disabled = findobj(testCase.Figure, 'Enable', 'off'); + % Verify buttons greyed out + testCase.verifyEqual(numel(disabled), 5, ... + 'Unexpected number of disabled ui elements') + + % Re-select something + event.Indices = [1, 1]; % Nothing selected + callback_fcn([], event) % Run selection callback (onSelect) + % Find all disabled controls + disabled = findobj(testCase.Figure, 'Enable', 'off'); + % Verify buttons greyed out + testCase.verifyEmpty(disabled, 'Unexpected number of disabled ui elements') + + % Test disabling param editor altogether + PE.Enable = false; + % Find all enabled controls + enabled = findobj(testCase.Figure, 'Enable', 'on'); + % Verify buttons greyed out + testCase.verifyEmpty(enabled, 'Not all ui elements disabled') + + % Re-enable param editor + PE.Enable = true; + % Find all disabled controls + disabled = findobj(testCase.Figure, 'Enable', 'off'); + % Verify buttons greyed out + testCase.verifyEmpty(disabled, 'Not all ui elements enabled') end end diff --git a/tests/ParametersTest.m b/tests/ParametersTest.m index 677a7046..83684645 100644 --- a/tests/ParametersTest.m +++ b/tests/ParametersTest.m @@ -3,10 +3,8 @@ properties % Parameters object Parameters + % Number of trial conditions (rows in table) nConditional = 6 - end - - properties %(MethodSetupParameter) % Parameters structure ParamStruct end @@ -38,9 +36,7 @@ function setupClass(testCase) % Check paths file assert(endsWith(which('dat.paths'), fullfile('tests','+dat','paths.m'))); % Create stand-alone panel - testCase.startMeasuring('gen_empty_obj'); testCase.Parameters = exp.Parameters(); - testCase.stopMeasuring('gen_empty_obj'); testCase.fatalAssertTrue(isa(testCase.Parameters, 'exp.Parameters')) end end @@ -49,9 +45,7 @@ function setupClass(testCase) function buildParams(testCase) % Re-load the param structure before each test so that changes in % previous test don't persist - testCase.startMeasuring('setStruct'); testCase.Parameters.Struct = testCase.ParamStruct; - testCase.stopMeasuring('setStruct'); testCase.fatalAssertTrue(length(testCase.Parameters.TrialSpecificNames) == 3) testCase.fatalAssertTrue(length(testCase.Parameters.GlobalNames) == 5) testCase.fatalAssertTrue(length(testCase.Parameters.Names) == 8) @@ -80,18 +74,14 @@ function test_title(testCase) % Test single param title testCase.verifyEqual(p.title('numRepeats'), 'Num repeats') % Test array of param titles, including those with units - testCase.startMeasuring('title'); str = p.title({'numRepeats', 'charParam', 'arrayParam', 'logicalParam'}); - testCase.stopMeasuring('title'); expected = {'Num repeats', 'Char param', 'Array param (mW)', 'Logical param'}; testCase.verifyTrue(isequal(str, expected)) end function test_assortForExperiment(testCase) p = testCase.Parameters; - testCase.startMeasuring('assort'); [globalParams, trialParams] = testCase.verifyWarningFree(@()p.assortForExperiment); - testCase.stopMeasuring('assort'); testCase.verifyTrue(isstruct(globalParams)) testCase.verifyTrue(isequal(fieldnames(globalParams), p.GlobalNames)) testCase.verifyTrue(isequal(size(trialParams), [1 testCase.nConditional])) @@ -100,9 +90,7 @@ function test_assortForExperiment(testCase) function test_makeGlobal(testCase) p = testCase.Parameters; - testCase.startMeasuring('makeGlobal'); p.makeGlobal('numRepeats') - testCase.stopMeasuring('makeGlobal'); testCase.verifyTrue(~p.isTrialSpecific('numRepeats')) testCase.verifyTrue(numel(p.Struct.numRepeats)==1) p.makeGlobal('arrayParam') @@ -113,6 +101,7 @@ function test_makeGlobal(testCase) testCase.verifyEqual(p.Struct.logicalArrayParam, false) try p.makeGlobal('numRepeats') + testCase.verifyTrue(false, 'Failed to throw error') catch ex testCase.verifyEqual(ex.message, '''numRepeats'' is already global') end @@ -120,9 +109,7 @@ function test_makeGlobal(testCase) function test_removeConditions(testCase) p = testCase.Parameters; - testCase.startMeasuring('removeConditions'); p.removeConditions([2,4,6]); - testCase.stopMeasuring('removeConditions'); % Expected result for arrayParam expected = magic(testCase.nConditional); expected = expected(:,[1,3,5]); @@ -134,16 +121,14 @@ function test_removeConditions(testCase) function test_description(testCase) p = testCase.Parameters; testCase.verifyEqual(p.description('numRepeats'), 'No. of repeats of each condition') - testCase.verifyTrue(isempty(p.description('arrayParam'))) + testCase.verifyEmpty(p.description('arrayParam')) testCase.verifyTrue(numel(p.description({'numRepeats', 'charParam'}))==2) end function test_toConditionServer(testCase) % Test behaviour when numRepeats is conditional p = testCase.Parameters; - testCase.startMeasuring('toConditionServer'); [cs, globalParams, trialParams] = p.toConditionServer; - testCase.stopMeasuring('toConditionServer'); testCase.verifyTrue(isa(cs, 'exp.PresetConditionServer')) testCase.verifyTrue(isstruct(globalParams)) testCase.verifyTrue(isequal(fieldnames(globalParams), p.GlobalNames)) @@ -177,14 +162,19 @@ function test_toConditionServer(testCase) result = [trialParams.arrayParam]; testCase.verifyTrue(~isequal(result, noneRandom), ... 'Expected trial conditions to be randomised') + + % Test behaviour when trialParams is empty + names = p.TrialSpecificNames; + for i = 1:length(names); p.makeGlobal(names{i}); end + [~, ~, trialParams] = p.toConditionServer(true); + testCase.assertEmpty(fieldnames(trialParams)) + testCase.verifyTrue(~isempty(trialParams), 'Trial conditions empty') end function test_makeTrialSpecific(testCase) p = testCase.Parameters; % Test making simply numerical param trial specific - testCase.startMeasuring('makeTrialSpecific'); p.makeTrialSpecific('doubleParam') - testCase.stopMeasuring('makeTrialSpecific'); testCase.verifyTrue(p.isTrialSpecific('doubleParam')) testCase.verifyTrue(isequal(p.Struct.doubleParam, repmat(3,1,testCase.nConditional))) testCase.verifyTrue(ismember('doubleParam', p.TrialSpecificNames)) @@ -200,6 +190,7 @@ function test_makeTrialSpecific(testCase) testCase.verifyTrue(isstring(p.Struct.strParam)) try p.makeTrialSpecific('numRepeats') + testCase.verifyTrue(false, 'Failed to throw error') catch ex testCase.verifyEqual(ex.message, '''numRepeats'' is already trial-specific') end @@ -208,10 +199,8 @@ function test_makeTrialSpecific(testCase) function test_set(testCase) p = testCase.Parameters; % Test setting simple param with units and description - testCase.startMeasuring('setPar'); p.set('randomiseConditions', true, ... 'Flag for whether to randomise trial conditions', 'logical') - testCase.stopMeasuring('setPar'); testCase.verifyTrue(ismember('randomiseConditions', p.GlobalNames)) testCase.verifyTrue(p.Struct.randomiseConditions == true) testCase.verifyTrue(isfield(p.Struct, 'randomiseConditionsDescription')) @@ -230,11 +219,6 @@ function test_set(testCase) p.set('globalPar', false(1, testCase.nConditional+1)) testCase.verifyTrue(ismember('globalPar', p.TrialSpecificNames)) end - -% function test_ui(testCase) -% p = testCase.Parameters; -% end - end end \ No newline at end of file From 335642a3415ebfc9f426ae65d69556b2219ec65f Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 13 Mar 2019 16:29:19 +0200 Subject: [PATCH 316/393] Better coverage of Changed event --- +eui/ConditionPanel.m | 1 + +eui/FieldPanel.m | 29 +++++++++++++++---- +eui/MControl.m | 3 ++ +eui/ParamEditor.m | 58 ++++++++++++++++++++++++++++++++++--- tests/ParamEditorTest.m | 64 +++++++++++++++++++++++++++++++++++------ 5 files changed, 138 insertions(+), 17 deletions(-) diff --git a/+eui/ConditionPanel.m b/+eui/ConditionPanel.m index 74e5dfba..91f24140 100644 --- a/+eui/ConditionPanel.m +++ b/+eui/ConditionPanel.m @@ -169,6 +169,7 @@ function deleteSelectedConditions(obj) obj.makeGlobal; else % Otherwise delete the selected conditions as usual obj.ParamEditor.Parameters.removeConditions(rows); + notify(obj.ParamEditor, 'Changed') end % Refresh the table of conditions obj.fillConditionTable(); diff --git a/+eui/FieldPanel.m b/+eui/FieldPanel.m index f35525af..b4e59719 100644 --- a/+eui/FieldPanel.m +++ b/+eui/FieldPanel.m @@ -45,7 +45,9 @@ props.HorizontalAlignment = 'left'; props.UIContextMenu = obj.ContextMenu; props.Parent = obj.UIPanel; - label = uicontrol('Style', 'text', 'String', name, props); + props.Tag = name; + title = obj.ParamEditor.Parameters.title(name); + label = uicontrol('Style', 'text', 'String', title, props); if nargin < 3 ctrl = uicontrol('Style', 'edit', props); end @@ -56,6 +58,14 @@ end function onEdit(obj, src, id) + % ONEDIT Callback for edits to field controls + % Updates the underlying parameter struct, changes the UI + % value/string and changes the label colour to red. The src object + % should be the edit or checkbox ui control that has been edited, + % and id is the unformatted parameter name (stored in the Tag + % property of the label and control elements). + % + % See also ADDFIELD, EUI.PARAMEDITOR/UPDATE disp(id); switch get(src, 'style') case 'checkbox' @@ -67,11 +77,14 @@ function onEdit(obj, src, id) newValue = obj.ParamEditor.update(id, get(src, 'string')); set(src, 'String', obj.ParamEditor.paramValue2Control(newValue)); end - changed = strcmp(id,{obj.Labels.String}); + changed = strcmp(src.Tag,{obj.Labels.Tag}); obj.Labels(changed).ForegroundColor = [1 0 0]; end function clear(obj, idx) + % CLEAR Delete a parameter field + % Deletes the label and control elements at index idx. If no index + % given, all controls are deleted. if nargin == 1 idx = true(size(obj.Labels)); end @@ -99,18 +112,24 @@ function makeConditional(obj, name) selected = obj.ParamEditor.getSelected(); if isa(selected, 'matlab.ui.control.UIControl') && ... strcmp(selected.Style, 'text') - name = selected.String; + name = selected.Tag; else % Assume control - name = obj.Labels([obj.Controls]==selected).String; + name = obj.Labels([obj.Controls]==selected).Tag; end end - idx = strcmp(name,{obj.Labels.String}); + idx = strcmp(name,{obj.Labels.Tag}); assert(~ismember(name, {'randomiseConditions'}), ... '%s can not be made a conditional parameter', name) obj.clear(idx); + % FIXME The below code could be in a makeConditional method of + % eui.ParamEditor, thus more clearly separating class functionality: + % Editing the exp.Parameters object directly should only be done by + % ParamEditor. This would also make subclassing these panel classes + % more straightforward obj.ParamEditor.Parameters.makeTrialSpecific(name); obj.ParamEditor.ConditionalUI.fillConditionTable(); + notify(obj.ParamEditor, 'Changed'); obj.onResize; end diff --git a/+eui/MControl.m b/+eui/MControl.m index d3032e83..eb0b15aa 100644 --- a/+eui/MControl.m +++ b/+eui/MControl.m @@ -321,6 +321,9 @@ function loadParamProfile(obj, profile) end function paramChanged(obj) + % PARAMCHANGED Indicate to user that parameters have been updated + % Changes the label above the ParamEditor indicating that the + % parameters have been edited s = get(obj.ParamProfileLabel, 'String'); if ~strEndsWith(s, '[EDITED]') set(obj.ParamProfileLabel, 'String', [s ' ' '[EDITED]'], 'ForegroundColor', [1 0 0]); diff --git a/+eui/ParamEditor.m b/+eui/ParamEditor.m index dbe625a8..9168b13a 100644 --- a/+eui/ParamEditor.m +++ b/+eui/ParamEditor.m @@ -1,30 +1,54 @@ classdef ParamEditor < handle - %UNTITLED2 Summary of this class goes here - % ParamEditor deals with setting the paramters via a GUI + %PARAMEDITOR GUI for visualizing and editing experiment parameters + % ParamEditor deals with setting the paramters via a GUI. In general + % this class is involved in constructing a UI comprising a Global UI + % panel, handled by the EUI.FIELDPANEL class, and a Condition table, + % handled by the EUI.CONDITIONPANEL class. It is also responsible for + % updating the underlying EXP.PARAMETERS object and notifying + % downstream listeners of parameter changes. + % + % See also EUI.FIELDPANEL, EUI.CONDITIONPANEL properties + % An exp.Parameters object, which keeps track of all parameter changes Parameters end properties (Access = {?eui.ConditionPanel, ?eui.FieldPanel}) - UIPanel + % Handle to the EUI.FIELDPANEL object, which manages the display of the + % Global parameters GlobalUI + % Handle to the EUI.CONDITIONPANEL object, which manages the display of + % the trial conditions within a ui table ConditionalUI + % Handle to the parent container for the ParamEditor. If constructor + % called with no parant input, then this will be a figure handle, the + % same as Root Parent + % Handle to the figure within which the ParamEditor is displayed Root + % A listener for changes to the figure size. See also ONRESIZE Listener end properties (Dependent) + % Flag for making editor read only by disabling all UI controls Enable end events + % Event notified each time a user makes an edit to a parameter Changed end methods function obj = ParamEditor(pars, parent) + % PARAMEDITOR GUI for visualizing and editing experiment parameters + % The input pars is expected to be an instance of the exp.Parameters + % class. Parant is a handle to a parent figure or UI Panel. If no + % parant is given, the editor is created in a new figure. + % + % See also EUI.FIELDPANEL, EUI.CONDITIONPANEL if nargin == 0; pars = []; end if nargin < 2 parent = figure('Name', 'Parameters', 'NumberTitle', 'off',... @@ -55,6 +79,10 @@ end function delete(obj) + % DELETE Deletes all panels + % Called when the ParamEditor object is deleted or its parant figure + % is closed. Deletes all UI elements and data. + % See also CLEAR delete(obj.GlobalUI); delete(obj.ConditionalUI); end @@ -79,11 +107,27 @@ function delete(obj) end function clear(obj) + % CLEAR Clear the Global and Condition panels + % Deletes all UI fields (labels and control elements) and clears the + % Condition Table data. + % + % See also BUILDUI clear(obj.GlobalUI); clear(obj.ConditionalUI); end function buildUI(obj, pars) + % BUILDUI Populate Global and Condition UI panels with paramter set + % Clears any existing fields and the condition table, then loads new + % UI controls and labels for the Global parameters, and fills the + % condition table. The input pars must be an instance of the + % exp.Parameters class. + % + % RandomiseConditions is not added as a field but instead is + % represented as a context menu item + % + % See also EXP.PARAMETERS, EUI.FIELDPANEL/ADDFIELD, + % EUI.CONDITIONPANEL/FILLCONDITIONTABLE obj.Parameters = pars; obj.clear() % Clear the current parameter UI elements if isempty(pars); return; end % Nothing to build @@ -95,7 +139,7 @@ function buildUI(obj, pars) if strcmp(nm, 'randomiseConditions'); continue; end if islogical(pars.Struct.(nm{:})) % If parameter is logical, make checkbox ctrl = uicontrol('Parent', c.UIPanel, 'Style', 'checkbox', ... - 'Value', pars.Struct.(nm{:})); + 'Value', pars.Struct.(nm{:}), 'Tag', nm{:}); addField(c, nm{:}, ctrl); else % Otherwise create the default field; a text box [~, ctrl] = addField(c, nm{:}); @@ -166,6 +210,12 @@ function addEmptyConditionToParam(obj, name) end function newValue = update(obj, name, value, row) + % UPDATE Updates the exp.Parameters object with new value + % Called when either the Condition table or Global param fields are + % updated by the user. Updated the underlying paramters structure + % and notifies listeners of the change via the Changed event. + % + % See also EUI.FIELDPANEL/ONEDIT, EUI.CONDITIONPANEL/ONEDIT if nargin < 4; row = 1; end currValue = obj.Parameters.Struct.(name)(:,row); if iscell(currValue) diff --git a/tests/ParamEditorTest.m b/tests/ParamEditorTest.m index 9c22e3ff..630dfbf4 100644 --- a/tests/ParamEditorTest.m +++ b/tests/ParamEditorTest.m @@ -9,10 +9,14 @@ Figure % Handle to trial conditions UI Table Table + % Test parameter structure + Parameters end - properties %(MethodSetupParameter) - Parameters + properties (SetAccess = private) + % Flag set to true each time the ParamEditor's Changed event is + % notified + Changed = false end methods (TestClassSetup) @@ -21,7 +25,7 @@ function setup(testCase) testCase.FigureVisibleDefault = get(0,'DefaultFigureVisible'); % set(0,'DefaultFigureVisible','off'); testCase.addTeardown(@set, 0, 'DefaultFigureVisible', testCase.FigureVisibleDefault); - + % Loads validation data % Graph data is a cell array where each element is the graph number % (1:3) and within each element is a cell of X- and Y- axis values @@ -59,6 +63,13 @@ function buildParams(testCase) % Ensure all conditional params have column in table testCase.fatalAssertTrue(isequal(size(testCase.Table.Data), ... [PE.Parameters.numTrialConditions, numel(PE.Parameters.TrialSpecificNames)])) + % Add callback for verifying that Changed event listeners are + % notified + callback = @(~,~)testCase.setChanged(true); + lh = event.listener(testCase.ParamEditor, 'Changed', callback); + testCase.addTeardown(@delete, lh); + % Reset the Changed flag + testCase.Changed = false; end end @@ -69,6 +80,7 @@ function test_makeConditional(testCase) % conditional, and that the underlying Parameters object is also % affected PE = testCase.ParamEditor; + testCase.assertTrue(~testCase.Changed, 'Changed flag incorrect') % Number of global parameters: find all text labels gLabels = @()findobj(testCase.Figure, 'Style', 'text'); gInputs = @()findobj(testCase.Figure, 'Style', 'checkbox', '-or', 'Style', 'edit'); @@ -80,9 +92,12 @@ function test_makeConditional(testCase) c = findobj(testCase.Figure, 'Text', 'Make Conditional'); % Set the focused object to one of the parameter labels set(testCase.Figure, 'CurrentObject', ... - findobj(testCase.Figure, 'String', 'rewardVolume')) + findobj(testCase.Figure, 'Tag', 'rewardVolume')) testCase.verifyWarningFree(c.MenuSelectedFcn, ... 'Problem making parameter conditional'); + % Verify Changed event triggered + testCase.verifyTrue(testCase.Changed, ... + 'Failed to notify listeners of parameter change') % Verify change in UI elements testCase.verifyTrue(numel(gLabels()) == nGlobalLabels-1, ... 'Global parameter UI element not removed') @@ -152,15 +167,17 @@ function test_paramValue2Control(testCase) function test_newCondition(testCase) PE = testCase.ParamEditor; - tableRows = size(testCase.Table.Data, 1); % Make function handle param conditional to test default value % Set the focused object to one of the parameter labels set(testCase.Figure, 'CurrentObject', ... - findobj(testCase.Figure, 'String', 'experimentFun')) + findobj(testCase.Figure, 'Tag', 'experimentFun')) feval(pick(findobj(testCase.Figure, 'Text', 'Make Conditional'), 'MenuSelectedFcn')) + % Reset Changed flag + testCase.Changed = false; + % Retrieve function handle for new condition fn = pick(findobj(testCase.Figure, 'String', 'New condition'), 'Callback'); testCase.verifyWarningFree(fn, 'Warning encountered adding trial condition') @@ -177,11 +194,15 @@ function test_newCondition(testCase) [~, trialParams] = PE.Parameters.assortForExperiment; testCase.verifyTrue(isequal(struct2cell(trialParams(end)), ... {@nop; zeros(2,1); zeros(3,1); false; 0})) + + % Verify listeners WEREN'T notified + testCase.verifyTrue(~testCase.Changed, ... + 'Shouldn''t have notified listeners of parameter change') end function test_deleteCondition(testCase) PE = testCase.ParamEditor; - + testCase.assertTrue(~testCase.Changed, 'Changed flag incorrect') tableRows = size(testCase.Table.Data, 1); % Select some cells to delete event.Indices = [(1:5)' ones(5,1)]; @@ -200,6 +221,10 @@ function test_deleteCondition(testCase) testCase.assertEqual(size(testCase.Table.Data), ... [PE.Parameters.numTrialConditions, numel(PE.Parameters.TrialSpecificNames)]) + % Verify Changed event triggered + testCase.verifyTrue(testCase.Changed, ... + 'Failed to notify listeners of parameter change') + % Test behaviour when < 2 conditions remain event.Indices = [(1:PE.Parameters.numTrialConditions-1)' ... ones(PE.Parameters.numTrialConditions-1,1)]; @@ -216,13 +241,14 @@ function test_deleteCondition(testCase) function test_setValues(testCase) % TODO Add test for the set values button. For now let's fail this + testCase.assertTrue(~testCase.Changed, 'Changed flag incorrect') PE = testCase.ParamEditor; testCase.assertTrue(false, 'Test not implemented') end function test_globaliseParam(testCase) PE = testCase.ParamEditor; - + testCase.assertTrue(~testCase.Changed, 'Changed flag incorrect') tableCols = size(testCase.Table.Data, 2); % Globalize one param event.Indices = [1, 2]; @@ -240,6 +266,10 @@ function test_globaliseParam(testCase) testCase.verifyEqual(size(testCase.Table.Data), ... [PE.Parameters.numTrialConditions, numel(PE.Parameters.TrialSpecificNames)]) + % Verify Changed event triggered + testCase.verifyTrue(testCase.Changed, ... + 'Failed to notify listeners of parameter change') + % Test removal of all but numRepeats: numRepeats should automatically % globalize n = numel(PE.Parameters.TrialSpecificNames)-1; @@ -280,6 +310,7 @@ function test_globaliseParam(testCase) function test_paramEdits(testCase) % Test basic edits to Global UI controls and Condition table PE = testCase.ParamEditor; + testCase.assertTrue(~testCase.Changed, 'Changed flag incorrect') % Retreive all global parameters labels and input controls gLabels = findobj(testCase.Figure, 'Style', 'text'); @@ -302,6 +333,10 @@ function test_paramEdits(testCase) par = strcmpi(PE.Parameters.GlobalNames, strrep(gLabels(idx).String, ' ', '')); testCase.verifyEqual(PE.Parameters.Struct.(PE.Parameters.GlobalNames{par}), 666, ... 'UI edit failed to update parameters struct') + % Verify Changed event triggered + testCase.verifyTrue(testCase.Changed, ... + 'Failed to notify listeners of parameter change') + testCase.Changed = false; % Test editing global param, 'checkbox' UI idx = find(strcmp({gInputs.Style}, 'checkbox'), 1); @@ -319,6 +354,10 @@ function test_paramEdits(testCase) testCase.verifyEqual(... PE.Parameters.Struct.(PE.Parameters.GlobalNames{par}), gInputs(idx).Value==true, ... 'UI checkbox failed to update parameters struct') + % Verify Changed event triggered + testCase.verifyTrue(testCase.Changed, ... + 'Failed to notify listeners of parameter change') + testCase.Changed = false; % Test edits to conditions table callback_fcn = testCase.Table.CellEditCallback; @@ -333,6 +372,9 @@ function test_paramEdits(testCase) value = PE.Parameters.Struct.(PE.Parameters.TrialSpecificNames{1}); testCase.verifyEqual(value(:,1), [0;5], ... 'Table UI failed to update parameters struct') + % Verify Changed event triggered + testCase.verifyTrue(testCase.Changed, ... + 'Failed to notify listeners of parameter change') end function test_interactivity(testCase) @@ -374,4 +416,10 @@ function test_interactivity(testCase) end + methods + function setChanged(testCase, value) + testCase.Changed = value; + end + end + end \ No newline at end of file From d9fc48daab6f532eb48d85690f4c021ef94aa765 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 13 Mar 2019 17:10:11 +0200 Subject: [PATCH 317/393] Added full documentation --- +eui/ConditionPanel.m | 69 ++++++++++++++++++++++++++++++++++++------- +eui/FieldPanel.m | 45 +++++++++++++++++++++++----- +eui/ParamEditor.m | 2 ++ 3 files changed, 98 insertions(+), 18 deletions(-) diff --git a/+eui/ConditionPanel.m b/+eui/ConditionPanel.m index 91f24140..1936c384 100644 --- a/+eui/ConditionPanel.m +++ b/+eui/ConditionPanel.m @@ -1,33 +1,48 @@ classdef ConditionPanel < handle - %UNTITLED Deals with formatting trial conditions UI table - % Detailed explanation goes here - % TODO Document + %CONDITIONPANEL Deals with formatting trial conditions UI table + % Designed to be an element of the EUI.PARAMEDITOR class that manages + % the UI elements associated with all Conditional parameters. % TODO Add sort by column % TODO Add set condition idx - % TODO Use tags for menu items properties + % Handle to UI Table that represents trial conditions ConditionTable + % Minimum UI Panel width allowed. See also EUI.PARAMEDITOR/ONRESIZE MinWidth = 80 % MaxWidth = 140 % Margin = 4 + % Handle to parent UI container UIPanel + % Handle to UI container for buttons ButtonPanel + % Handles to context menu items ContextMenus end properties (Access = protected) + % Handle to EUI.PARAMEDITOR object ParamEditor - Listener + % UIControl button for adding a new trial condition (row) to the table NewConditionButton + % UIControl button for deleting trial conditions (rows) from the table DeleteConditionButton + % UIControl button for making conditional parameter (column) global MakeGlobalButton + % UIControl button for setting multiple table cells at once SetValuesButton - SelectedCells %[row, column;...] of each selected cell + % Indicies of selected table cells as array [row, column;...] of each + % selected cell + SelectedCells end methods function obj = ConditionPanel(f, ParamEditor, varargin) + % FIELDPANEL Panel UI for Conditional parameters + % Input f may be a figure or other UI container object + % ParamEditor is a handle to an eui.ParamEditor object. + % + % See also EUI.PARAMEDITOR, EUI.FIELDPANEL obj.ParamEditor = ParamEditor; obj.UIPanel = uix.VBox('Parent', f); % obj.UIPanel.BackgroundColor = 'white'; @@ -108,18 +123,26 @@ function onEdit(obj, src, eventData) end function clear(obj) + % CLEAR Clear all table data + % Clears all trial condition data from UI Table + % + % See also EUI.PARAMEDITOR/BUILDUI, EUI.PARAMEDITOR/CLEAR set(obj.ConditionTable, 'ColumnName', [], ... 'Data', [], 'ColumnEditable', false); end function delete(obj) + % DELETE Deletes the UI container + % Called when this object or its parant ParamEditor is deleted + % See also CLEAR disp('delete called'); delete(obj.UIPanel); end function onSelect(obj, ~, eventData) - % If at least one cell is selected, ensure buttons and menu items are - % enabled, otherwise disable them. + % ONSELECT Callback for when table cells are (de-)selected + % If at least one cell is selected, ensure buttons and menu items + % are enabled, otherwise disable them. if nargin > 2; obj.SelectedCells = eventData.Indices; end controls = ... [obj.MakeGlobalButton, ... @@ -130,7 +153,12 @@ function onSelect(obj, ~, eventData) end function makeGlobal(obj) - % FIXME Don't allow only numRepeats to remain + % MAKEGLOBAL Make condition parameter (table column) global + % Find all selected columns are turn into global parameters, using + % the value of the first selected cell as the global parameter + % value. + % + % See also eui.ParamEditor/globaliseParamAtCell if isempty(obj.SelectedCells) disp('nothing selected') return @@ -175,7 +203,19 @@ function deleteSelectedConditions(obj) obj.fillConditionTable(); end - function setSelectedValues(obj) % Set multiple fields in conditional table + function setSelectedValues(obj) + % SETSELECTEDVALUES Set multiple fields in conditional table at once + % Generates an input dialog for setting multiple trial conditions at + % once. Also allows the use of function handles for more complex + % values. + % + % Examples: + % (1:10:100) % Sets selected rows to [1 11 21 31 41 51 61 71 81 91] + % @(~)randi(100) % Assigned random integer to each selected row + % @(a)a*50 % Multiplies each condition value by 50 + % false % Sets all selected rows to false + % + % See also SETNEWVALS, ONEDIT disp('updating table cells'); cols = obj.SelectedCells(:,2); % selected columns uCol = unique(obj.SelectedCells(:,2)); @@ -233,7 +273,10 @@ function setSelectedValues(obj) % Set multiple fields in conditional table end function fillConditionTable(obj) - % Build the condition table + % FILLCONDITIONTABLE Build the condition table + % Populates the UI Table with trial specific parameters, where each + % row is a trial condition (that is, a parameter column) and each + % column is a different trial specific parameter titles = obj.ParamEditor.Parameters.TrialSpecificNames; [~, trialParams] = obj.ParamEditor.Parameters.assortForExperiment; if isempty(titles) @@ -252,6 +295,10 @@ function fillConditionTable(obj) end function newCondition(obj) + % Adds a new trial condition (row) to the ConditionTable + % Adds new row and populates it with sensible 'default' values. + % These are mostly zeros or empty values. + % See also eui.ParamEditor/addEmptyConditionToParam disp('adding new condition row'); PE = obj.ParamEditor; cellfun(@PE.addEmptyConditionToParam, ... diff --git a/+eui/FieldPanel.m b/+eui/FieldPanel.m index b4e59719..666cf8b8 100644 --- a/+eui/FieldPanel.m +++ b/+eui/FieldPanel.m @@ -1,32 +1,47 @@ classdef FieldPanel < handle - %UNTITLED Deals with formatting global parameter UI elements - % Detailed explanation goes here + %FIELDPANEL Deals with formatting global parameter UI elements + % Designed to be an element of the EUI.PARAMEDITOR class that manages + % the UI elements associated with all Global parameters. properties + % Minimum allowable width (in pixels) for each UIControl element MinCtrlWidth = 40 + % Maximum allowable width (in pixels) for each UIControl element MaxCtrlWidth = 140 + % Space (in pixels) between parent container and parameter fields Margin = 14 + % Space (in pixels) between each parameter field row RowSpacing = 1 + % Space (in pixels) between each parameter field column ColSpacing = 3 + % Handle to parent UI container UIPanel + % Handles to context menu option for making a parameter conditional ContextMenu end properties (Access = ?eui.ParamEditor) + % Handle to EUI.PARAMEDITOR object ParamEditor + % Minimum height (in pixels) of each field row. See ONRESIZE MinRowHeight + % Listener handle for when parent container is resized Listener + % Array of UIControl labels Labels + % Array of UIControl elements. Either 'edit' or 'checkbox' controls Controls + % Array widths, one for each label in Labels. See ONRESIZE LabelWidths end - events - Changed - end - methods function obj = FieldPanel(f, ParamEditor) + % FIELDPANEL Panel UI for Global parameters + % Input f may be a figure or other UI container object + % ParamEditor is a handle to an eui.ParamEditor object. + % + % See also EUI.PARAMEDITOR, EUI.CONDITIONPANEL obj.ParamEditor = ParamEditor; p = uix.Panel('Parent', f, 'BorderType', 'none'); obj.UIPanel = uipanel('Parent', p, 'BorderType', 'none',... @@ -35,7 +50,14 @@ end function [label, ctrl] = addField(obj, name, ctrl) - % TODO Maybe use exp.Parameters/ui + % ADDFIELD Adds a new field label and input control + % Adds a label and control element for representing Global + % parameters. The input name should be identical to a parameter + % fieldname. From this the label string title is derived using + % exp.Parameters/title. Callback are added for the context menu and + % for edits + % + % See also ONEDIT, EXP.PARAMETERS/TITLE, EUI.PARAMEDITOR/BUILDUI if isempty(obj.ContextMenu) obj.ContextMenu = uicontextmenu; uimenu(obj.ContextMenu, 'Label', 'Make Conditional', ... @@ -134,11 +156,20 @@ function makeConditional(obj, name) end function delete(obj) + % DELETE Deletes the UI container + % Called when this object or its parant ParamEditor is deleted + % See also CLEAR disp('delete called'); delete(obj.UIPanel); end function onResize(obj, ~, ~) + % ONRESIZE Re-position field UI elements after container resize + % Calculates the positions all field labels and input controls. + % These are organised into rows and columns that maximize use of + % space. + % + % See also EUI.PARAMEDITOR/ONRESIZE if isempty(obj.Controls) return end diff --git a/+eui/ParamEditor.m b/+eui/ParamEditor.m index 9168b13a..5b174214 100644 --- a/+eui/ParamEditor.m +++ b/+eui/ParamEditor.m @@ -177,6 +177,8 @@ function setRandomized(obj, value) function addEmptyConditionToParam(obj, name) % Add a new trial specific condition to the table + % Adds a new trial condition to each trial specific parameter. That + % is, adds a new column to each parameter. % See also EUI.CONDITIONPANEL/NEWCONDITION assert(obj.Parameters.isTrialSpecific(name),... 'Tried to add a new condition to global parameter ''%s''', name); From 8272c01dbe7d64edbc6813b62013ab45a0e48779 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 13 Mar 2019 22:14:15 +0200 Subject: [PATCH 318/393] Added performance test and tooltip strings --- +eui/ConditionPanel.m | 20 ++- +eui/FieldPanel.m | 17 +-- +eui/ParamEditor.m | 11 +- tests/ParamEditorPerfTest.m | 240 ++++++++++++++++++++++++++++++++++++ tests/ParamEditorTest.m | 4 +- 5 files changed, 270 insertions(+), 22 deletions(-) create mode 100644 tests/ParamEditorPerfTest.m diff --git a/+eui/ConditionPanel.m b/+eui/ConditionPanel.m index 1936c384..6a939ca8 100644 --- a/+eui/ConditionPanel.m +++ b/+eui/ConditionPanel.m @@ -101,10 +101,18 @@ end function onEdit(obj, src, eventData) + % ONEDIT Callback for edits to condition table + % Updates the underlying parameter struct, changes the UI table + % data. The src object should be the UI Table that has been edited, + % and eventData contains the table indices of the edited cell. + % + % See also FILLCONDITIONTABLE, EUI.PARAMEDITOR/UPDATE disp('updating table cell'); row = eventData.Indices(1); col = eventData.Indices(2); - paramName = obj.ConditionTable.ColumnName{col}; + assert(all(cellfun(@strcmpi, strrep(obj.ConditionTable.ColumnName, ' ', ''), ... + obj.ParamEditor.Parameters.TrialSpecificNames)), 'Unexpected condition names') + paramName = obj.ParamEditor.Parameters.TrialSpecificNames{col}; newValue = obj.ParamEditor.update(paramName, eventData.NewData, row); reformed = obj.ParamEditor.paramValue2Control(newValue); % If successful update the cell with default formatting @@ -163,10 +171,10 @@ function makeGlobal(obj) disp('nothing selected') return end + PE = obj.ParamEditor; [cols, iu] = unique(obj.SelectedCells(:,2)); - names = obj.ConditionTable.ColumnName(cols); + names = PE.Parameters.TrialSpecificNames(cols); rows = num2cell(obj.SelectedCells(iu,1)); %get rows of unique selected cols - PE = obj.ParamEditor; cellfun(@PE.globaliseParamAtCell, names, rows); % If only numRepeats remains, globalise it if isequal(PE.Parameters.TrialSpecificNames, {'numRepeats'}) @@ -180,7 +188,6 @@ function deleteSelectedConditions(obj) % The callback for the 'Delete condition' button. This removes the % selected conditions from the table and if less than two conditions % remain, globalizes them. - % TODO: comment function better, index in a clearer fashion % % See also EXP.PARAMETERS, GLOBALISESELECTEDPARAMETERS rows = unique(obj.SelectedCells(:,1)); @@ -277,8 +284,9 @@ function fillConditionTable(obj) % Populates the UI Table with trial specific parameters, where each % row is a trial condition (that is, a parameter column) and each % column is a different trial specific parameter - titles = obj.ParamEditor.Parameters.TrialSpecificNames; - [~, trialParams] = obj.ParamEditor.Parameters.assortForExperiment; + P = obj.ParamEditor.Parameters; + titles = P.title(P.TrialSpecificNames); + [~, trialParams] = P.assortForExperiment; if isempty(titles) obj.ButtonPanel.Visible = 'off'; obj.UIPanel.Visible = 'off'; diff --git a/+eui/FieldPanel.m b/+eui/FieldPanel.m index 666cf8b8..3cfe4049 100644 --- a/+eui/FieldPanel.m +++ b/+eui/FieldPanel.m @@ -9,7 +9,7 @@ % Maximum allowable width (in pixels) for each UIControl element MaxCtrlWidth = 140 % Space (in pixels) between parent container and parameter fields - Margin = 14 + Margin = 4 % Space (in pixels) between each parameter field row RowSpacing = 1 % Space (in pixels) between each parameter field column @@ -49,30 +49,31 @@ obj.Listener = event.listener(obj.UIPanel, 'SizeChanged', @obj.onResize); end - function [label, ctrl] = addField(obj, name, ctrl) + function [label, ctrl] = addField(obj, name, type) % ADDFIELD Adds a new field label and input control % Adds a label and control element for representing Global % parameters. The input name should be identical to a parameter - % fieldname. From this the label string title is derived using - % exp.Parameters/title. Callback are added for the context menu and - % for edits + % fieldname. Type is an optional input specifying the style of + % uicontrol (default 'edit'). From this the label string title is + % derived using exp.Parameters/title. Callbacks are added for the + % context menu and for edits % % See also ONEDIT, EXP.PARAMETERS/TITLE, EUI.PARAMEDITOR/BUILDUI + if nargin < 3; type = 'edit'; end if isempty(obj.ContextMenu) obj.ContextMenu = uicontextmenu; uimenu(obj.ContextMenu, 'Label', 'Make Conditional', ... 'MenuSelectedFcn', @(~,~)obj.makeConditional); end % props.BackgroundColor = 'white'; + props.TooltipString = obj.ParamEditor.Parameters.description(name); props.HorizontalAlignment = 'left'; props.UIContextMenu = obj.ContextMenu; props.Parent = obj.UIPanel; props.Tag = name; title = obj.ParamEditor.Parameters.title(name); label = uicontrol('Style', 'text', 'String', title, props); - if nargin < 3 - ctrl = uicontrol('Style', 'edit', props); - end + ctrl = uicontrol('Style', type, props); callback = @(src,~)onEdit(obj, src, name); set(ctrl, 'Callback', callback); obj.Labels = [obj.Labels; label]; diff --git a/+eui/ParamEditor.m b/+eui/ParamEditor.m index 5b174214..cbe86e75 100644 --- a/+eui/ParamEditor.m +++ b/+eui/ParamEditor.m @@ -59,6 +59,7 @@ % obj.Listener = event.listener(parent, 'SizeChanged', @(~,~)obj.onResize); obj.Parent = uix.HBox('Parent', parent); obj.GlobalUI = eui.FieldPanel(obj.Parent, obj); + if nargin == 2; obj.GlobalUI.Margin = 14; end % FIXME Add as generic input name-value pair obj.ConditionalUI = eui.ConditionPanel(obj.Parent, obj); obj.buildUI(pars); % FIXME Current hack for drawing params first time @@ -138,9 +139,8 @@ function buildUI(obj, pars) % context menu, don't create global param field if strcmp(nm, 'randomiseConditions'); continue; end if islogical(pars.Struct.(nm{:})) % If parameter is logical, make checkbox - ctrl = uicontrol('Parent', c.UIPanel, 'Style', 'checkbox', ... - 'Value', pars.Struct.(nm{:}), 'Tag', nm{:}); - addField(c, nm{:}, ctrl); + [~, ctrl] = addField(c, nm{:}, 'checkbox'); + ctrl.Value = pars.Struct.(nm{:}); else % Otherwise create the default field; a text box [~, ctrl] = addField(c, nm{:}); ctrl.String = obj.paramValue2Control(pars.Struct.(nm{:})); @@ -242,9 +242,8 @@ function globaliseParamAtCell(obj, name, row) obj.ConditionalUI.fillConditionTable; % Add new global parameter to field panel if islogical(value) % If parameter is logical, make checkbox - ctrl = uicontrol('Parent', obj.GlobalUI.UIPanel, ... - 'Style', 'checkbox', 'Value', value); - addField(obj.GlobalUI, name, ctrl); + [~, ctrl] = addField(obj.GlobalUI, name, 'checkbox'); + ctrl.Value = value; else [~, ctrl] = addField(obj.GlobalUI, name); ctrl.String = obj.paramValue2Control(value); diff --git a/tests/ParamEditorPerfTest.m b/tests/ParamEditorPerfTest.m new file mode 100644 index 00000000..e281cd5f --- /dev/null +++ b/tests/ParamEditorPerfTest.m @@ -0,0 +1,240 @@ +classdef ParamEditorPerfTest < matlab.perftest.TestCase + + properties + % Figure visibility setting before running tests + FigureVisibleDefault + % ParamEditor instance + ParamEditor + % Figure handle for ParamEditor + Figure + % Handle to trial conditions UI Table + Table + % Test parameter structure + Parameters + end + + methods (TestClassSetup) + function setup(testCase) + % Hide figures and add teardown function to restore settings + testCase.FigureVisibleDefault = get(0,'DefaultFigureVisible'); + set(0,'DefaultFigureVisible','off'); + testCase.addTeardown(@set, 0, 'DefaultFigureVisible', testCase.FigureVisibleDefault); + + % Loads validation data + % Graph data is a cell array where each element is the graph number + % (1:3) and within each element is a cell of X- and Y- axis values + % respecively + testCase.Parameters = exp.choiceWorldParams; + + % Check paths file + assert(endsWith(which('dat.paths'), fullfile('tests','+dat','paths.m'))); + % Create stand-alone panel + testCase.ParamEditor = eui.ParamEditor; + testCase.Figure = gcf(); + testCase.addTeardown(@close, testCase.Figure); + assert(isa(testCase.ParamEditor, 'eui.ParamEditor')) + % Find Condition Table + testCase.Table = findobj(testCase.Figure, '-property', 'ColumnName'); + assert(isa(testCase.Table, 'matlab.ui.control.Table'), ... + 'Failed to find handle to condition table') + end + + end + + methods (TestMethodSetup) + function buildParams(testCase) + % Re-build the parameters before each test so that changes in + % previous test don't persist + PE = testCase.ParamEditor; + pars = exp.Parameters(testCase.Parameters); + PE.buildUI(pars); + % Number of global parameters: find all text labels + nGlobalLabels = numel(findobj(testCase.Figure, 'Style', 'text')); + nGlobalInput = numel(findobj(testCase.Figure, 'Style', 'checkbox', '-or', 'Style', 'edit')); + % Ensure all global params have UI input and label + assert(nGlobalLabels == numel(PE.Parameters.GlobalNames)) + assert(nGlobalInput == numel(PE.Parameters.GlobalNames)) + % Ensure all conditional params have column in table + assert(isequal(size(testCase.Table.Data), ... + [PE.Parameters.numTrialConditions, numel(PE.Parameters.TrialSpecificNames)])) + end + end + + methods (Test) + function test_newParamEditor(testCase) + % Test instantiate new param editor from scratch + pars = exp.Parameters(testCase.Parameters); + testCase.startMeasuring(); + PE = eui.ParamEditor(pars); + testCase.stopMeasuring(); + close(gcf) + delete(PE) + end + + function test_buildUI(testCase) + % Test clear and rebuild params + pars = exp.Parameters(testCase.Parameters); + testCase.startMeasuring(); + testCase.ParamEditor.buildUI(pars); + testCase.stopMeasuring(); + end + + function test_makeConditional(testCase) + % Make some global params trial conditions. This test checks that + % the UI elements are re-rendered after making a parameter + % conditional, and that the underlying Parameters object is also + % affected + PE = testCase.ParamEditor; + % Number of global parameters: find all text labels + gLabels = @()findobj(testCase.Figure, 'Style', 'text'); + gInputs = @()findobj(testCase.Figure, 'Style', 'checkbox', '-or', 'Style', 'edit'); + nGlobalLabels = numel(gLabels()); + nGlobalInputs = numel(gInputs()); + tableSz = size(testCase.Table.Data); + + % Retrieve context menu function handle + c = findobj(testCase.Figure, 'Text', 'Make Conditional'); + % Set the focused object to one of the parameter labels + set(testCase.Figure, 'CurrentObject', ... + findobj(testCase.Figure, 'Tag', 'rewardVolume')) + + %%% Make conditional %%% + testCase.startMeasuring(); + c.MenuSelectedFcn() + testCase.stopMeasuring(); + + % Verify change in UI elements + testCase.verifyTrue(numel(gLabels()) == nGlobalLabels-1, ... + 'Global parameter UI element not removed') + testCase.verifyTrue(numel(gInputs()) == nGlobalInputs-1, ... + 'Global parameter UI element not removed') + testCase.verifyTrue(size(testCase.Table.Data,2) == tableSz(2)+1, ... + 'Incorrect condition table') + % Verify change in Parameters object for global + testCase.assertTrue(numel(gLabels()) == numel(PE.Parameters.GlobalNames)) + % Verify change in Parameters object for conditional + testCase.assertTrue(isequal(size(testCase.Table.Data), ... + [PE.Parameters.numTrialConditions, numel(PE.Parameters.TrialSpecificNames)])) + % Verify table values are correct + testCase.verifyTrue(isequal(testCase.Table.Data(:,1), repmat({'3'}, ... + size(testCase.Table.Data,1), 1)), 'Unexpected table values') + end + + function test_newCondition(testCase) + PE = testCase.ParamEditor; + tableRows = size(testCase.Table.Data, 1); + + % Make function handle param conditional to test default value + % Set the focused object to one of the parameter labels + set(testCase.Figure, 'CurrentObject', ... + findobj(testCase.Figure, 'Tag', 'experimentFun')) + feval(pick(findobj(testCase.Figure, 'Text', 'Make Conditional'), 'MenuSelectedFcn')) + + % Retrieve function handle for new condition + fn = pick(findobj(testCase.Figure, 'String', 'New condition'), 'Callback'); + testCase.startMeasuring(); + fn() + testCase.stopMeasuring(); + + % Verify change in table data + testCase.verifyEqual(size(testCase.Table.Data, 1), tableRows+1, ... + 'Unexpected number of trial conditions') + + % Verify change in Parameters object for conditional + testCase.assertEqual(size(testCase.Table.Data), ... + [PE.Parameters.numTrialConditions, numel(PE.Parameters.TrialSpecificNames)]) + end + + function test_deleteCondition(testCase) + PE = testCase.ParamEditor; + tableRows = size(testCase.Table.Data, 1); + % Select some cells to delete + event.Indices = [(1:5)' ones(5,1)]; + selection_fn = testCase.Table.CellSelectionCallback; + selection_fn([],event) + + % Retrieve function handle for delete condition + callback_fn = pick(findobj(testCase.Figure, 'String', 'Delete condition'), 'Callback'); + testCase.startMeasuring(); + callback_fn() + testCase.stopMeasuring(); + + % Verify change in table data + testCase.verifyEqual(size(testCase.Table.Data, 1), tableRows-5, ... + 'Unexpected number of trial conditions') + + % Verify change in Parameters object for conditional + testCase.assertEqual(size(testCase.Table.Data), ... + [PE.Parameters.numTrialConditions, numel(PE.Parameters.TrialSpecificNames)]) + end + + function test_globaliseParam(testCase) + PE = testCase.ParamEditor; + tableCols = size(testCase.Table.Data, 2); + % Globalize one param + event.Indices = [1, 2]; + selection_fn = testCase.Table.CellSelectionCallback; + selection_fn([],event) + + % Retrieve function handle for new condition + callback_fn = pick(findobj(testCase.Figure, 'String', 'Globalise parameter'), 'Callback'); + testCase.startMeasuring(); + callback_fn() + testCase.stopMeasuring(); + + % Verify change in table data + testCase.verifyEqual(size(testCase.Table.Data,2), tableCols-1, ... + 'Unexpected number of conditional parameters') + % Verify change in Parameters object for conditional + testCase.verifyEqual(size(testCase.Table.Data), ... + [PE.Parameters.numTrialConditions, numel(PE.Parameters.TrialSpecificNames)]) + end + + function test_paramEdits(testCase) + % Test basic edits to Global UI controls and Condition table + PE = testCase.ParamEditor; + + % Retreive all global parameters labels and input controls + gLabels = findobj(testCase.Figure, 'Style', 'text'); + gInputs = findobj(testCase.Figure, 'Style', 'checkbox', '-or', 'Style', 'edit'); + + % Test editing global param, 'edit' UI + idx = find(strcmp({gInputs.Style}, 'edit'), 1); + % Change string + gInputs(idx).String = '666'; + % Trigger callback + callback_fcn = gInputs(idx).Callback; + testCase.startMeasuring(); + callback_fcn(gInputs(idx)); + testCase.stopMeasuring(); + + % Verify change in ui string + testCase.verifyEqual(gInputs(idx).String, '666') + % Verify change in label color + testCase.verifyEqual(gLabels(idx).ForegroundColor, [1 0 0], ... + 'Unexpected label colour') + % Verify change in underlying param struct + par = strcmpi(PE.Parameters.GlobalNames, strrep(gLabels(idx).String, ' ', '')); + testCase.verifyEqual(PE.Parameters.Struct.(PE.Parameters.GlobalNames{par}), 666, ... + 'UI edit failed to update parameters struct') + + % Test edits to conditions table + callback_fcn = testCase.Table.CellEditCallback; + event.Indices = [1, 1]; + event.NewData = '0,5'; + testCase.startMeasuring(); + callback_fcn(testCase.Table, event) + testCase.stopMeasuring(); + + % Verify change to table value + testCase.verifyEqual(testCase.Table.Data{1,1}, '0, 5', ... + 'Unexpected table data') + % Verify change in underlying param struct + value = PE.Parameters.Struct.(PE.Parameters.TrialSpecificNames{1}); + testCase.verifyEqual(value(:,1), [0;5], ... + 'Table UI failed to update parameters struct') + end + + end + +end \ No newline at end of file diff --git a/tests/ParamEditorTest.m b/tests/ParamEditorTest.m index 630dfbf4..fc369141 100644 --- a/tests/ParamEditorTest.m +++ b/tests/ParamEditorTest.m @@ -23,7 +23,7 @@ function setup(testCase) % Hide figures and add teardown function to restore settings testCase.FigureVisibleDefault = get(0,'DefaultFigureVisible'); -% set(0,'DefaultFigureVisible','off'); + set(0,'DefaultFigureVisible','off'); testCase.addTeardown(@set, 0, 'DefaultFigureVisible', testCase.FigureVisibleDefault); % Loads validation data @@ -209,7 +209,7 @@ function test_deleteCondition(testCase) selection_fn = testCase.Table.CellSelectionCallback; selection_fn([],event) - % Retrieve function handle for new condition + % Retrieve function handle for delete condition callback_fn = pick(findobj(testCase.Figure, 'String', 'Delete condition'), 'Callback'); testCase.verifyWarningFree(callback_fn, 'Warning encountered deleting trial conditions') From cb4f2ff7fe109cdafa03e4beab6d4bb31b6d5fea Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 14 Mar 2019 02:18:36 +0200 Subject: [PATCH 319/393] Added test for login; bug fix for round --- +eui/AlyxPanel.m | 23 ++++++++--- tests/AlyxPanelTest.m | 96 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 110 insertions(+), 9 deletions(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index e8b24c6c..851c7d18 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -716,16 +716,29 @@ function log(obj, varargin) end methods (Static) - function A = round(a, direction, sigFigures) - if nargin < 3; sigFigures = 2; end - c = 1*10^sigFigures; + function A = round(a, direction, N) + % ROUND Rounds a value a up or down to the nearest N s.f. + % Rounds a value in the specified direction to the nearest N + % significant figures. The default behaviour is the same as + % MATLAB's builtin round function, that is to round to the + % nearest value. + % + % Examples: + % eui.AlyxPanel.round(0.8437, 'up') % 0.85 + % eui.AlyxPanel.round(12.65, 'up', 3) % 12.6 + % eui.AlyxPanel.round(12.6, 'down'), 12); + % + % See also ROUND + if nargin < 2; direction = 'nearest'; end + if nargin < 3; N = 2; end + c = 10^(N-ceil(log10(a))); switch direction case 'up' A = ceil(a*c)/c; case 'down' - A = ceil(a*c)/c; + A = floor(a*c)/c; otherwise - A = round(a, sigFigures, 'significant'); + A = round(a, N, 'significant'); end end end diff --git a/tests/AlyxPanelTest.m b/tests/AlyxPanelTest.m index b220d9cb..dcf04a48 100644 --- a/tests/AlyxPanelTest.m +++ b/tests/AlyxPanelTest.m @@ -5,13 +5,15 @@ FigureVisibleDefault % AlyxPanel instance Panel + % Parent container for AlyxPanel + Parent % Figure handle for AlyxPanel hPanel % Figure handle for any extra figures opened during tests Figure % List of subjects returned by the test database Subjects = {'ZM_1085'; 'ZM_1087'; 'ZM_1094'; 'ZM_1098'; 'ZM_335'} - % bui.Selector object for setting the subject list in tests + % bui.Selector for setting the subject list in tests SubjectUI % Expected Y-axis labels for the viewSubjectHistory plots Ylabels = {'water (mL)', 'weight as pct (%)', 'weight (g)'} @@ -25,7 +27,7 @@ methods (TestClassSetup) function killFigures(testCase) testCase.FigureVisibleDefault = get(0,'DefaultFigureVisible'); - set(0,'DefaultFigureVisible','off'); +% set(0,'DefaultFigureVisible','off'); end function loadData(testCase) @@ -48,13 +50,21 @@ function setupPanel(testCase) 'NumberTitle', 'off',... 'Units', 'normalized',... 'OuterPosition', [0.1 0.1 0.4 .4]); - parent = uiextras.VBox('Parent', testCase.hPanel, 'Visible', 'on'); - testCase.Panel = eui.AlyxPanel(parent); % subject selector + parent = uiextras.VBox('Parent', testCase.hPanel, 'Visible', 'on'); sbox = uix.HBox('Parent', parent); bui.label('Select subject: ', sbox); % Subject dropdown box testCase.SubjectUI = bui.Selector(sbox, [{'default'}; testCase.Subjects]); + % Logging display + uicontrol('Parent', parent, 'Style', 'listbox',... + 'Enable', 'inactive', 'String', {}, 'Tag', 'Logging Display'); + % Create panel + testCase.Panel = eui.AlyxPanel(parent); + testCase.Parent = parent.Children(1); + % Rearrange + parent.Children = parent.Children([2,1,3]); + parent.Sizes = [50 150 150]; % set a callback on subject selection so that we can show water % requirements for new mice as they are selected. This should % be set by any other GUI that instantiates this object (e.g. @@ -128,6 +138,84 @@ function test_viewAllSubjects(testCase) testCase.verifyTrue(isequal(tableData, expected)); end + function test_dispWaterReq(testCase) + testCase.Panel; + end + + function test_launchSessionURL(testCase) + testCase.Panel; + end + + function test_launchSubjectURL(testCase) + testCase.Panel; + end + + function test_recordWeight(testCase) + testCase.Panel; + end + + function test_login(testCase) + % Test panel behaviour when logged in and out + testCase.assertTrue(testCase.Panel.AlyxInstance.IsLoggedIn); + % Check log + logPanel = findobj(testCase.hPanel, 'Tag', 'Logging Display'); + testCase.verifyTrue(endsWith(logPanel.String{1}, 'Logged into Alyx successfully as test_user')) + labels = findobj(testCase.Parent, 'Style', 'text'); + % Check all components enabled + testCase.verifyEmpty(findobj(testCase.Parent, 'Enable', 'off')) + % Check labels + testCase.verifyEqual(labels(3).String, 'You are logged in as test_user') + testCase.verifyTrue(~contains(labels(2).String, 'Log in')) + + % Log out + testCase.Panel.login; + testCase.assertTrue(~testCase.Panel.AlyxInstance.IsLoggedIn); + % Check log + testCase.verifyTrue(endsWith(logPanel.String{end}, 'Logged out of Alyx')) + % Check all components disabled + testCase.verifyTrue(numel(findobj(testCase.Parent, 'Enable', 'on')) <= 2,... + 'Unexpected number of enabled UI elements') + % Check labels + testCase.verifyEqual(labels(3).String, 'Not logged in') + testCase.verifyEqual(labels(2).String, 'Log in to see water requirements') + end + + function test_updateWeightButton(testCase) + testCase.Panel; + end + + function test_log(testCase) + testCase.Panel; + end + + function test_giveWater(testCase) + testCase.Panel; + end + + function test_giveFutureWater(testCase) + testCase.Panel; + end + + function test_changeWaterText(testCase) + testCase.Panel; + end + + function test_round(testCase) + % Test round up + testCase.verifyEqual(testCase.Panel.round(0.8437, 'up'), 0.85); + testCase.verifyEqual(testCase.Panel.round(0.8437, 'up', 3), 0.844); + testCase.verifyEqual(testCase.Panel.round(12.6, 'up'), 13); + + % Test round down + testCase.verifyEqual(testCase.Panel.round(0.8437, 'down'), 0.84); + testCase.verifyEqual(testCase.Panel.round(0.78375, 'down', 3), 0.783); + testCase.verifyEqual(testCase.Panel.round(12.6, 'down'), 12); + + % Test default behaviour + testCase.verifyEqual(testCase.Panel.round(0.8437), 0.84); + testCase.verifyEqual(testCase.Panel.round(0.855), 0.86); + end + end end \ No newline at end of file From 618c79c8f75c807e27d360c8fdae1685cf923b88 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 14 Mar 2019 20:38:21 +0200 Subject: [PATCH 320/393] Protected some methods --- +eui/AlyxPanel.m | 178 +++++++++++++++++++++--------------------- tests/AlyxPanelTest.m | 7 +- 2 files changed, 97 insertions(+), 88 deletions(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index 851c7d18..45dc0cae 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -347,92 +347,6 @@ function giveFutureWater(obj) end end - function dispWaterReq(obj, src, ~) - % Display the amount of water required by the selected subject - % for it to reach its minimum requirement. This function is - % also used to update the selected subject, for example it is - % this funtion to use as a callback to subject dropdown - % listeners - ai = obj.AlyxInstance; - % Set the selected subject if it is an input - if nargin>1; obj.Subject = src.Selected; end - if ~ai.IsLoggedIn - set(obj.WaterRequiredText, 'ForegroundColor', 'black',... - 'String', 'Log in to see water requirements'); - return - end - % Refresh the timer as the user isn't inactive - stop(obj.LoginTimer); start(obj.LoginTimer) - try - s = ai.getData('water-restricted-subjects'); % struct with data about restricted subjects - idx = strcmp(obj.Subject, {s.nickname}); - if ~any(idx) % Subject not on water restriction - set(obj.WaterRequiredText, 'ForegroundColor', 'black',... - 'String', sprintf('Subject %s not on water restriction', obj.Subject)); - else - % Get information on weight and water given - endpnt = sprintf('water-requirement/%s?start_date=%s&end_date=%s',... - obj.Subject, datestr(now, 'yyyy-mm-dd'),datestr(now, 'yyyy-mm-dd')); - wr = ai.getData(endpnt); % Get today's weight and water record - if ~isempty(wr.records) - record = wr.records(end); - else - record = struct(); - end - weight = iff(isempty(record.weighing_at), NaN, record.weight); % Get today's measured weight - water = getOr(record, 'given_water_liquid', 0); % Get total water given - gel = getOr(record, 'given_water_hydrogel', 0); % Get total gel given - expected_weight = getOr(record, 'expected_weight', NaN); - % Set colour based on weight percentage - weight_pct = (weight-wr.implant_weight)/(expected_weight-wr.implant_weight); - if weight_pct < 0.8 % Mouse below 80% original weight - colour = [0.91, 0.41, 0.17]; % Orange - weight_pct = '< 80%'; - elseif weight_pct < 0.7 % Mouse below 70% original weight - colour = 'red'; - weight_pct = '< 70%'; - else - colour = 'black'; % Mouse above 80% or no weight measured today - weight_pct = '> 80%'; - end - % Round up water remaining to the near 0.01 - remainder = obj.round(s(idx).remaining_water, 'up'); - % Set text - set(obj.WaterRequiredText, 'ForegroundColor', colour, 'String', ... - sprintf(['Subject %s requires %.2f of %.2f today\n\t '... - 'Weight today: %.2f (%s) Water today: %.2f'], obj.Subject, ... - remainder, obj.round(s(idx).expected_water, 'up'), weight, ... - weight_pct, obj.round(sum([water gel]), 'down'))); - % Set WaterRemaining attribute for changeWaterText callback - obj.WaterRemaining = remainder; - end - catch me - d = me.message; %FIXME: JSON no longer returned - if isfield(d, 'detail') && strcmp(d.detail, 'Not found.') - set(obj.WaterRequiredText, 'ForegroundColor', 'black',... - 'String', sprintf('Subject %s not found in alyx', obj.Subject)); - else - rethrow(me) - end - end - end - - function changeWaterText(obj, src, ~) - % Update the panel text to show the amount of water still - % required for the subject to reach its minimum requirement. - % This text is updated before the value in the water text box - % has been posted to Alyx. For example if the user is unsure - % how much gel over the minimum they have weighed out, pressing - % return will display this without posting to Alyx - % - % See also DISPWATERREQ, GIVEWATER - if obj.AlyxInstance.IsLoggedIn && ~isempty(obj.WaterRemaining) - rem = obj.WaterRemaining; - curr = str2double(src.String); - set(obj.WaterRemainingText, 'String', sprintf('(%.2f)', rem-curr)); - end - end - function recordWeight(obj, weight, subject) % Post a subject's weight to Alyx. If no inputs are provided, % create an input dialog for the user to input a weight. If no @@ -509,7 +423,7 @@ function launchSessionURL(obj) else % success obj.log(['Created new base session in Alyx for ' obj.Subject]); end - case 'No' + otherwise return end else @@ -674,6 +588,76 @@ function viewAllSubjects(obj) end end + function dispWaterReq(obj, src, ~) + % Display the amount of water required by the selected subject + % for it to reach its minimum requirement. This function is + % also used to update the selected subject, for example it is + % this funtion to use as a callback to subject dropdown + % listeners + ai = obj.AlyxInstance; + % Set the selected subject if it is an input + if nargin>1; obj.Subject = src.Selected; end + if ~ai.IsLoggedIn + set(obj.WaterRequiredText, 'ForegroundColor', 'black',... + 'String', 'Log in to see water requirements'); + return + end + % Refresh the timer as the user isn't inactive + stop(obj.LoginTimer); start(obj.LoginTimer) + try + s = ai.getData('water-restricted-subjects'); % struct with data about restricted subjects + idx = strcmp(obj.Subject, {s.nickname}); + if ~any(idx) % Subject not on water restriction + set(obj.WaterRequiredText, 'ForegroundColor', 'black',... + 'String', sprintf('Subject %s not on water restriction', obj.Subject)); + else + % Get information on weight and water given + endpnt = sprintf('water-requirement/%s?start_date=%s&end_date=%s',... + obj.Subject, datestr(now, 'yyyy-mm-dd'),datestr(now, 'yyyy-mm-dd')); + wr = ai.getData(endpnt); % Get today's weight and water record + if ~isempty(wr.records) + record = wr.records(end); + else + record = struct(); + end + weight = iff(isempty(record.weighing_at), NaN, record.weight); % Get today's measured weight + water = getOr(record, 'given_water_liquid', 0); % Get total water given + gel = getOr(record, 'given_water_hydrogel', 0); % Get total gel given + expected_weight = getOr(record, 'expected_weight', NaN); + % Set colour based on weight percentage + weight_pct = (weight-wr.implant_weight)/(expected_weight-wr.implant_weight); + if weight_pct < 0.8 % Mouse below 80% original weight + colour = [0.91, 0.41, 0.17]; % Orange + weight_pct = '< 80%'; + elseif weight_pct < 0.7 % Mouse below 70% original weight + colour = 'red'; + weight_pct = '< 70%'; + else + colour = 'black'; % Mouse above 80% or no weight measured today + weight_pct = '> 80%'; + end + % Round up water remaining to the near 0.01 + remainder = obj.round(s(idx).remaining_water, 'up'); + % Set text + set(obj.WaterRequiredText, 'ForegroundColor', colour, 'String', ... + sprintf(['Subject %s requires %.2f of %.2f today\n\t '... + 'Weight today: %.2f (%s) Water today: %.2f'], obj.Subject, ... + remainder, obj.round(s(idx).expected_water, 'up'), weight, ... + weight_pct, obj.round(sum([water gel]), 'down'))); + % Set WaterRemaining attribute for changeWaterText callback + obj.WaterRemaining = remainder; + end + catch me + d = me.message; %FIXME: JSON no longer returned + if isfield(d, 'detail') && strcmp(d.detail, 'Not found.') + set(obj.WaterRequiredText, 'ForegroundColor', 'black',... + 'String', sprintf('Subject %s not found in alyx', obj.Subject)); + else + rethrow(me) + end + end + end + function updateWeightButton(obj, src, ~) % Function for changing the text on the weight button to reflect the % current weight value obtained by the scale. This function must be @@ -693,7 +677,27 @@ function updateWeightButton(obj, src, ~) 'StopFcn', @(src,~)delete(src), 'StartDelay', 10); start(obj.WeightTimer) end + + end + + methods (Access = protected) + function changeWaterText(obj, src, ~) + % Update the panel text to show the amount of water still + % required for the subject to reach its minimum requirement. + % This text is updated before the value in the water text box + % has been posted to Alyx. For example if the user is unsure + % how much gel over the minimum they have weighed out, pressing + % return will display this without posting to Alyx + % + % See also DISPWATERREQ, GIVEWATER + if obj.AlyxInstance.IsLoggedIn && ~isempty(obj.WaterRemaining) + rem = obj.WaterRemaining; + curr = str2double(src.String); + set(obj.WaterRemainingText, 'String', sprintf('(%.2f)', rem-curr)); + end + end + function log(obj, varargin) % Function for displaying timestamped information about % occurrences. If the LoggingDisplay property is unset, the diff --git a/tests/AlyxPanelTest.m b/tests/AlyxPanelTest.m index dcf04a48..935ff127 100644 --- a/tests/AlyxPanelTest.m +++ b/tests/AlyxPanelTest.m @@ -27,7 +27,7 @@ methods (TestClassSetup) function killFigures(testCase) testCase.FigureVisibleDefault = get(0,'DefaultFigureVisible'); -% set(0,'DefaultFigureVisible','off'); + set(0,'DefaultFigureVisible','off'); end function loadData(testCase) @@ -143,7 +143,12 @@ function test_dispWaterReq(testCase) end function test_launchSessionURL(testCase) + % Test the launch of the session page in the admin Web interface testCase.Panel; + % Set new subject + testCase.SubjectUI.Selected = testCase.SubjectUI.Option{2}; + testCase.assertEmpty(testCase.Panel.AlyxInstance.SessionURL) + testCase.Panel.launchSessionURL() end function test_launchSubjectURL(testCase) From 53e23fe65fa5a704190244f6fd775d0ed1c0ad81 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 14 Mar 2019 21:08:14 +0200 Subject: [PATCH 321/393] Removed some comments and command promt output, renamed tests --- +eui/ConditionPanel.m | 6 ------ +eui/FieldPanel.m | 3 --- +eui/MControl.m | 2 -- tests/{ParamEditorPerfTest.m => ParamEditor_perf.m} | 4 ++-- tests/{ParamEditorTest.m => ParamEditor_test.m} | 6 +++--- tests/{ParametersTest.m => Parameters_test.m} | 2 +- 6 files changed, 6 insertions(+), 17 deletions(-) rename tests/{ParamEditorPerfTest.m => ParamEditor_perf.m} (99%) rename tests/{ParamEditorTest.m => ParamEditor_test.m} (99%) rename tests/{ParametersTest.m => Parameters_test.m} (99%) diff --git a/+eui/ConditionPanel.m b/+eui/ConditionPanel.m index 6a939ca8..0e9bae60 100644 --- a/+eui/ConditionPanel.m +++ b/+eui/ConditionPanel.m @@ -45,7 +45,6 @@ % See also EUI.PARAMEDITOR, EUI.FIELDPANEL obj.ParamEditor = ParamEditor; obj.UIPanel = uix.VBox('Parent', f); -% obj.UIPanel.BackgroundColor = 'white'; % Create a child menu for the uiContextMenus c = uicontextmenu; obj.UIPanel.UIContextMenu = c; @@ -71,7 +70,6 @@ % Create button panel to hold condition control buttons obj.ButtonPanel = uix.HBox('Parent', obj.UIPanel); % Define some common properties -% props.BackgroundColor = 'white'; props.Style = 'pushbutton'; props.Units = 'normalized'; props.Parent = obj.ButtonPanel; @@ -107,7 +105,6 @@ function onEdit(obj, src, eventData) % and eventData contains the table indices of the edited cell. % % See also FILLCONDITIONTABLE, EUI.PARAMEDITOR/UPDATE - disp('updating table cell'); row = eventData.Indices(1); col = eventData.Indices(2); assert(all(cellfun(@strcmpi, strrep(obj.ConditionTable.ColumnName, ' ', ''), ... @@ -143,7 +140,6 @@ function delete(obj) % DELETE Deletes the UI container % Called when this object or its parant ParamEditor is deleted % See also CLEAR - disp('delete called'); delete(obj.UIPanel); end @@ -223,7 +219,6 @@ function setSelectedValues(obj) % false % Sets all selected rows to false % % See also SETNEWVALS, ONEDIT - disp('updating table cells'); cols = obj.SelectedCells(:,2); % selected columns uCol = unique(obj.SelectedCells(:,2)); rows = obj.SelectedCells(:,1); % selected rows @@ -307,7 +302,6 @@ function newCondition(obj) % Adds new row and populates it with sensible 'default' values. % These are mostly zeros or empty values. % See also eui.ParamEditor/addEmptyConditionToParam - disp('adding new condition row'); PE = obj.ParamEditor; cellfun(@PE.addEmptyConditionToParam, ... PE.Parameters.TrialSpecificNames); diff --git a/+eui/FieldPanel.m b/+eui/FieldPanel.m index 3cfe4049..ce950813 100644 --- a/+eui/FieldPanel.m +++ b/+eui/FieldPanel.m @@ -65,7 +65,6 @@ uimenu(obj.ContextMenu, 'Label', 'Make Conditional', ... 'MenuSelectedFcn', @(~,~)obj.makeConditional); end -% props.BackgroundColor = 'white'; props.TooltipString = obj.ParamEditor.Parameters.description(name); props.HorizontalAlignment = 'left'; props.UIContextMenu = obj.ContextMenu; @@ -89,7 +88,6 @@ function onEdit(obj, src, id) % property of the label and control elements). % % See also ADDFIELD, EUI.PARAMEDITOR/UPDATE - disp(id); switch get(src, 'style') case 'checkbox' newValue = logical(get(src, 'value')); @@ -160,7 +158,6 @@ function delete(obj) % DELETE Deletes the UI container % Called when this object or its parant ParamEditor is deleted % See also CLEAR - disp('delete called'); delete(obj.UIPanel); end diff --git a/+eui/MControl.m b/+eui/MControl.m index eb0b15aa..9bec3799 100644 --- a/+eui/MControl.m +++ b/+eui/MControl.m @@ -247,7 +247,6 @@ function saveParamProfile(obj) % Called by 'Save...' button press, save a new pa end function loadParamProfile(obj, profile) - tic set(obj.ParamProfileLabel, 'String', 'loading...', 'ForegroundColor', [1 0 0]); % Red 'Loading...' while new set loads if ~isempty(obj.ParamEditor) % Clear existing parameters control @@ -317,7 +316,6 @@ function loadParamProfile(obj, profile) if strcmp(obj.RemoteRigs.Selected.Status, 'idle') set(obj.BeginExpButton, 'Enable', 'on') % Re-enable start button end - disp(toc) end function paramChanged(obj) diff --git a/tests/ParamEditorPerfTest.m b/tests/ParamEditor_perf.m similarity index 99% rename from tests/ParamEditorPerfTest.m rename to tests/ParamEditor_perf.m index e281cd5f..5afdefb3 100644 --- a/tests/ParamEditorPerfTest.m +++ b/tests/ParamEditor_perf.m @@ -1,4 +1,4 @@ -classdef ParamEditorPerfTest < matlab.perftest.TestCase +classdef ParamEditor_perf < matlab.perftest.TestCase properties % Figure visibility setting before running tests @@ -23,7 +23,7 @@ function setup(testCase) % Loads validation data % Graph data is a cell array where each element is the graph number % (1:3) and within each element is a cell of X- and Y- axis values - % respecively + % respectively testCase.Parameters = exp.choiceWorldParams; % Check paths file diff --git a/tests/ParamEditorTest.m b/tests/ParamEditor_test.m similarity index 99% rename from tests/ParamEditorTest.m rename to tests/ParamEditor_test.m index fc369141..31f44c3f 100644 --- a/tests/ParamEditorTest.m +++ b/tests/ParamEditor_test.m @@ -1,4 +1,4 @@ -classdef ParamEditorTest < matlab.unittest.TestCase +classdef ParamEditor_test < matlab.unittest.TestCase properties % Figure visibility setting before running tests @@ -29,7 +29,7 @@ function setup(testCase) % Loads validation data % Graph data is a cell array where each element is the graph number % (1:3) and within each element is a cell of X- and Y- axis values - % respecively + % respectively testCase.Parameters = exp.choiceWorldParams; % Check paths file @@ -242,7 +242,7 @@ function test_deleteCondition(testCase) function test_setValues(testCase) % TODO Add test for the set values button. For now let's fail this testCase.assertTrue(~testCase.Changed, 'Changed flag incorrect') - PE = testCase.ParamEditor; +% PE = testCase.ParamEditor; testCase.assertTrue(false, 'Test not implemented') end diff --git a/tests/ParametersTest.m b/tests/Parameters_test.m similarity index 99% rename from tests/ParametersTest.m rename to tests/Parameters_test.m index 83684645..4241b314 100644 --- a/tests/ParametersTest.m +++ b/tests/Parameters_test.m @@ -1,4 +1,4 @@ -classdef ParametersTest < matlab.unittest.TestCase +classdef Parameters_test < matlab.unittest.TestCase properties % Parameters object From 7162d92318b1d269e018597a9e9b155137973b4d Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 14 Mar 2019 21:35:36 +0200 Subject: [PATCH 322/393] Update readme.md Recommended to use --recurse-submodules flag instead --- readme.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 6f6e5470..e238d412 100644 --- a/readme.md +++ b/readme.md @@ -60,8 +60,7 @@ and restart MATLAB. To keep the submodules up to date, run the following in the Git Bash terminal (within the Rigbox directory): ``` -git pull -git submodule update --remote +git pull --recurse-submodules ``` ## Running an experiment in MATLAB From c8f7eb663d4a15d29728148d91405cfbe3565ce9 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 15 Mar 2019 19:34:54 +0200 Subject: [PATCH 323/393] Changed RearrangeableColumns from true to 'on' --- +eui/ConditionPanel.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/+eui/ConditionPanel.m b/+eui/ConditionPanel.m index 0e9bae60..79bba69b 100644 --- a/+eui/ConditionPanel.m +++ b/+eui/ConditionPanel.m @@ -61,7 +61,7 @@ obj.ConditionTable = uitable('Parent', p,... 'FontName', 'Consolas',... 'RowName', [],... - 'RearrangeableColumns', true,... + 'RearrangeableColumns', 'on',... 'Units', 'normalized',... 'Position',[0 0 1 1],... 'UIContextMenu', c,... From d1ded065138705eb511ed31877b5fcab944c2056 Mon Sep 17 00:00:00 2001 From: jaib1 <j.bhagat@ucl.ac.uk> Date: Fri, 15 Mar 2019 23:06:50 +0000 Subject: [PATCH 324/393] reorganized test folder structure, plus added some comments --- +eui/ConditionPanel.m | 2 -- .github/config.yml | 2 -- ...amEditor_perf.m => ParamEditor_perfTest.m} | 18 +++++++++++++++--- tests/ParamEditor_test.m | 16 ++++++++++++++-- tests/Parameters_test.m | 14 +++++++++++++- tests/{ => helpers}/+dat/paths.m | 0 .../expDefinitions/advancedChoiceWorld.m | 0 .../advancedChoiceWorldExpPanel.m | 0 .../advancedChoiceWorld_parameters.mat | Bin .../expDefinitions/choiceWorld.m | 0 .../expDefinitions/choiceWorld_parameters.mat | Bin ...rformanceTest.m => inferParams_perfTest.m} | 6 +++--- .../{inferParamsTest.m => inferParams_test.m} | 2 +- 13 files changed, 46 insertions(+), 14 deletions(-) delete mode 100644 .github/config.yml rename tests/{ParamEditor_perf.m => ParamEditor_perfTest.m} (93%) rename tests/{ => helpers}/+dat/paths.m (100%) rename tests/{ => helpers}/expDefinitions/advancedChoiceWorld.m (100%) rename tests/{ => helpers}/expDefinitions/advancedChoiceWorldExpPanel.m (100%) rename tests/{ => helpers}/expDefinitions/advancedChoiceWorld_parameters.mat (100%) rename tests/{ => helpers}/expDefinitions/choiceWorld.m (100%) rename tests/{ => helpers}/expDefinitions/choiceWorld_parameters.mat (100%) rename tests/{inferParamsPerformanceTest.m => inferParams_perfTest.m} (86%) rename tests/{inferParamsTest.m => inferParams_test.m} (98%) diff --git a/+eui/ConditionPanel.m b/+eui/ConditionPanel.m index 79bba69b..55fed39f 100644 --- a/+eui/ConditionPanel.m +++ b/+eui/ConditionPanel.m @@ -10,8 +10,6 @@ ConditionTable % Minimum UI Panel width allowed. See also EUI.PARAMEDITOR/ONRESIZE MinWidth = 80 -% MaxWidth = 140 -% Margin = 4 % Handle to parent UI container UIPanel % Handle to UI container for buttons diff --git a/.github/config.yml b/.github/config.yml deleted file mode 100644 index c41e8225..00000000 --- a/.github/config.yml +++ /dev/null @@ -1,2 +0,0 @@ -todo: - keyword: ['@todo','TODO','@fixme','FIXME'] diff --git a/tests/ParamEditor_perf.m b/tests/ParamEditor_perfTest.m similarity index 93% rename from tests/ParamEditor_perf.m rename to tests/ParamEditor_perfTest.m index 5afdefb3..f52102b8 100644 --- a/tests/ParamEditor_perf.m +++ b/tests/ParamEditor_perfTest.m @@ -1,4 +1,4 @@ -classdef ParamEditor_perf < matlab.perftest.TestCase +classdef ParamEditor_perfTest < matlab.perftest.TestCase properties % Figure visibility setting before running tests @@ -19,7 +19,19 @@ function setup(testCase) testCase.FigureVisibleDefault = get(0,'DefaultFigureVisible'); set(0,'DefaultFigureVisible','off'); testCase.addTeardown(@set, 0, 'DefaultFigureVisible', testCase.FigureVisibleDefault); - + + % add a teardown for pre-test path: + % (first arg is fixture instance (i.e. environment - when to + % teardown (when this is out of scope))) + % (second arg is tearDownFcn (i.e. what to execute during teardown)) + p = path; + testCase.addTeardown(@path,p); + + % add the the 'helpers' folder in the 'tests' folder for using the + % 'tests' folder's version of 'dat.paths' + curpath = fileparts(mfilename('fullpath')); + addpath([curpath '\helpers']) + % Loads validation data % Graph data is a cell array where each element is the graph number % (1:3) and within each element is a cell of X- and Y- axis values @@ -27,7 +39,7 @@ function setup(testCase) testCase.Parameters = exp.choiceWorldParams; % Check paths file - assert(endsWith(which('dat.paths'), fullfile('tests','+dat','paths.m'))); + assert(endsWith(which('dat.paths'), fullfile('tests', 'helpers', '+dat', 'paths.m'))); % Create stand-alone panel testCase.ParamEditor = eui.ParamEditor; testCase.Figure = gcf(); diff --git a/tests/ParamEditor_test.m b/tests/ParamEditor_test.m index 31f44c3f..7201bfbb 100644 --- a/tests/ParamEditor_test.m +++ b/tests/ParamEditor_test.m @@ -25,7 +25,19 @@ function setup(testCase) testCase.FigureVisibleDefault = get(0,'DefaultFigureVisible'); set(0,'DefaultFigureVisible','off'); testCase.addTeardown(@set, 0, 'DefaultFigureVisible', testCase.FigureVisibleDefault); - + + % add a teardown for pre-test path: + % (first arg is fixture instance (i.e. environment - when to + % teardown (when this is out of scope))) + % (second arg is tearDownFcn (i.e. what to execute during teardown)) + p = path; + testCase.addTeardown(@path,p); + + % add the the 'helpers' folder in the 'tests' folder for using the + % 'tests' folder's version of 'dat.paths' + curpath = fileparts(mfilename('fullpath')); + addpath([curpath '\helpers']) + % Loads validation data % Graph data is a cell array where each element is the graph number % (1:3) and within each element is a cell of X- and Y- axis values @@ -33,7 +45,7 @@ function setup(testCase) testCase.Parameters = exp.choiceWorldParams; % Check paths file - assert(endsWith(which('dat.paths'), fullfile('tests','+dat','paths.m'))); + assert(endsWith(which('dat.paths'), fullfile('tests', 'helpers', '+dat', 'paths.m'))); % Create stand-alone panel testCase.ParamEditor = eui.ParamEditor; testCase.Figure = gcf(); diff --git a/tests/Parameters_test.m b/tests/Parameters_test.m index 4241b314..c9bb6f05 100644 --- a/tests/Parameters_test.m +++ b/tests/Parameters_test.m @@ -33,8 +33,20 @@ function loadData(testCase) end function setupClass(testCase) + % add a teardown for pre-test path: + % (first arg is fixture instance (i.e. environment - when to + % teardown (when this is out of scope))) + % (second arg is tearDownFcn (i.e. what to execute during teardown)) + p = path; + testCase.addTeardown(@path,p); + + % add the the 'helpers' folder in the 'tests' folder for using the + % 'tests' folder's version of 'dat.paths' + curpath = fileparts(mfilename('fullpath')); + addpath([curpath '\helpers']) + % Check paths file - assert(endsWith(which('dat.paths'), fullfile('tests','+dat','paths.m'))); + assert(endsWith(which('dat.paths'), fullfile('tests', 'helpers', '+dat', 'paths.m'))); % Create stand-alone panel testCase.Parameters = exp.Parameters(); testCase.fatalAssertTrue(isa(testCase.Parameters, 'exp.Parameters')) diff --git a/tests/+dat/paths.m b/tests/helpers/+dat/paths.m similarity index 100% rename from tests/+dat/paths.m rename to tests/helpers/+dat/paths.m diff --git a/tests/expDefinitions/advancedChoiceWorld.m b/tests/helpers/expDefinitions/advancedChoiceWorld.m similarity index 100% rename from tests/expDefinitions/advancedChoiceWorld.m rename to tests/helpers/expDefinitions/advancedChoiceWorld.m diff --git a/tests/expDefinitions/advancedChoiceWorldExpPanel.m b/tests/helpers/expDefinitions/advancedChoiceWorldExpPanel.m similarity index 100% rename from tests/expDefinitions/advancedChoiceWorldExpPanel.m rename to tests/helpers/expDefinitions/advancedChoiceWorldExpPanel.m diff --git a/tests/expDefinitions/advancedChoiceWorld_parameters.mat b/tests/helpers/expDefinitions/advancedChoiceWorld_parameters.mat similarity index 100% rename from tests/expDefinitions/advancedChoiceWorld_parameters.mat rename to tests/helpers/expDefinitions/advancedChoiceWorld_parameters.mat diff --git a/tests/expDefinitions/choiceWorld.m b/tests/helpers/expDefinitions/choiceWorld.m similarity index 100% rename from tests/expDefinitions/choiceWorld.m rename to tests/helpers/expDefinitions/choiceWorld.m diff --git a/tests/expDefinitions/choiceWorld_parameters.mat b/tests/helpers/expDefinitions/choiceWorld_parameters.mat similarity index 100% rename from tests/expDefinitions/choiceWorld_parameters.mat rename to tests/helpers/expDefinitions/choiceWorld_parameters.mat diff --git a/tests/inferParamsPerformanceTest.m b/tests/inferParams_perfTest.m similarity index 86% rename from tests/inferParamsPerformanceTest.m rename to tests/inferParams_perfTest.m index a6a5ff86..828dc660 100644 --- a/tests/inferParamsPerformanceTest.m +++ b/tests/inferParams_perfTest.m @@ -1,4 +1,4 @@ -function tests = inferParamsPerformanceTest +function tests = inferParams_perfTest % expDefPath = fullfile(fileparts(which('addRigboxPaths')), 'tests', 'expDefinitions'); % def1 = fullfile(expDefPath, 'advancedChoiceWorld.m'); % def2 = fullfile(expDefPath, 'choiceWorld.m'); @@ -9,13 +9,13 @@ end function testInferParams(testCase) -expDefPath = fullfile(fileparts(which('addRigboxPaths')), 'tests', 'expDefinitions'); +expDefPath = fullfile(fileparts(which('addRigboxPaths')), 'tests', 'helpers', 'expDefinitions'); def1 = fullfile(expDefPath, 'advancedChoiceWorld.m'); exp.inferParameters(def1); end function testInferParams2(testCase) -expDefPath = fullfile(fileparts(which('addRigboxPaths')), 'tests', 'expDefinitions'); +expDefPath = fullfile(fileparts(which('addRigboxPaths')), 'tests', 'helpers', 'expDefinitions'); def2 = fullfile(expDefPath, 'choiceWorld.m'); exp.inferParameters(def2); end \ No newline at end of file diff --git a/tests/inferParamsTest.m b/tests/inferParams_test.m similarity index 98% rename from tests/inferParamsTest.m rename to tests/inferParams_test.m index 1b34cb19..3b558465 100644 --- a/tests/inferParamsTest.m +++ b/tests/inferParams_test.m @@ -1,5 +1,5 @@ %inferParams test -expDefPath = fullfile(fileparts(which('addRigboxPaths')), 'tests', 'expDefinitions'); +expDefPath = fullfile(fileparts(which('addRigboxPaths')), 'tests', 'helpers', 'expDefinitions'); % preconditions parameters = exp.inferParameters(@nop); From 4c9b67f38e8df0d8ccf5159a161d0fd5d333454c Mon Sep 17 00:00:00 2001 From: jaib1 <j.bhagat@ucl.ac.uk> Date: Tue, 19 Mar 2019 14:38:39 +0000 Subject: [PATCH 325/393] renamed folder 'helpers' to 'fixtures'; added 'SharedTestFixtures' to test classes --- +eui/ConditionPanel.m | 2 +- +eui/FieldPanel.m | 2 +- +eui/ParamEditor.m | 8 +- tests/ParamEditor_perfTest.m | 46 ++++++------ tests/ParamEditor_test.m | 70 ++++++++++-------- tests/Parameters_test.m | 21 ++---- tests/{helpers => fixtures}/+dat/paths.m | 0 .../expDefinitions/advancedChoiceWorld.m | 0 .../advancedChoiceWorldExpPanel.m | 0 .../advancedChoiceWorld_parameters.mat | Bin .../expDefinitions/choiceWorld.m | 0 .../expDefinitions/choiceWorld_parameters.mat | Bin tests/inferParams_perfTest.m | 4 +- tests/inferParams_test.m | 2 +- 14 files changed, 76 insertions(+), 79 deletions(-) rename tests/{helpers => fixtures}/+dat/paths.m (100%) rename tests/{helpers => fixtures}/expDefinitions/advancedChoiceWorld.m (100%) rename tests/{helpers => fixtures}/expDefinitions/advancedChoiceWorldExpPanel.m (100%) rename tests/{helpers => fixtures}/expDefinitions/advancedChoiceWorld_parameters.mat (100%) rename tests/{helpers => fixtures}/expDefinitions/choiceWorld.m (100%) rename tests/{helpers => fixtures}/expDefinitions/choiceWorld_parameters.mat (100%) diff --git a/+eui/ConditionPanel.m b/+eui/ConditionPanel.m index 55fed39f..88247815 100644 --- a/+eui/ConditionPanel.m +++ b/+eui/ConditionPanel.m @@ -136,7 +136,7 @@ function clear(obj) function delete(obj) % DELETE Deletes the UI container - % Called when this object or its parant ParamEditor is deleted + % Called when this object or its parent ParamEditor is deleted % See also CLEAR delete(obj.UIPanel); end diff --git a/+eui/FieldPanel.m b/+eui/FieldPanel.m index ce950813..d0e48c37 100644 --- a/+eui/FieldPanel.m +++ b/+eui/FieldPanel.m @@ -156,7 +156,7 @@ function makeConditional(obj, name) function delete(obj) % DELETE Deletes the UI container - % Called when this object or its parant ParamEditor is deleted + % Called when this object or its parent ParamEditor is deleted % See also CLEAR delete(obj.UIPanel); end diff --git a/+eui/ParamEditor.m b/+eui/ParamEditor.m index cbe86e75..85d62b6c 100644 --- a/+eui/ParamEditor.m +++ b/+eui/ParamEditor.m @@ -22,7 +22,7 @@ % the trial conditions within a ui table ConditionalUI % Handle to the parent container for the ParamEditor. If constructor - % called with no parant input, then this will be a figure handle, the + % called with no parent input, then this will be a figure handle, the % same as Root Parent % Handle to the figure within which the ParamEditor is displayed @@ -45,8 +45,8 @@ function obj = ParamEditor(pars, parent) % PARAMEDITOR GUI for visualizing and editing experiment parameters % The input pars is expected to be an instance of the exp.Parameters - % class. Parant is a handle to a parent figure or UI Panel. If no - % parant is given, the editor is created in a new figure. + % class. Parent is a handle to a parent figure or UI Panel. If no + % parent is given, the editor is created in a new figure. % % See also EUI.FIELDPANEL, EUI.CONDITIONPANEL if nargin == 0; pars = []; end @@ -81,7 +81,7 @@ function delete(obj) % DELETE Deletes all panels - % Called when the ParamEditor object is deleted or its parant figure + % Called when the ParamEditor object is deleted or its parent figure % is closed. Deletes all UI elements and data. % See also CLEAR delete(obj.GlobalUI); diff --git a/tests/ParamEditor_perfTest.m b/tests/ParamEditor_perfTest.m index f52102b8..eb6cbbf9 100644 --- a/tests/ParamEditor_perfTest.m +++ b/tests/ParamEditor_perfTest.m @@ -1,4 +1,6 @@ -classdef ParamEditor_perfTest < matlab.perftest.TestCase +classdef (SharedTestFixtures={matlab.unittest.fixtures.PathFixture(... +[fileparts(mfilename('fullpath')) '\fixtures'])})... % add 'fixtures' folder as test fixture +ParamEditor_perfTest < matlab.perftest.TestCase properties % Figure visibility setting before running tests @@ -18,19 +20,8 @@ function setup(testCase) % Hide figures and add teardown function to restore settings testCase.FigureVisibleDefault = get(0,'DefaultFigureVisible'); set(0,'DefaultFigureVisible','off'); - testCase.addTeardown(@set, 0, 'DefaultFigureVisible', testCase.FigureVisibleDefault); - - % add a teardown for pre-test path: - % (first arg is fixture instance (i.e. environment - when to - % teardown (when this is out of scope))) - % (second arg is tearDownFcn (i.e. what to execute during teardown)) - p = path; - testCase.addTeardown(@path,p); - - % add the the 'helpers' folder in the 'tests' folder for using the - % 'tests' folder's version of 'dat.paths' - curpath = fileparts(mfilename('fullpath')); - addpath([curpath '\helpers']) + testCase.addTeardown(@set, 0,... + 'DefaultFigureVisible', testCase.FigureVisibleDefault); % Loads validation data % Graph data is a cell array where each element is the graph number @@ -39,7 +30,8 @@ function setup(testCase) testCase.Parameters = exp.choiceWorldParams; % Check paths file - assert(endsWith(which('dat.paths'), fullfile('tests', 'helpers', '+dat', 'paths.m'))); + assert(endsWith(which('dat.paths'),... + fullfile('tests', 'fixtures', '+dat', 'paths.m'))); % Create stand-alone panel testCase.ParamEditor = eui.ParamEditor; testCase.Figure = gcf(); @@ -62,7 +54,8 @@ function buildParams(testCase) PE.buildUI(pars); % Number of global parameters: find all text labels nGlobalLabels = numel(findobj(testCase.Figure, 'Style', 'text')); - nGlobalInput = numel(findobj(testCase.Figure, 'Style', 'checkbox', '-or', 'Style', 'edit')); + nGlobalInput = numel(findobj(testCase.Figure,... + 'Style', 'checkbox', '-or', 'Style', 'edit')); % Ensure all global params have UI input and label assert(nGlobalLabels == numel(PE.Parameters.GlobalNames)) assert(nGlobalInput == numel(PE.Parameters.GlobalNames)) @@ -99,7 +92,8 @@ function test_makeConditional(testCase) PE = testCase.ParamEditor; % Number of global parameters: find all text labels gLabels = @()findobj(testCase.Figure, 'Style', 'text'); - gInputs = @()findobj(testCase.Figure, 'Style', 'checkbox', '-or', 'Style', 'edit'); + gInputs = @()findobj(testCase.Figure, 'Style', 'checkbox',... + '-or', 'Style', 'edit'); nGlobalLabels = numel(gLabels()); nGlobalInputs = numel(gInputs()); tableSz = size(testCase.Table.Data); @@ -140,10 +134,12 @@ function test_newCondition(testCase) % Set the focused object to one of the parameter labels set(testCase.Figure, 'CurrentObject', ... findobj(testCase.Figure, 'Tag', 'experimentFun')) - feval(pick(findobj(testCase.Figure, 'Text', 'Make Conditional'), 'MenuSelectedFcn')) + feval(pick(findobj(testCase.Figure, 'Text', 'Make Conditional'),... + 'MenuSelectedFcn')) % Retrieve function handle for new condition - fn = pick(findobj(testCase.Figure, 'String', 'New condition'), 'Callback'); + fn = pick(findobj(testCase.Figure, 'String', 'New condition'),... + 'Callback'); testCase.startMeasuring(); fn() testCase.stopMeasuring(); @@ -166,7 +162,8 @@ function test_deleteCondition(testCase) selection_fn([],event) % Retrieve function handle for delete condition - callback_fn = pick(findobj(testCase.Figure, 'String', 'Delete condition'), 'Callback'); + callback_fn = pick(findobj(testCase.Figure,... + 'String', 'Delete condition'), 'Callback'); testCase.startMeasuring(); callback_fn() testCase.stopMeasuring(); @@ -189,7 +186,8 @@ function test_globaliseParam(testCase) selection_fn([],event) % Retrieve function handle for new condition - callback_fn = pick(findobj(testCase.Figure, 'String', 'Globalise parameter'), 'Callback'); + callback_fn = pick(findobj(testCase.Figure, 'String',... + 'Globalise parameter'), 'Callback'); testCase.startMeasuring(); callback_fn() testCase.stopMeasuring(); @@ -208,7 +206,8 @@ function test_paramEdits(testCase) % Retreive all global parameters labels and input controls gLabels = findobj(testCase.Figure, 'Style', 'text'); - gInputs = findobj(testCase.Figure, 'Style', 'checkbox', '-or', 'Style', 'edit'); + gInputs = findobj(testCase.Figure, 'Style', 'checkbox',... + '-or', 'Style', 'edit'); % Test editing global param, 'edit' UI idx = find(strcmp({gInputs.Style}, 'edit'), 1); @@ -226,7 +225,8 @@ function test_paramEdits(testCase) testCase.verifyEqual(gLabels(idx).ForegroundColor, [1 0 0], ... 'Unexpected label colour') % Verify change in underlying param struct - par = strcmpi(PE.Parameters.GlobalNames, strrep(gLabels(idx).String, ' ', '')); + par = strcmpi(PE.Parameters.GlobalNames,... + strrep(gLabels(idx).String, ' ', '')); testCase.verifyEqual(PE.Parameters.Struct.(PE.Parameters.GlobalNames{par}), 666, ... 'UI edit failed to update parameters struct') diff --git a/tests/ParamEditor_test.m b/tests/ParamEditor_test.m index 7201bfbb..783e70f0 100644 --- a/tests/ParamEditor_test.m +++ b/tests/ParamEditor_test.m @@ -1,4 +1,6 @@ -classdef ParamEditor_test < matlab.unittest.TestCase +classdef (SharedTestFixtures={matlab.unittest.fixtures.PathFixture(... +[fileparts(mfilename('fullpath')) '\fixtures'])})... % add 'fixtures' folder as test fixture + ParamEditor_test < matlab.unittest.TestCase properties % Figure visibility setting before running tests @@ -24,19 +26,8 @@ function setup(testCase) % Hide figures and add teardown function to restore settings testCase.FigureVisibleDefault = get(0,'DefaultFigureVisible'); set(0,'DefaultFigureVisible','off'); - testCase.addTeardown(@set, 0, 'DefaultFigureVisible', testCase.FigureVisibleDefault); - - % add a teardown for pre-test path: - % (first arg is fixture instance (i.e. environment - when to - % teardown (when this is out of scope))) - % (second arg is tearDownFcn (i.e. what to execute during teardown)) - p = path; - testCase.addTeardown(@path,p); - - % add the the 'helpers' folder in the 'tests' folder for using the - % 'tests' folder's version of 'dat.paths' - curpath = fileparts(mfilename('fullpath')); - addpath([curpath '\helpers']) + testCase.addTeardown(@set, 0,... + 'DefaultFigureVisible', testCase.FigureVisibleDefault); % Loads validation data % Graph data is a cell array where each element is the graph number @@ -45,7 +36,8 @@ function setup(testCase) testCase.Parameters = exp.choiceWorldParams; % Check paths file - assert(endsWith(which('dat.paths'), fullfile('tests', 'helpers', '+dat', 'paths.m'))); + assert(endsWith(which('dat.paths'),... + fullfile('tests', 'fixtures', '+dat', 'paths.m'))); % Create stand-alone panel testCase.ParamEditor = eui.ParamEditor; testCase.Figure = gcf(); @@ -68,7 +60,8 @@ function buildParams(testCase) PE.buildUI(pars); % Number of global parameters: find all text labels nGlobalLabels = numel(findobj(testCase.Figure, 'Style', 'text')); - nGlobalInput = numel(findobj(testCase.Figure, 'Style', 'checkbox', '-or', 'Style', 'edit')); + nGlobalInput = numel(findobj(testCase.Figure, 'Style', 'checkbox',... + '-or', 'Style', 'edit')); % Ensure all global params have UI input and label testCase.fatalAssertTrue(nGlobalLabels == numel(PE.Parameters.GlobalNames)) testCase.fatalAssertTrue(nGlobalInput == numel(PE.Parameters.GlobalNames)) @@ -95,7 +88,8 @@ function test_makeConditional(testCase) testCase.assertTrue(~testCase.Changed, 'Changed flag incorrect') % Number of global parameters: find all text labels gLabels = @()findobj(testCase.Figure, 'Style', 'text'); - gInputs = @()findobj(testCase.Figure, 'Style', 'checkbox', '-or', 'Style', 'edit'); + gInputs = @()findobj(testCase.Figure, 'Style', 'checkbox',... + '-or', 'Style', 'edit'); nGlobalLabels = numel(gLabels()); nGlobalInputs = numel(gInputs()); tableSz = size(testCase.Table.Data); @@ -140,7 +134,8 @@ function test_paramValue2Control(testCase) PE = testCase.ParamEditor; % Test function handle testCase.verifyEqual(PE.paramValue2Control(@nop), 'nop') - testCase.verifyEqual(PE.controlValue2Param(@nop, 'identity'), @identity) + testCase.verifyEqual(PE.controlValue2Param(@nop, 'identity'),... + @identity) % Test logical array testCase.verifyEqual(PE.paramValue2Control(true(1,2)), true(1,2)) @@ -163,7 +158,6 @@ function test_paramValue2Control(testCase) % Test string data % TODO Outcome will change in near future testCase.verifyEqual(PE.paramValue2Control("hello"), 'hello') -% testCase.verifyEqual(PE.controlValue2Param("hello", "Goodbye"), "Goodbye") % Test numeric data testCase.verifyEqual(PE.paramValue2Control(pi), '3.1416') @@ -185,14 +179,17 @@ function test_newCondition(testCase) % Set the focused object to one of the parameter labels set(testCase.Figure, 'CurrentObject', ... findobj(testCase.Figure, 'Tag', 'experimentFun')) - feval(pick(findobj(testCase.Figure, 'Text', 'Make Conditional'), 'MenuSelectedFcn')) + feval(pick(findobj(testCase.Figure, 'Text', 'Make Conditional'),... + 'MenuSelectedFcn')) % Reset Changed flag testCase.Changed = false; % Retrieve function handle for new condition - fn = pick(findobj(testCase.Figure, 'String', 'New condition'), 'Callback'); - testCase.verifyWarningFree(fn, 'Warning encountered adding trial condition') + fn = pick(findobj(testCase.Figure, 'String', 'New condition'),... + 'Callback'); + testCase.verifyWarningFree(fn,... + 'Warning encountered adding trial condition') % Verify change in table data testCase.verifyEqual(size(testCase.Table.Data, 1), tableRows+1, ... @@ -222,15 +219,17 @@ function test_deleteCondition(testCase) selection_fn([],event) % Retrieve function handle for delete condition - callback_fn = pick(findobj(testCase.Figure, 'String', 'Delete condition'), 'Callback'); - testCase.verifyWarningFree(callback_fn, 'Warning encountered deleting trial conditions') + callback_fn = pick(findobj(testCase.Figure,... + 'String', 'Delete condition'), 'Callback'); + testCase.verifyWarningFree(callback_fn,... + 'Warning encountered deleting trial conditions') % Verify change in table data testCase.verifyEqual(size(testCase.Table.Data, 1), tableRows-5, ... 'Unexpected number of trial conditions') % Verify change in Parameters object for conditional - testCase.assertEqual(size(testCase.Table.Data), ... + testCase.assertEqual(size(testCase.Table.Data),... [PE.Parameters.numTrialConditions, numel(PE.Parameters.TrialSpecificNames)]) % Verify Changed event triggered @@ -241,7 +240,8 @@ function test_deleteCondition(testCase) event.Indices = [(1:PE.Parameters.numTrialConditions-1)' ... ones(PE.Parameters.numTrialConditions-1,1)]; selection_fn([],event) - testCase.verifyWarningFree(callback_fn, 'Warning encountered deleting trial conditions') + testCase.verifyWarningFree(callback_fn,... + 'Warning encountered deleting trial conditions') % Verify change in table data testCase.verifyEmpty(testCase.Table.Data, ... @@ -268,8 +268,10 @@ function test_globaliseParam(testCase) selection_fn([],event) % Retrieve function handle for new condition - callback_fn = pick(findobj(testCase.Figure, 'String', 'Globalise parameter'), 'Callback'); - testCase.verifyWarningFree(callback_fn, 'Warning encountered globalising params') + callback_fn = pick(findobj(testCase.Figure,... + 'String', 'Globalise parameter'), 'Callback'); + testCase.verifyWarningFree(callback_fn,... + 'Warning encountered globalising params') % Verify change in table data testCase.verifyEqual(size(testCase.Table.Data,2), tableCols-1, ... @@ -288,7 +290,8 @@ function test_globaliseParam(testCase) event.Indices = [ones(n,1), (1:n)']; numRepeatsTotal = sum(PE.Parameters.Struct.numRepeats); selection_fn([],event) - testCase.verifyWarningFree(callback_fn, 'Warning encountered globalising params') + testCase.verifyWarningFree(callback_fn,... + 'Warning encountered globalising params') % Verify numRepeats is global testCase.verifyTrue(~PE.Parameters.isTrialSpecific('numRepeats'), ... @@ -309,7 +312,8 @@ function test_globaliseParam(testCase) n = numel(PE.Parameters.TrialSpecificNames); event.Indices = [ones(n-1,1), (2:n)']; selection_fn([],event) - testCase.verifyWarningFree(callback_fn, 'Warning encountered globalising params') + testCase.verifyWarningFree(callback_fn,... + 'Warning encountered globalising params') % Verify change in table data testCase.verifyEqual(size(testCase.Table.Data,2), 1, ... @@ -326,7 +330,8 @@ function test_paramEdits(testCase) % Retreive all global parameters labels and input controls gLabels = findobj(testCase.Figure, 'Style', 'text'); - gInputs = findobj(testCase.Figure, 'Style', 'checkbox', '-or', 'Style', 'edit'); + gInputs = findobj(testCase.Figure, 'Style', 'checkbox',... + '-or', 'Style', 'edit'); % Test editing global param, 'edit' UI idx = find(strcmp({gInputs.Style}, 'edit'), 1); @@ -409,7 +414,8 @@ function test_interactivity(testCase) % Find all disabled controls disabled = findobj(testCase.Figure, 'Enable', 'off'); % Verify buttons greyed out - testCase.verifyEmpty(disabled, 'Unexpected number of disabled ui elements') + testCase.verifyEmpty(disabled,... + 'Unexpected number of disabled ui elements') % Test disabling param editor altogether PE.Enable = false; diff --git a/tests/Parameters_test.m b/tests/Parameters_test.m index c9bb6f05..2a22eace 100644 --- a/tests/Parameters_test.m +++ b/tests/Parameters_test.m @@ -1,4 +1,6 @@ -classdef Parameters_test < matlab.unittest.TestCase +classdef (SharedTestFixtures={matlab.unittest.fixtures.PathFixture(... +[fileparts(mfilename('fullpath')) '\fixtures'])})... % add 'fixtures' folder as test fixture + Parameters_test < matlab.unittest.TestCase properties % Parameters object @@ -32,21 +34,10 @@ function loadData(testCase) 'functionParam', @(pars,rig)exp.configureChoiceExperiment(exp.ChoiceWorld,pars,rig)); end - function setupClass(testCase) - % add a teardown for pre-test path: - % (first arg is fixture instance (i.e. environment - when to - % teardown (when this is out of scope))) - % (second arg is tearDownFcn (i.e. what to execute during teardown)) - p = path; - testCase.addTeardown(@path,p); - - % add the the 'helpers' folder in the 'tests' folder for using the - % 'tests' folder's version of 'dat.paths' - curpath = fileparts(mfilename('fullpath')); - addpath([curpath '\helpers']) - + function setupClass(testCase) % Check paths file - assert(endsWith(which('dat.paths'), fullfile('tests', 'helpers', '+dat', 'paths.m'))); + assert(endsWith(which('dat.paths'), fullfile('tests', 'fixtures',... + '+dat', 'paths.m'))); % Create stand-alone panel testCase.Parameters = exp.Parameters(); testCase.fatalAssertTrue(isa(testCase.Parameters, 'exp.Parameters')) diff --git a/tests/helpers/+dat/paths.m b/tests/fixtures/+dat/paths.m similarity index 100% rename from tests/helpers/+dat/paths.m rename to tests/fixtures/+dat/paths.m diff --git a/tests/helpers/expDefinitions/advancedChoiceWorld.m b/tests/fixtures/expDefinitions/advancedChoiceWorld.m similarity index 100% rename from tests/helpers/expDefinitions/advancedChoiceWorld.m rename to tests/fixtures/expDefinitions/advancedChoiceWorld.m diff --git a/tests/helpers/expDefinitions/advancedChoiceWorldExpPanel.m b/tests/fixtures/expDefinitions/advancedChoiceWorldExpPanel.m similarity index 100% rename from tests/helpers/expDefinitions/advancedChoiceWorldExpPanel.m rename to tests/fixtures/expDefinitions/advancedChoiceWorldExpPanel.m diff --git a/tests/helpers/expDefinitions/advancedChoiceWorld_parameters.mat b/tests/fixtures/expDefinitions/advancedChoiceWorld_parameters.mat similarity index 100% rename from tests/helpers/expDefinitions/advancedChoiceWorld_parameters.mat rename to tests/fixtures/expDefinitions/advancedChoiceWorld_parameters.mat diff --git a/tests/helpers/expDefinitions/choiceWorld.m b/tests/fixtures/expDefinitions/choiceWorld.m similarity index 100% rename from tests/helpers/expDefinitions/choiceWorld.m rename to tests/fixtures/expDefinitions/choiceWorld.m diff --git a/tests/helpers/expDefinitions/choiceWorld_parameters.mat b/tests/fixtures/expDefinitions/choiceWorld_parameters.mat similarity index 100% rename from tests/helpers/expDefinitions/choiceWorld_parameters.mat rename to tests/fixtures/expDefinitions/choiceWorld_parameters.mat diff --git a/tests/inferParams_perfTest.m b/tests/inferParams_perfTest.m index 828dc660..aab2306b 100644 --- a/tests/inferParams_perfTest.m +++ b/tests/inferParams_perfTest.m @@ -9,13 +9,13 @@ end function testInferParams(testCase) -expDefPath = fullfile(fileparts(which('addRigboxPaths')), 'tests', 'helpers', 'expDefinitions'); +expDefPath = fullfile(fileparts(which('addRigboxPaths')), 'tests', 'fixtures', 'expDefinitions'); def1 = fullfile(expDefPath, 'advancedChoiceWorld.m'); exp.inferParameters(def1); end function testInferParams2(testCase) -expDefPath = fullfile(fileparts(which('addRigboxPaths')), 'tests', 'helpers', 'expDefinitions'); +expDefPath = fullfile(fileparts(which('addRigboxPaths')), 'tests', 'fixtures', 'expDefinitions'); def2 = fullfile(expDefPath, 'choiceWorld.m'); exp.inferParameters(def2); end \ No newline at end of file diff --git a/tests/inferParams_test.m b/tests/inferParams_test.m index 3b558465..bf444632 100644 --- a/tests/inferParams_test.m +++ b/tests/inferParams_test.m @@ -1,5 +1,5 @@ %inferParams test -expDefPath = fullfile(fileparts(which('addRigboxPaths')), 'tests', 'helpers', 'expDefinitions'); +expDefPath = fullfile(fileparts(which('addRigboxPaths')), 'tests', 'fixtures', 'expDefinitions'); % preconditions parameters = exp.inferParameters(@nop); From ef551b9faea0508dff2f076d69963a8e68c5641f Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 19 Mar 2019 17:13:37 +0200 Subject: [PATCH 326/393] Documentation for launchSubjectURL --- +eui/AlyxPanel.m | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index 45dc0cae..db784bae 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -442,6 +442,10 @@ function launchSessionURL(obj) end function launchSubjectURL(obj) + % LAUNCHSUBJECTURL Launch the Webpage for the current subject + % Launches Web page in the default Web browser. + % + % See also LAUNCHSESSIONURL ai = obj.AlyxInstance; if ai.IsLoggedIn s = ai.getData(ai.makeEndpoint(['subjects/' obj.Subject])); From 5d28fb18a986e56d565e89fd39056f24fe07269f Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 19 Mar 2019 17:20:50 +0200 Subject: [PATCH 327/393] HOTFIX: double-check experiment isn't already running before starting another --- +srv/expServer.m | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/+srv/expServer.m b/+srv/expServer.m index 7af7d14c..2a6bf091 100644 --- a/+srv/expServer.m +++ b/+srv/expServer.m @@ -186,6 +186,15 @@ function handleMessage(id, data, host) case 'run' % exp run request [expRef, preDelay, postDelay, AlyxInstance] = args{:}; + % assert that experiment not already running + if ~isempty(experiment) + failMsg = sprintf(... + 'Failed because another experiment (ref ''%s'') running', ... + experiment.Data.expRef); + log(failMsg); + communicator.send(id, {'fail', expRef, failMsg}); + return + end if isempty(AlyxInstance); AlyxInstance = Alyx('',''); end AlyxInstance.Headless = true; % Supress all dialog prompts if dat.expExists(expRef) From e0eca9edd6b293cbab96d54d1a6b701b74d83d94 Mon Sep 17 00:00:00 2001 From: Jai Bhagat <j.bhagat@ucl.ac.uk> Date: Tue, 19 Mar 2019 16:42:36 +0000 Subject: [PATCH 328/393] HOTFIX: 'ExpRunnning' changed to 'ExpRunning' --- +eui/MControl.m | 2 +- +srv/RemoteRig.m | 4 ++-- +srv/StimulusControl.m | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/+eui/MControl.m b/+eui/MControl.m index 0c432533..b46dcd96 100644 --- a/+eui/MControl.m +++ b/+eui/MControl.m @@ -375,7 +375,7 @@ function rigConnected(obj, rig, ~) % See also REMOTERIGCHANGED, SRV.STIMULUSCONTROL, EUI.EXPPANEL % If rig is connected check no experiments are running... - expRef = rig.ExpRunnning; % returns expRef if running + expRef = rig.ExpRunning; % returns expRef if running if expRef % error('Experiment %s already running of %s', expDef, rig.Name) choice = questdlg(['Attention: An experiment is already running on ', rig.Name], ... diff --git a/+srv/RemoteRig.m b/+srv/RemoteRig.m index 2f04e118..d847b562 100644 --- a/+srv/RemoteRig.m +++ b/+srv/RemoteRig.m @@ -29,7 +29,7 @@ %active services on the rig, 'active' if any services are currently %running Status - ExpRunnning %Reference of currently running experiment, if any/known + ExpRunning %Reference of currently running experiment, if any/known end events @@ -120,7 +120,7 @@ function refresh(obj) end end - function value = get.ExpRunnning(obj) + function value = get.ExpRunning(obj) value = []; % default to empty means none if obj.pConnected r = obj.exchange({'status'}); diff --git a/+srv/StimulusControl.m b/+srv/StimulusControl.m index 8147b603..ea5de8b3 100644 --- a/+srv/StimulusControl.m +++ b/+srv/StimulusControl.m @@ -30,7 +30,7 @@ %active services on the rig, 'active' if any services are currently %running Status - ExpRunnning %Reference of currently running experiment, if any/known + ExpRunning %Reference of currently running experiment, if any/known end properties (Transient, SetAccess = protected, Hidden) @@ -92,7 +92,7 @@ end end - function value = get.ExpRunnning(obj) + function value = get.ExpRunning(obj) value = []; % default to empty means none if connected(obj) r = obj.exchange({'status'}); From c5ac87a6a09112d49175d9239b6a0ddae02fb1a2 Mon Sep 17 00:00:00 2001 From: Jai Bhagat <j.bhagat@ucl.ac.uk> Date: Tue, 19 Mar 2019 17:09:25 +0000 Subject: [PATCH 329/393] HOTFIX: updated 'eui.MControl/beginExp' to not run exp on rig already running exp --- +eui/MControl.m | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/+eui/MControl.m b/+eui/MControl.m index b46dcd96..1e35a67d 100644 --- a/+eui/MControl.m +++ b/+eui/MControl.m @@ -545,6 +545,10 @@ function beginExp(obj) % See also SRV.STIMULUSCONTROL, EUI.EXPPANEL, EUI.ALYXPANEL set([obj.BeginExpButton obj.RigOptionsButton], 'Enable', 'off'); % Grey out buttons rig = obj.RemoteRigs.Selected; % Find which rig is selected + if strcmpi(rig.Status, 'running') + obj.log('Failed because another experiment is running'); + return; + end % Save the current instance of Alyx so that eui.ExpPanel can register water to the correct account if ~obj.AlyxPanel.AlyxInstance.IsLoggedIn &&... ~strcmp(obj.NewExpSubject.Selected,'default') &&... From 866cd9d5e1cbe0a17c669ba0cdd7ea3594a39ec4 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 19 Mar 2019 17:20:50 +0200 Subject: [PATCH 330/393] HOTFIX: double-check experiment isn't already running before starting another --- +srv/expServer.m | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/+srv/expServer.m b/+srv/expServer.m index 7af7d14c..2a6bf091 100644 --- a/+srv/expServer.m +++ b/+srv/expServer.m @@ -186,6 +186,15 @@ function handleMessage(id, data, host) case 'run' % exp run request [expRef, preDelay, postDelay, AlyxInstance] = args{:}; + % assert that experiment not already running + if ~isempty(experiment) + failMsg = sprintf(... + 'Failed because another experiment (ref ''%s'') running', ... + experiment.Data.expRef); + log(failMsg); + communicator.send(id, {'fail', expRef, failMsg}); + return + end if isempty(AlyxInstance); AlyxInstance = Alyx('',''); end AlyxInstance.Headless = true; % Supress all dialog prompts if dat.expExists(expRef) From 177b4426dc30214fec82904070c98f38a2f32b94 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 20 Mar 2019 00:11:56 +0200 Subject: [PATCH 331/393] Added mocking framework for input dialogs, reorganized --- tests/{AlyxPanelTest.m => AlyxPanel_test.m} | 22 ++- tests/{ => fixtures}/+dat/paths.m | 0 tests/{ => fixtures}/data/viewSubjectData.mat | Bin tests/fixtures/util/MockDialog.m | 163 ++++++++++++++++++ tests/fixtures/util/inputdlg.m | 21 +++ tests/fixtures/util/questdlg.m | 5 + 6 files changed, 206 insertions(+), 5 deletions(-) rename tests/{AlyxPanelTest.m => AlyxPanel_test.m} (91%) rename tests/{ => fixtures}/+dat/paths.m (100%) rename tests/{ => fixtures}/data/viewSubjectData.mat (100%) create mode 100644 tests/fixtures/util/MockDialog.m create mode 100644 tests/fixtures/util/inputdlg.m create mode 100644 tests/fixtures/util/questdlg.m diff --git a/tests/AlyxPanelTest.m b/tests/AlyxPanel_test.m similarity index 91% rename from tests/AlyxPanelTest.m rename to tests/AlyxPanel_test.m index 935ff127..4cd80187 100644 --- a/tests/AlyxPanelTest.m +++ b/tests/AlyxPanel_test.m @@ -1,4 +1,7 @@ -classdef AlyxPanelTest < matlab.unittest.TestCase +classdef (SharedTestFixtures={ % add 'fixtures' folder as test fixture + matlab.unittest.fixtures.PathFixture('fixtures'),... + matlab.unittest.fixtures.PathFixture('util')})... + AlyxPanel_test < matlab.unittest.TestCase properties % Figure visibility setting before running tests @@ -35,14 +38,15 @@ function loadData(testCase) % Graph data is a cell array where each element is the graph number % (1:3) and within each element is a cell of X- and Y- axis values % respecively - load('data/viewSubjectData.mat', 'tableData', 'graphData') + testdata = fullfile('fixtures', 'data', 'viewSubjectData.mat'); + load(testdata, 'tableData', 'graphData') testCase.TableData = tableData; testCase.GraphData = graphData; end function setupPanel(testCase) % Check paths file - assert(endsWith(which('dat.paths'), fullfile('tests','+dat','paths.m'))); + assert(endsWith(which('dat.paths'), fullfile('fixtures','+dat','paths.m'))); % Create figure for panel testCase.hPanel = figure('Name', 'alyx GUI',... 'MenuBar', 'none',... @@ -144,10 +148,18 @@ function test_dispWaterReq(testCase) function test_launchSessionURL(testCase) % Test the launch of the session page in the admin Web interface - testCase.Panel; + p = testCase.Panel; % Set new subject testCase.SubjectUI.Selected = testCase.SubjectUI.Option{2}; - testCase.assertEmpty(testCase.Panel.AlyxInstance.SessionURL) + todaySession = p.AlyxInstance.getSessions(testCase.SubjectUI.Selected, now); + testCase.assertEmpty(todaySession) + + % Add mock user response + mockDialog = MockDialog.instance; + mockDialog.UseDefaults = false; + mockDialog.Dialogs(0) = 'No'; + mockDialog.Dialogs(1) = 'Yes'; + testCase.Panel.launchSessionURL() end diff --git a/tests/+dat/paths.m b/tests/fixtures/+dat/paths.m similarity index 100% rename from tests/+dat/paths.m rename to tests/fixtures/+dat/paths.m diff --git a/tests/data/viewSubjectData.mat b/tests/fixtures/data/viewSubjectData.mat similarity index 100% rename from tests/data/viewSubjectData.mat rename to tests/fixtures/data/viewSubjectData.mat diff --git a/tests/fixtures/util/MockDialog.m b/tests/fixtures/util/MockDialog.m new file mode 100644 index 00000000..23c7f014 --- /dev/null +++ b/tests/fixtures/util/MockDialog.m @@ -0,0 +1,163 @@ +classdef MockDialog < handle + % MOCKDIALOG A class for mocking MATLAB dialog windows + % Examples: + % + % mockdlg = MockDialog.instance('char'); + % mockdlg.Dialogs('Set tolerence') = sequence({50, 5, 0.05}); + % + % mockdlg = MockDialog.instance('char'); + % mockdlg.Dialogs('1st dlg title') = 12; + % mockdlg.Dialogs('2nd dlg title') = false; + % + % mockdlg = MockDialog.instance('uin32'); + % mockdlg.Dialogs(0) = 12; + % mockdlg.Dialogs(1) = {12, 'second input ans', true}; + % + % mockdlg = MockDialog.instance(); + % mockdlg.UseDefaults = true; + % + % See also QUESTDLG, INPUTDLG + + properties + % Flag to ensure that we're in a test enviroment. Should be set to + % true explicitly by a test script upon setup + InTest logical = false + % Containers map of user input, whose keys are either dialog titles or + % function call number + Dialogs = containers.Map('KeyType', 'uint32') + % + UseDefaults logical = true + % Number of calls to newCall % TODO must be uint + NumCalls uint32 {mustBeInteger, mustBeNonnegative} = 0 + end + + methods (Static) + + function obj = instance(keyType) + if nargin == 0; keyType = 'uint32'; end + persistent inst + if isempty(inst) + inst = MockDialog(); + end + if inst.Dialogs.Count ~= 0 && ~strcmp(inst.Dialogs.KeyType, keyType) + warning('MockDialog:Instance:SetKeyType', ... + 'KeyType change from to %s. Resetting object', keyType) + inst.reset(); + inst.Dialogs = containers.Map('KeyType', keyType); + end + + obj = inst; + end + +% function reset() +% delete(inst) +% clear('inst') +% obj = []; +% clear('MockDialog') +% end + + end + + methods + + function reset(obj) + keySet = obj.Dialogs.keys; + remove(obj.Dialogs,keySet) + obj.NumCalls = 0; + end + + function answer = newCall(obj, type, varargin) + % NEWCALL Called by shadowed dialog functions during tests + % The origin of the calls may be identified in two ways: + % 1. Its sequence in the calls to this method. If the KeyType of + % the obj.Dialog container is uint32 then the answers returned or + % defaults set are based on its order in the sequence of calls. + % 2. The dialog title or prompt string. If the obj.Dialog keys + % are 'char' (default), then the dialog title strings are used as + % keys. If the dialog has no title then the prompt string is + % used. NB: for inputdlg boxes the default is 'Input'. If your + % function has multiple calls to inputdlg() with no inputs, better + % set KeyType to unit32. + % + % Using the call sequence is useful when the title/prompt strings + % are variable, unknown or none-unique. Use title strings as keys + % if you wish to run multiple tests in parallel. + % + % If the object's UseDefaults property is true, return the dialog's + % default answer for each call. + % + % Inputs: + % type ('char') - function name that was called by function under + % test. Currently implemented options are 'inputdlg' and + % 'questdlg'. Anything else causes method to return % FIXME + % All other inputs must be those that would be passed to the + % function designated by 'type'. + % + % See also + + % Check we're in test mode, throw warning if not + if ~obj.InTest + warning('MockDialog:newCall:InTestFalse', ... + ['MockDialog method called whilst InTest flag set to false. ' ... + 'Check paths or set flag to true to avoid this message']) + end + + % Check which dialog function was called and get answer keys and + % default answers + switch type + case 'questdlg' + % ignore options struct; we don't care about them + if isstruct(varargin{end}); varargin(end) = []; end + if length(varargin) < 3 + def = 'Yes'; % Default is 'Yes' + elseif length(varargin(3:end)) == length(unique(varargin(3:end)) + def = varargin(3); % Custom button one is default + else + def = varargin(end); % Assume last input default + end + if strcmp(obj.Dialog.KeyType, 'char') + key = varargin{1}; % Key is prompt + else + key = obj.NumCalls; + if key > obj.Dialog.Count + key = key - obj.Dialog.Count*floor(key/obj.Dialog.Count); + key = typecast(key, obj.Dialog.KeyType); + end + end + case 'inputdlg' + % Find key + if ~strcmp(obj.Dialog.KeyType, 'char') + key = obj.NumCalls; + if key > obj.Dialog.Count + key = key - obj.Dialog.Count*floor(key/obj.Dialog.Count); + key = typecast(key, obj.Dialog.KeyType); + end + elseif isempty(varargin) + key = 'Input'; + elseif length(varargin) == 1 % Use prompt string + key = varargin{1}; + else % Use dialog title + key = varargin{2}; + end + % Set default answer as default returned value + def = iff(length(varargin) > 3, @()varargin(4), {}); + otherwise + % pass + return + end + + % Set the answer to be either the dialog's default of whatever's set + % in the Dialogs containers.Map object + answer = iff(obj.UseDefaults, def, @()obj.Dialogs(key)); + % If the answer is a Sequence object, return the first in the + % sequence and iterate + if isa(answer, 'fun.CellSeq') + answer = answer.first; + obj.Dialogs(key) = obj.Dialogs(key).rest; + end + obj.NumCalls = obj.NumCalls + 1; % Iterate number of calls + end + + end + +end \ No newline at end of file diff --git a/tests/fixtures/util/inputdlg.m b/tests/fixtures/util/inputdlg.m new file mode 100644 index 00000000..94556fdf --- /dev/null +++ b/tests/fixtures/util/inputdlg.m @@ -0,0 +1,21 @@ +function choice = inputdlg(varargin) +mock = MockDialog.instance; + +choice = mock.newCall('inputdlg', varargin{:}); +end + +% function answer = inputdlg(varargin) +% if nargin == 5 +% if ischar(varargin{5}) +% opts.Resize = varargin{5}; +% else +% opts = varargin{5}; +% end +% opts.WindowStyle = 'normal'; +% else +% opts.WindowStyle = 'normal'; +% end +% all_f = which('-all', 'inputdlg'); +% all_f = all_f(cell2mat(strfind (all_f, matlabroot))); +% f = fileFunction(all_f{:}); +% answer = feval(f, varargin{:}, opts); diff --git a/tests/fixtures/util/questdlg.m b/tests/fixtures/util/questdlg.m new file mode 100644 index 00000000..0099c4e1 --- /dev/null +++ b/tests/fixtures/util/questdlg.m @@ -0,0 +1,5 @@ +function choice = questdlg(varargin) +mock = MockDialog.instance; + +choice = mock.newCall('questdlg', varargin{:}); +end \ No newline at end of file From 228a016b677e4d6ed08181bbab39e1fc24a81fdf Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 20 Mar 2019 11:54:01 +0200 Subject: [PATCH 332/393] HOTFIX Update to ALF extraction --- alyx-matlab | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alyx-matlab b/alyx-matlab index 55e3e2ad..9c2f6b5b 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit 55e3e2adc57c42a1c84dbc0b8570c428f246f65d +Subproject commit 9c2f6b5b2583a4e5c610b7469e1251a12ad5d743 From 9f20d25f17c6a520980a54b60034bdbf35ec163a Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 20 Mar 2019 15:47:34 +0200 Subject: [PATCH 333/393] Added config YAML for todo bot --- .github/config.yml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .github/config.yml diff --git a/.github/config.yml b/.github/config.yml new file mode 100644 index 00000000..1c659e44 --- /dev/null +++ b/.github/config.yml @@ -0,0 +1,9 @@ +todo: + autoAssign: true + keyword: ['FIXME','TODO','%TODO','%FIXME','@todo','@fixme'] + bodyKeyword: ['@body','BODY'] + blobLines: 5 + caseSensitive: false + label: true + reopenClosed: true + exclude: null From 8820651acb28309b952f4581aeeba684f184d0b5 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 20 Mar 2019 15:47:34 +0200 Subject: [PATCH 334/393] Added config YAML for todo bot --- .github/config.yml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .github/config.yml diff --git a/.github/config.yml b/.github/config.yml new file mode 100644 index 00000000..1c659e44 --- /dev/null +++ b/.github/config.yml @@ -0,0 +1,9 @@ +todo: + autoAssign: true + keyword: ['FIXME','TODO','%TODO','%FIXME','@todo','@fixme'] + bodyKeyword: ['@body','BODY'] + blobLines: 5 + caseSensitive: false + label: true + reopenClosed: true + exclude: null From 73a84949f1c264d724555a14c38348160dbde31a Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 20 Mar 2019 15:47:34 +0200 Subject: [PATCH 335/393] Added config YAML for todo bot --- .github/config.yml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .github/config.yml diff --git a/.github/config.yml b/.github/config.yml new file mode 100644 index 00000000..1c659e44 --- /dev/null +++ b/.github/config.yml @@ -0,0 +1,9 @@ +todo: + autoAssign: true + keyword: ['FIXME','TODO','%TODO','%FIXME','@todo','@fixme'] + bodyKeyword: ['@body','BODY'] + blobLines: 5 + caseSensitive: false + label: true + reopenClosed: true + exclude: null From 7483cdbb9bab3fd2db43881768404b5265b77e26 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 21 Mar 2019 20:22:33 +0200 Subject: [PATCH 336/393] Started documenting expServer and Communicator --- +srv/expServer.m | 31 ++++++++++++++++++++++++-- cb-tools/burgbox/+io/WSJCommunicator.m | 11 +++++++-- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/+srv/expServer.m b/+srv/expServer.m index 2a6bf091..954c6aee 100644 --- a/+srv/expServer.m +++ b/+srv/expServer.m @@ -1,6 +1,33 @@ function expServer(useTimelineOverride, bgColour) %SRV.EXPSERVER Start the presentation server -% TODO +% Principle function for running experiments. ExpServer listens for +% commands via TCP/IP Web sockets to start, stop and pause stimulus +% presentation experiments. +% +% Inputs: +% useTimelineOverride (logical) - Flag indicating whether to start +% Timeline. If empty the default is the UseTimeline flag in the +% hardware file. Timeline may still be toggled by pressing the 't' +% key. +% bgColour (1-by-3 double) - The background colour of the stimulus +% window. If not specified the background colour specified in the +% harware file is used. +% +% Key bindings: +% t - Toggle Timeline on and off. The default state is defined in the +% hardware file but may be overridden as the first input argument. +% w - Toggle reward on and off. This switches the output of the first +% DAQ output channel between its 'high' and 'low' state. The +% specific DAQ channel and its default state are set in the hardware +% file. +% space - Deliver default reward, specified by the DefaultCommand +% property in the hardware file. +% m - Perform water calibration. +% b - Toggle the background colour between the default and white. +% g - Perform gamma correction +% +% +% See also MC, io.WSJCommunicator, hw.devices, srv.prepareExp, hw.Timeline % % Part of Rigbox @@ -96,6 +123,7 @@ function expServer(useTimelineOverride, bgColour) %% Main loop for service while running + % Check for messages when out of event mode if communicator.IsMessageAvailable [msgid, msgdata, host] = communicator.receive(); handleMessage(msgid, msgdata, host); @@ -130,7 +158,6 @@ function expServer(useTimelineOverride, bgColour) if firstPress(rewardPulseKey) > 0 log('Delivering default reward'); def = [rig.daqController.SignalGenerators(rewardId).DefaultCommand]; - % def = rewardSig.DefaultCommand; rig.daqController.command(def); end diff --git a/cb-tools/burgbox/+io/WSJCommunicator.m b/cb-tools/burgbox/+io/WSJCommunicator.m index f6c16f41..cd3af8ee 100644 --- a/cb-tools/burgbox/+io/WSJCommunicator.m +++ b/cb-tools/burgbox/+io/WSJCommunicator.m @@ -8,11 +8,15 @@ % 2014-08 CB created properties (Dependent, SetAccess = protected) + % Flag set to true while there is a message in the buffer IsMessageAvailable + % The role of the Commuicator object, either 'client' or 'server' + % depending on which constructor method was used Role end properties (Constant) + % The listen port used if one isn't already specified in the URI DefaultListenPort = 2014 end @@ -25,11 +29,14 @@ properties (Transient) WebSocket hWebSocket %handle to java WebSocket - EventMode = false + % When true listeners are notified of new messages via the + % MessageRecieved event + EventMode logical = false end properties (Access = private, Transient) - Listener + Listener % TODO This property appears to be unused. Test before removing + % Handle to java.util.LinkedList object containing recieved data InBuffer end From 7ed3427e78beef3db7bac5c5eaca60de07677897 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 21 Mar 2019 20:24:03 +0200 Subject: [PATCH 337/393] Added test for cellflat --- cb-tools/burgbox/cellflat.m | 2 +- tests/cellflat_test.m | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 tests/cellflat_test.m diff --git a/cb-tools/burgbox/cellflat.m b/cb-tools/burgbox/cellflat.m index 37a5252d..98e2daed 100644 --- a/cb-tools/burgbox/cellflat.m +++ b/cb-tools/burgbox/cellflat.m @@ -2,7 +2,7 @@ %cellflat Eliminates nesting from nested cell arrays % flat = cellflat(C) returns all elements from cell array `C`, including % those nested within further cell arrays (etc) in a single flat cell -% array. +% array. NB: Cells always returned as a column array. % % Part of Burgbox diff --git a/tests/cellflat_test.m b/tests/cellflat_test.m new file mode 100644 index 00000000..e66eccb5 --- /dev/null +++ b/tests/cellflat_test.m @@ -0,0 +1,25 @@ +%% Test 1: Flatten simple nested cell array +arr = {num2cell(1:5)}; +flat = cellflat(arr); +expected = num2cell(1:5)'; + +assert(isequal(flat, expected), 'Failed to remove outer cell') + +%% Test 2: Flatten highly nested cell array +arr = {cell(2,1), {num2cell(1:5)}, {{'one'}}, {cell(1,2)}}; +flat = cellflat(arr); +expected = [{[]; [];}; num2cell(1:5)'; {'one'; []; []}]; + +assert(isequal(flat, expected), 'Failed to flatten nested cells') + +%% Test 3: Flatten cell array of Signals +net = sig.Net; +A = net.origin('A'); +B = net.origin('B'); +C = net.origin('C'); + +arr = [{A}, {{B}}, {{{C}}}]; +flat = cellflat(arr); +expected = {A; B; C}; + +assert(isequal(flat, expected), 'Failed to return flat cell array of Signals') \ No newline at end of file From 45e1a62832b07b1ce638c0607aed20b35286682e Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Thu, 21 Mar 2019 20:29:04 +0200 Subject: [PATCH 338/393] Bonus cellflat test --- tests/cellflat_test.m | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/cellflat_test.m b/tests/cellflat_test.m index e66eccb5..d740b591 100644 --- a/tests/cellflat_test.m +++ b/tests/cellflat_test.m @@ -5,14 +5,18 @@ assert(isequal(flat, expected), 'Failed to remove outer cell') -%% Test 2: Flatten highly nested cell array +%% Test 2: Return cell when passed single element arrays +flat = cellflat({}); +assert(iscell(flat) && isequal(flat, {}), 'Failed to return as cell') + +%% Test 3: Flatten highly nested cell array arr = {cell(2,1), {num2cell(1:5)}, {{'one'}}, {cell(1,2)}}; flat = cellflat(arr); expected = [{[]; [];}; num2cell(1:5)'; {'one'; []; []}]; assert(isequal(flat, expected), 'Failed to flatten nested cells') -%% Test 3: Flatten cell array of Signals +%% Test 4: Flatten cell array of Signals net = sig.Net; A = net.origin('A'); B = net.origin('B'); From a83c43788c5434bbbaf0a37fea5faec519962e8c Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Sat, 23 Mar 2019 12:50:47 +0200 Subject: [PATCH 339/393] Added docs folder for package demos --- +dat/newExp.m | 2 +- doc/setup/Alyx_config.m | 15 ++++++++++ doc/setup/hardware_config.m | 45 ++++++++++++++++++++++++++++++ doc/setup/websocket_config.m | 25 +++++++++++++++++ doc/using_dat_package.m | 54 ++++++++++++++++++++++++++++++++++++ doc/using_parameters.m | 12 ++++++++ 6 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 doc/setup/Alyx_config.m create mode 100644 doc/setup/hardware_config.m create mode 100644 doc/setup/websocket_config.m create mode 100644 doc/using_dat_package.m create mode 100644 doc/using_parameters.m diff --git a/+dat/newExp.m b/+dat/newExp.m index 0ff092b8..9cec8849 100644 --- a/+dat/newExp.m +++ b/+dat/newExp.m @@ -1,6 +1,6 @@ function [expRef, expSeq] = newExp(subject, expDate, expParams) %DAT.NEWEXP Create a new unique experiment in the database -% [ref, seq, url] = DAT.NEWEXP(subject, expDate, expParams[, AlyxInstance]) +% [ref, seq] = DAT.NEWEXP(subject, expDate, expParams) % Create a new experiment by creating the relevant folder tree in the % local and main data repositories in the following format: % diff --git a/doc/setup/Alyx_config.m b/doc/setup/Alyx_config.m new file mode 100644 index 00000000..2736b7f3 --- /dev/null +++ b/doc/setup/Alyx_config.m @@ -0,0 +1,15 @@ +%% Setting up Alyx +% Alyx is an... +% Info on setting up database instance +% Add submodule: +git.install('alyx-matlab'); %TODO some code that runs init on this submodule + +% To activate Alyx +opentoline(which('eui.MControl'),755,46) +% obj.AlyxPanel = eui.AlyxPanel(headerBox); + +% Default database url +url = getOr(dat.paths, 'databaseURL'); + +%% More info: +opentoline(which('Examples.m'),1,1) \ No newline at end of file diff --git a/doc/setup/hardware_config.m b/doc/setup/hardware_config.m new file mode 100644 index 00000000..c2164d1d --- /dev/null +++ b/doc/setup/hardware_config.m @@ -0,0 +1,45 @@ +%% Configuring hardware devices +% The stimulus computer (expServer) that... +% Many of the classes for interacting with the hardware are found in the +% +hw package. + +%% Configuring the visual stimuli +% The +hw Window class is the main class for configuring the visual +% stimulus window. It contains the attributes and methods for interacting +% with the lower level functions that interact with the graphics drivers. +% Currently the only concrete implementation is support for the +% Psychophysics Toolbox, the hw.ptb.Window class. +doc hw.ptb.Window +stimWindow = hw.ptb.Window; + +%% Adding a viewing model +% The following classes [...] how the stimuli are [...] +% hw.BasicScreenViewingModel +% hw.PseudoCircularScreenViewingModel +% screen + +%% Saving the hardware configurations +% The location of the configuration file is set in dat.paths. If running +% this on the stimulus computer you can use the following syntax: +p = getOr(dat.paths, 'rigConfig'); +% save(fullfile(p, 'hardware.mat'), 'stimWindow', '--append') + +%% Timeline +%Open your hardware.mat file and instantiate a new Timeline object +timeline = hw.Timeline; +%Set tl to be started by default +timeline.UseTimeline = true; +%To set up chrono a wire must bridge the terminals defined in +% timeline.Outputs(1).DaqChannelID and timeline.Inputs(1).daqChannelID +timeline.wiringInfo('chrono'); +%Add the rotary encoder +timeline.addInput('rotaryEncoder', 'ctr0', 'Position'); +%For a lick detector +timeline.addInput('lickDetector', 'ctr1', 'EdgeCount'); +%We want use camera frame acquisition trigger by default +timeline.UseOutputs{end+1} = 'clock'; +%Save your hardware.mat file +% save('hardware.mat', 'timeline', '-append') + +%% Loading your hardware file +rig = hw.devices; \ No newline at end of file diff --git a/doc/setup/websocket_config.m b/doc/setup/websocket_config.m new file mode 100644 index 00000000..5abdd7d4 --- /dev/null +++ b/doc/setup/websocket_config.m @@ -0,0 +1,25 @@ +%% Configuring WebSockets +% In order for the two computers to communicate... + +% The stimulus controllers are loaded from a MAT file with the name +% 'remote' in the globalConfig directory, defined in dat.paths: +p = fullfile(getOr(dat.paths, 'globalConfig'), 'remote.mat'); + +% Let's create a new stimulus controller +name = ipaddress(hostname); +stimulusControllers = srv.StimulusControl.create(name); + +% A note on adding new computers. Do not simply copy objects, instead use +% the following method: +uri = 'ws://192.168.0.1:5000'; +stimulusControllers(2) = srv.StimulusControl.create('rig2', uri); + +% the stimulus controllers can be loaded using the srv.stimulusControllers +% function: +sc = srv.stimulusControllers; + +%% Configuring Services +% srv.prepareExp +% srv.findService + +%% UDP communication diff --git a/doc/using_dat_package.m b/doc/using_dat_package.m new file mode 100644 index 00000000..b84a65b0 --- /dev/null +++ b/doc/using_dat_package.m @@ -0,0 +1,54 @@ +%% Loading experiments +% Listing all subjects +subjects = dat.listSubjects; + +% The subjects list is generated from the folder names in the main +% repository path +mainRepo = getOr(dat.paths, 'mainRepository'); +% To get all paths you should save to for the "main" repository: +savePaths = dat.reposPath('main'); % savePaths is a string cell array +% To get the master location for the "main" repository: +loadPath = dat.reposPath('main', 'master'); % loadPath is a string + +% List experiments for a given subject +[ref, date, seq] = dat.listExps(subject); + +% Return experiment path +p = dat.expPath(ref); +[p, ref] = dat.expPath(subject, now, 1, 'main'); + +% Check a given experiment exists +bool = expExists(ref); + +% Return specific file path +[fullpath, filename] = dat.expFilePath(ref, 'block'); +[fullpath, filename] = dat.expFilePath(ref, 'block', 'master', 'json'); +[fullpath, filename] = dat.expFilePath(subject, now, 1, 'timeline'); + +parameters = dat.expParams(ref); +block = dat.loadBlock(ref, expType); +clear BurgboxCache + +%% Manually creating experiments +[expRef, expSeq] = newExp(subject, expDate, expParams); + +%% Using expRefs +ref = dat.constructExpRef('subject', now, 2); +[subjectRef, expDate, expSequence] = parseExpRef(ref); + +%% Loading other things +expType = 'custom'; +p = dat.loadParamProfiles(expType); +dat.saveParamProfile(expType, profileName, params); +dat.delParamProfile(expType, profileName); + +%% Using the log +% FIXME Remove dat.expLogRequest +% @body expLogRequest clearly an old attempt at logging meta data to a +% server via JSON. This is now Alyx. +[result, info] = expLogRequest(instruction, varargin); + +e = dat.addLogEntry(subject, timestamp, type, value, comments, AlyxInstance); +p = dat.logPath(subject, 'all'); +e = dat.logEntries(subject); +e = dat.updateLogEntry(subject, id, newEntry); \ No newline at end of file diff --git a/doc/using_parameters.m b/doc/using_parameters.m new file mode 100644 index 00000000..d98a0ec6 --- /dev/null +++ b/doc/using_parameters.m @@ -0,0 +1,12 @@ +%% Dealing with parameters +paramStruct = exp.inferParameters('defFunction'); +parameters = exp.Parameters(paramStruct); + +parameters.set(name, value, description, units) +parameters.makeTrialSpecific(name) +parameters.makeGlobal(name, newValue) +parameters.Struct = rmfield(parameters.Struct, name); +parameters.removeConditions(indices) + +[cs, globalParams, trialParams] = parameters.toConditionServer(obj, randomOrder); +[globalParams, trialParams] = parameters.assortForExperiment; \ No newline at end of file From da69e8f77da4f42124db45d94939c43493d164de Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Sat, 23 Mar 2019 18:37:23 +0200 Subject: [PATCH 340/393] Tests for posting weight and launching subject url; bug fix for <70% label --- +eui/AlyxPanel.m | 28 +++++---- tests/AlyxPanel_test.m | 113 +++++++++++++++++++++++++++++++++--- tests/fixtures/+dat/paths.m | 6 +- 3 files changed, 124 insertions(+), 23 deletions(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index db784bae..429ac8e0 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -441,17 +441,23 @@ function launchSessionURL(obj) web(adminURL, '-browser'); end - function launchSubjectURL(obj) + function [stat, url] = launchSubjectURL(obj) % LAUNCHSUBJECTURL Launch the Webpage for the current subject - % Launches Web page in the default Web browser. + % Launches Web page in the default Web browser. Note that the + % logged in state of the AlyxPanel is independent of the + % browser cookies, therefore you may need to log in to see the + % subject page. + % + % Outputs: + % stat (double) - returns the status of the operation: + % 0 if successful, 1 or 2 if unsuccessful. + % url (char) - the url for the subject page % % See also LAUNCHSESSIONURL ai = obj.AlyxInstance; - if ai.IsLoggedIn - s = ai.getData(ai.makeEndpoint(['subjects/' obj.Subject])); - subjURL = fullfile(ai.BaseURL, 'admin', 'subjects', 'subject', s.id, 'change'); % this is wrong - need uuid - web(subjURL, '-browser'); - end + s = ai.getData(ai.makeEndpoint(['subjects/' obj.Subject])); + url = fullfile(ai.BaseURL, 'admin', 'subjects', 'subject', s.id, 'change'); % this is wrong - need uuid + stat = web(url, '-browser'); end function viewSubjectHistory(obj, ax) @@ -630,12 +636,12 @@ function dispWaterReq(obj, src, ~) expected_weight = getOr(record, 'expected_weight', NaN); % Set colour based on weight percentage weight_pct = (weight-wr.implant_weight)/(expected_weight-wr.implant_weight); - if weight_pct < 0.8 % Mouse below 80% original weight - colour = [0.91, 0.41, 0.17]; % Orange - weight_pct = '< 80%'; - elseif weight_pct < 0.7 % Mouse below 70% original weight + if weight_pct < 0.7 % Mouse below 70% original weight colour = 'red'; weight_pct = '< 70%'; + elseif weight_pct < 0.8 % Mouse below 80% original weight + colour = [0.91, 0.41, 0.17]; % Orange + weight_pct = '< 80%'; else colour = 'black'; % Mouse above 80% or no weight measured today weight_pct = '> 80%'; diff --git a/tests/AlyxPanel_test.m b/tests/AlyxPanel_test.m index 4cd80187..e32e7062 100644 --- a/tests/AlyxPanel_test.m +++ b/tests/AlyxPanel_test.m @@ -1,11 +1,13 @@ classdef (SharedTestFixtures={ % add 'fixtures' folder as test fixture matlab.unittest.fixtures.PathFixture('fixtures'),... - matlab.unittest.fixtures.PathFixture('util')})... + matlab.unittest.fixtures.PathFixture(['fixtures' filesep 'util'])})... AlyxPanel_test < matlab.unittest.TestCase properties % Figure visibility setting before running tests FigureVisibleDefault + % Instance of MockDialog object + Mock % AlyxPanel instance Panel % Parent container for AlyxPanel @@ -30,7 +32,7 @@ methods (TestClassSetup) function killFigures(testCase) testCase.FigureVisibleDefault = get(0,'DefaultFigureVisible'); - set(0,'DefaultFigureVisible','off'); +% set(0,'DefaultFigureVisible','off'); end function loadData(testCase) @@ -47,6 +49,12 @@ function loadData(testCase) function setupPanel(testCase) % Check paths file assert(endsWith(which('dat.paths'), fullfile('fixtures','+dat','paths.m'))); + % Check temp mainRepo folder is empty. An extra safe measure as we + % don't won't to delete important folders by accident! + mainRepo = getOr(dat.paths, 'mainRepository'); + assert(~exist(mainRepo, 'dir') || isempty(setdiff(getOr(dir(mainRepo),'name'),{'.','..'})),... + 'Test experiment repo not empty. Please set another path or manual empty folder'); + % Create figure for panel testCase.hPanel = figure('Name', 'alyx GUI',... 'MenuBar', 'none',... @@ -80,6 +88,17 @@ function setupPanel(testCase) testCase.Panel.login('test_user', 'TapetesBloc18'); testCase.fatalAssertTrue(testCase.Panel.AlyxInstance.IsLoggedIn,... 'Failed to log into Alyx'); + + % Verify subject folders created + present = ismember([{'default'}; testCase.Subjects], dat.listSubjects); + testCase.verifyTrue(all(present), 'Failed to create missing subject folders') + + % Ensure local Alyx queue set up + alyxQ = getOr(dat.paths,'localAlyxQueue', ['fixtures' filesep 'alyxQ']); + testCase.resetQueue(alyxQ); + + % Setup dialog mocking + testCase.Mock = MockDialog('char'); end end @@ -87,16 +106,27 @@ function setupPanel(testCase) function restoreFigures(testCase) set(0,'DefaultFigureVisible',testCase.FigureVisibleDefault); close(testCase.hPanel) + % Remove subject directories + dataRepo = getOr(dat.paths, 'mainRepository'); + assert(rmdir(dataRepo, 's'), 'Failed to remove test data directory') + % Remove Alyx queue + alyxQ = getOr(dat.paths,'localAlyxQueue', ['fixtures' filesep 'alyxQ']); + assert(rmdir(alyxQ, 's'), 'Failed to remove test Alx queue') end end methods (TestMethodTeardown) - function closeFigure(testCase) + function methodTaredown(testCase) + % Ensure local Alyx queue set up + alyxQ = getOr(dat.paths,'localAlyxQueue', ['fixtures' filesep 'alyxQ']); + testCase.resetQueue(alyxQ); % Close any figures opened during the test if ~isempty(testCase.Figure); close(testCase.Figure); end + % Reset state of dialog mock + testCase.Mock.reset; end end - + methods (Test) function test_viewSubjectHistory(testCase) % Post some weights for plotting @@ -164,11 +194,67 @@ function test_launchSessionURL(testCase) end function test_launchSubjectURL(testCase) - testCase.Panel; + % Test the launch of the subject page in the admin Web interface + p = testCase.Panel; + % Set new subject + testCase.SubjectUI.Selected = testCase.SubjectUI.Option{2}; + [failed, url] = p.launchSubjectURL; + testCase.verifyTrue(~failed, 'Failed to launch subject page in browser') + expected = ['https:\\test.alyx.internationalbrainlab.org\admin\'... + 'subjects\subject\bcefd268-68c2-4ea8-9b60-588ee4e99ebb\change']; + testCase.verifyEqual(url, expected, 'unexpected subject page url') end function test_recordWeight(testCase) testCase.Panel; + % Set subject on water restriction + testCase.SubjectUI.Option{end+1} = 'algernon'; + testCase.SubjectUI.Selected = testCase.SubjectUI.Option{end}; + % Find label for weight + labels = findall(testCase.Parent, 'Style', 'text'); + weight_text = labels(cellfun(@(ch)size(ch,1)==2,{labels.String})); + + % Post weight < 70 + weight = randi(13) + rand; + testCase.Panel.recordWeight(weight) + expected = sprintf('Weight today: %.2f (< 70%%)', weight); + testCase.verifyTrue(startsWith(strip(weight_text.String(2,:)), expected),... + 'Failed to update weight label value') + testCase.verifyEqual(weight_text.ForegroundColor, [1 0 0],... + 'Failed to update weight label color') + + % Post weight < 80 + weight = 16 + rand; + testCase.Panel.recordWeight(weight) + expected = sprintf('Weight today: %.2f (< 80%%)', weight); + testCase.verifyTrue(startsWith(strip(weight_text.String(2,:)), expected),... + 'Failed to update weight label value') + testCase.verifyEqual(weight_text.ForegroundColor, [.91 .41 .17],... + 'Failed to update weight label color') + + % Check log + logPanel = findobj(testCase.hPanel, 'Tag', 'Logging Display'); + expected = sprintf('Alyx weight posting succeeded: %.2f for algernon', weight); + testCase.verifyTrue(endsWith(logPanel.String{end}, expected)) + + % Test manual weight dialog + weight = 25 + rand; + testCase.Mock.Dialogs('Manual weight logging') = num2str(weight); + testCase.Panel.recordWeight() + + expected = sprintf('Alyx weight posting succeeded: %.2f for algernon', weight); + testCase.verifyTrue(endsWith(logPanel.String{end}, expected)) + + % Test weight post when logged out + testCase.Panel.logout + testCase.Panel.recordWeight() + + expected = 'Warning: Weight not posted to Alyx; will be posted upon login.'; + testCase.verifyTrue(endsWith(logPanel.String{end}, expected)) + + % Check post was saved + savedPost = dir([getOr(dat.paths, 'localAlyxQueue') filesep '*.post']); + testCase.assertNotEmpty(savedPost, 'Post not saved') end function test_login(testCase) @@ -201,10 +287,6 @@ function test_updateWeightButton(testCase) testCase.Panel; end - function test_log(testCase) - testCase.Panel; - end - function test_giveWater(testCase) testCase.Panel; end @@ -235,4 +317,17 @@ function test_round(testCase) end + + methods (Static) + function resetQueue(alyxQ) + % Create test directory if it doesn't exist + if exist(alyxQ, 'dir') ~= 7 + mkdir(alyxQ); + else % Delete any queued posts + files = dir(alyxQ); + files = {files(endsWith({files.name},{'put', 'patch', 'post'})).name}; + cellfun(@delete, fullfile(alyxQ, files)) + end + end + end end \ No newline at end of file diff --git a/tests/fixtures/+dat/paths.m b/tests/fixtures/+dat/paths.m index 7d22e839..7e1ad43b 100644 --- a/tests/fixtures/+dat/paths.m +++ b/tests/fixtures/+dat/paths.m @@ -7,7 +7,7 @@ % 2013-03 CB created -thishost = 'dummyRig'; +thishost = 'testRig'; if nargin < 1 || isempty(rig) rig = thishost; @@ -18,13 +18,13 @@ p.rigbox = fileparts(which('addRigboxPaths')); % Repository for local copy of everything generated on this rig p.localRepository = 'C:\LocalExpData'; -p.localAlyxQueue = fullfile(p.rigbox, 'alyx-matlab', 'tests', 'data'); +p.localAlyxQueue = fullfile(p.rigbox, 'tests', 'fixtures', 'alyxQ'); p.databaseURL = 'https://test.alyx.internationalbrainlab.org'; p.gitExe = 'C:\Program Files\Git\cmd\git.exe'; % Under the new system of having data grouped by mouse % rather than data type, all experimental data are saved here. -p.mainRepository = fullfile(p.rigbox, 'tests', 'data', 'Subjects'); +p.mainRepository = fullfile(p.rigbox, 'tests', 'fixtures', 'Subjects'); % directory for organisation-wide configuration files, for now these should % all remain on zserver From d0fb53b0b16815e053c3fdb5447cc396860a4522 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Sat, 23 Mar 2019 20:16:11 +0200 Subject: [PATCH 341/393] Tests for weight button --- +eui/AlyxPanel.m | 5 ++++ tests/AlyxPanel_test.m | 42 ++++++++++++++++++++++++++------ tests/fixtures/util/MockDialog.m | 36 ++++++++++++--------------- tests/fixtures/util/inputdlg.m | 18 +------------- 4 files changed, 57 insertions(+), 44 deletions(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index 429ac8e0..113f0dec 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -210,6 +210,11 @@ function delete(obj) delete(obj.LoginTimer) % ... delete it... obj.LoginTimer = []; % ... and remove it end + if ~isempty(obj.WeightTimer) % If there is a timer object + stop(obj.WeightTimer) % Stop the timer... + delete(obj.WeightTimer) % ... delete it... + obj.WeightTimer = []; % ... and remove it + end end function login(obj, varargin) diff --git a/tests/AlyxPanel_test.m b/tests/AlyxPanel_test.m index e32e7062..85def785 100644 --- a/tests/AlyxPanel_test.m +++ b/tests/AlyxPanel_test.m @@ -32,7 +32,7 @@ methods (TestClassSetup) function killFigures(testCase) testCase.FigureVisibleDefault = get(0,'DefaultFigureVisible'); -% set(0,'DefaultFigureVisible','off'); + set(0,'DefaultFigureVisible','off'); end function loadData(testCase) @@ -98,7 +98,7 @@ function setupPanel(testCase) testCase.resetQueue(alyxQ); % Setup dialog mocking - testCase.Mock = MockDialog('char'); + testCase.Mock = MockDialog.instance('char'); end end @@ -106,6 +106,7 @@ function setupPanel(testCase) function restoreFigures(testCase) set(0,'DefaultFigureVisible',testCase.FigureVisibleDefault); close(testCase.hPanel) + delete(testCase.Panel) % Remove subject directories dataRepo = getOr(dat.paths, 'mainRepository'); assert(rmdir(dataRepo, 's'), 'Failed to remove test data directory') @@ -207,6 +208,8 @@ function test_launchSubjectURL(testCase) function test_recordWeight(testCase) testCase.Panel; + testCase.Mock.InTest = true; + testCase.Mock.UseDefaults = false; % Set subject on water restriction testCase.SubjectUI.Option{end+1} = 'algernon'; testCase.SubjectUI.Selected = testCase.SubjectUI.Option{end}; @@ -239,14 +242,26 @@ function test_recordWeight(testCase) % Test manual weight dialog weight = 25 + rand; + button = findobj(testCase.Parent, 'String', 'Manual weighing'); + testCase.assertTrue(~isempty(button), 'Unable to find button object') testCase.Mock.Dialogs('Manual weight logging') = num2str(weight); - testCase.Panel.recordWeight() + button.Callback() expected = sprintf('Alyx weight posting succeeded: %.2f for algernon', weight); testCase.verifyTrue(endsWith(logPanel.String{end}, expected)) + testCase.verifyEqual(weight_text.ForegroundColor, zeros(1,3),... + 'Failed to update weight label color') + + % Test button with weighing scale listener + src.readGrams = randi(35) + rand; + testCase.Panel.updateWeightButton(src,[]) + button.Callback() + expected = sprintf('Weight today: %.2f', src.readGrams); + testCase.verifyTrue(startsWith(strip(weight_text.String(2,:)), expected),... + 'Failed to update weight label value') % Test weight post when logged out - testCase.Panel.logout + testCase.Panel.login testCase.Panel.recordWeight() expected = 'Warning: Weight not posted to Alyx; will be posted upon login.'; @@ -269,7 +284,7 @@ function test_login(testCase) % Check labels testCase.verifyEqual(labels(3).String, 'You are logged in as test_user') testCase.verifyTrue(~contains(labels(2).String, 'Log in')) - + % Log out testCase.Panel.login; testCase.assertTrue(~testCase.Panel.AlyxInstance.IsLoggedIn); @@ -284,7 +299,21 @@ function test_login(testCase) end function test_updateWeightButton(testCase) - testCase.Panel; + % Find the weight button + button = findobj(testCase.Parent, 'String', 'Manual weighing'); + testCase.assertTrue(~isempty(button), 'Unable to find button object') + callbk_fn = button.Callback; + src.readGrams = randi(35) + rand; + testCase.Panel.updateWeightButton(src,[]) + % Check button updated + testCase.verifyEqual(button.String, sprintf('Record %.1fg',src.readGrams)) + testCase.verifyTrue(~isequal(button.Callback, callbk_fn), 'Callback unchanged') + callbk_fn = button.Callback; + + % Check button resets + pause(10) + testCase.verifyEqual(button.String, 'Manual weighing', 'Button failed to reset') + testCase.verifyTrue(~isequal(button.Callback, callbk_fn), 'Callback unchanged') end function test_giveWater(testCase) @@ -317,7 +346,6 @@ function test_round(testCase) end - methods (Static) function resetQueue(alyxQ) % Create test directory if it doesn't exist diff --git a/tests/fixtures/util/MockDialog.m b/tests/fixtures/util/MockDialog.m index 23c7f014..e5b26896 100644 --- a/tests/fixtures/util/MockDialog.m +++ b/tests/fixtures/util/MockDialog.m @@ -34,12 +34,11 @@ methods (Static) function obj = instance(keyType) - if nargin == 0; keyType = 'uint32'; end persistent inst if isempty(inst) inst = MockDialog(); end - if inst.Dialogs.Count ~= 0 && ~strcmp(inst.Dialogs.KeyType, keyType) + if nargin > 0 && ~strcmp(inst.Dialogs.KeyType, keyType) warning('MockDialog:Instance:SetKeyType', ... 'KeyType change from to %s. Resetting object', keyType) inst.reset(); @@ -49,21 +48,15 @@ obj = inst; end -% function reset() -% delete(inst) -% clear('inst') -% obj = []; -% clear('MockDialog') -% end - end methods function reset(obj) keySet = obj.Dialogs.keys; - remove(obj.Dialogs,keySet) + remove(obj.Dialogs,keySet); obj.NumCalls = 0; + obj.InTest = false; end function answer = newCall(obj, type, varargin) @@ -110,27 +103,27 @@ function reset(obj) if isstruct(varargin{end}); varargin(end) = []; end if length(varargin) < 3 def = 'Yes'; % Default is 'Yes' - elseif length(varargin(3:end)) == length(unique(varargin(3:end)) + elseif length(varargin(3:end)) == length(unique(varargin(3:end))) def = varargin(3); % Custom button one is default else def = varargin(end); % Assume last input default end - if strcmp(obj.Dialog.KeyType, 'char') + if strcmp(obj.Dialogs.KeyType, 'char') key = varargin{1}; % Key is prompt else key = obj.NumCalls; - if key > obj.Dialog.Count - key = key - obj.Dialog.Count*floor(key/obj.Dialog.Count); - key = typecast(key, obj.Dialog.KeyType); + if key > obj.Dialogs.Count + key = key - obj.Dialogs.Count*floor(key/obj.Dialogs.Count); + key = typecast(key, obj.Dialogs.KeyType); end end case 'inputdlg' % Find key - if ~strcmp(obj.Dialog.KeyType, 'char') + if ~strcmp(obj.Dialogs.KeyType, 'char') key = obj.NumCalls; - if key > obj.Dialog.Count - key = key - obj.Dialog.Count*floor(key/obj.Dialog.Count); - key = typecast(key, obj.Dialog.KeyType); + if key > obj.Dialogs.Count + key = key - obj.Dialogs.Count*floor(key/obj.Dialogs.Count); + key = typecast(key, obj.Dialogs.KeyType); end elseif isempty(varargin) key = 'Input'; @@ -140,7 +133,7 @@ function reset(obj) key = varargin{2}; end % Set default answer as default returned value - def = iff(length(varargin) > 3, @()varargin(4), {}); + def = iff(length(varargin) > 3, @()varargin{4}, {}); otherwise % pass return @@ -155,6 +148,9 @@ function reset(obj) answer = answer.first; obj.Dialogs(key) = obj.Dialogs(key).rest; end + + % inputdlg always returns a cell + if strcmp(type, 'inputdlg'); answer = ensureCell(answer); end obj.NumCalls = obj.NumCalls + 1; % Iterate number of calls end diff --git a/tests/fixtures/util/inputdlg.m b/tests/fixtures/util/inputdlg.m index 94556fdf..e5560ea3 100644 --- a/tests/fixtures/util/inputdlg.m +++ b/tests/fixtures/util/inputdlg.m @@ -2,20 +2,4 @@ mock = MockDialog.instance; choice = mock.newCall('inputdlg', varargin{:}); -end - -% function answer = inputdlg(varargin) -% if nargin == 5 -% if ischar(varargin{5}) -% opts.Resize = varargin{5}; -% else -% opts = varargin{5}; -% end -% opts.WindowStyle = 'normal'; -% else -% opts.WindowStyle = 'normal'; -% end -% all_f = which('-all', 'inputdlg'); -% all_f = all_f(cell2mat(strfind (all_f, matlabroot))); -% f = fileFunction(all_f{:}); -% answer = feval(f, varargin{:}, opts); +end \ No newline at end of file From 1176532c35099095d9cc02c1ec18052a78cbd164 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Sun, 24 Mar 2019 16:04:36 +0200 Subject: [PATCH 342/393] Finished all AlyxPanel tests --- +eui/AlyxPanel.m | 5 +- tests/AlyxPanel_test.m | 122 +++++++++++++++++++++++++++++-- tests/fixtures/util/MockDialog.m | 18 +++-- 3 files changed, 130 insertions(+), 15 deletions(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index 113f0dec..f7c253b9 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -636,8 +636,7 @@ function dispWaterReq(obj, src, ~) record = struct(); end weight = iff(isempty(record.weighing_at), NaN, record.weight); % Get today's measured weight - water = getOr(record, 'given_water_liquid', 0); % Get total water given - gel = getOr(record, 'given_water_hydrogel', 0); % Get total gel given + water = getOr(record, 'given_water_total', 0); % Get total water given expected_weight = getOr(record, 'expected_weight', NaN); % Set colour based on weight percentage weight_pct = (weight-wr.implant_weight)/(expected_weight-wr.implant_weight); @@ -658,7 +657,7 @@ function dispWaterReq(obj, src, ~) sprintf(['Subject %s requires %.2f of %.2f today\n\t '... 'Weight today: %.2f (%s) Water today: %.2f'], obj.Subject, ... remainder, obj.round(s(idx).expected_water, 'up'), weight, ... - weight_pct, obj.round(sum([water gel]), 'down'))); + weight_pct, obj.round(water, 'down'))); % Set WaterRemaining attribute for changeWaterText callback obj.WaterRemaining = remainder; end diff --git a/tests/AlyxPanel_test.m b/tests/AlyxPanel_test.m index 85def785..0817f9a4 100644 --- a/tests/AlyxPanel_test.m +++ b/tests/AlyxPanel_test.m @@ -32,7 +32,7 @@ methods (TestClassSetup) function killFigures(testCase) testCase.FigureVisibleDefault = get(0,'DefaultFigureVisible'); - set(0,'DefaultFigureVisible','off'); +% set(0,'DefaultFigureVisible','off'); end function loadData(testCase) @@ -174,7 +174,25 @@ function test_viewAllSubjects(testCase) end function test_dispWaterReq(testCase) - testCase.Panel; + % Set subject on water restriction + testCase.SubjectUI.Option{end+1} = 'algernon'; + testCase.SubjectUI.Selected = testCase.SubjectUI.Option{end}; + % Find label for weight + labels = findall(testCase.Parent, 'Style', 'text'); + weight_text = labels(cellfun(@(ch)size(ch,1)==2,{labels.String})); + button = findobj(testCase.Parent, 'String', 'Refresh'); + testCase.assertTrue(numel([weight_text button])==2, ... + 'Unable to retrieve all required UI elements'); + + % Update weight outside of Panel + w = testCase.Panel.AlyxInstance.postWeight(randi(35)+rand, 'algernon'); + testCase.assertTrue(~isempty(w), 'Failed to update Alyx') + + prev = weight_text.String(2,:); + button.Callback(); % Hit refresh + new = weight_text.String(2,:); + + testCase.verifyTrue(~strcmp(prev, new), 'Failed to retrieve new data') end function test_launchSessionURL(testCase) @@ -318,14 +336,106 @@ function test_updateWeightButton(testCase) function test_giveWater(testCase) testCase.Panel; + % Set subject on water restriction + testCase.SubjectUI.Option{end+1} = 'algernon'; + testCase.SubjectUI.Selected = testCase.SubjectUI.Option{end}; + % Ensure there's a weight for today + testCase.Panel.recordWeight(20) + % Find input for water + input = findall(testCase.Parent, 'Style', 'edit'); + % Find labels + labels = findall(testCase.Parent, 'Style', 'text'); + wtr_text = labels(cellfun(@(ch)size(ch,1)==2,{labels.String})); + remaining = labels(cellfun(@(ch)all(size(ch)==[1 2]),{labels.String})); + % Find Give water button + button = findall(testCase.Parent, 'String', 'Give water'); + testCase.assertTrue(numel([input wtr_text remaining button])==4, ... + 'Unable to retrieve all required UI elements'); + + % Test return callback + amount = rand; + input.String = num2str(amount); + input.Callback(input, []); + % Get record from Alyx + endpnt = sprintf('water-requirement/%s?start_date=%s&end_date=%s',... + testCase.SubjectUI.Selected, datestr(now, 'yyyy-mm-dd'),datestr(now, 'yyyy-mm-dd')); + [vals, record] = get_test_data; + + testCase.verifyEqual(record.expected_water, vals(2), 'RelTol', 0.1, 'Expected water mismatch') + testCase.verifyEqual(-record.excess_water, vals(1), 'RelTol', 0.1, 'Excess water mismatch') + testCase.verifyEqual(record.given_water_total, vals(3), 'RelTol', 0.1, 'Given water mismatch') + rem = str2double(remaining.String(2:end-1)); + testCase.verifyEqual(rem, -(record.excess_water+amount), 'RelTol', 0.1, 'Given water mismatch') + + % Test give water callback + button.Callback() + [vals, record] = get_test_data; + + testCase.verifyEqual(record.expected_water, vals(2), 'RelTol', 0.1, 'Expected water mismatch') + testCase.verifyEqual(-record.excess_water, vals(1), 'RelTol', 0.1, 'Excess water mismatch') + testCase.verifyEqual(record.given_water_total, vals(3), 'RelTol', 0.1, 'Given water mismatch') + rem = str2double(remaining.String(2:end-1)); + testCase.verifyEqual(rem, -record.excess_water, 'RelTol', 0.1, 'Given water mismatch') + + % Check log + logPanel = findobj(testCase.hPanel, 'Tag', 'Logging Display'); + expected = sprintf('%.2f for "%s"', amount, testCase.SubjectUI.Selected); + testCase.verifyTrue(contains(logPanel.String{end}, expected), 'Failed to update log') + + function [vals, record] = get_test_data() + wr = testCase.Panel.AlyxInstance.getData(endpnt); % Get today's weight and water record + record = wr.records(end); + vals = [cell2mat(textscan(wtr_text.String(1,:), '%*s %*s %*s %.2f %*s %.2f %*s')),... + cell2mat(textscan(wtr_text.String(2,end-10:end), '%*s %.2f'))]; + vals(isnan(vals)) = 0; + end end function test_giveFutureWater(testCase) testCase.Panel; - end - - function test_changeWaterText(testCase) - testCase.Panel; + subject = testCase.SubjectUI.Option{end}; + testCase.SubjectUI.Selected = subject; + + testCase.Mock.InTest = true; + testCase.Mock.UseDefaults = false; + + % Find Give water in future button + button = findall(testCase.Parent, 'String', 'Give water in future'); + testCase.assertTrue(numel(button)==1, 'Unable to retrieve all required UI elements'); + + amount = rand; + responses = {['0 0 -1 ', num2str(amount), ' -1'], '0 0 -1'}; + testCase.Mock.Dialogs('Future Amounts') = fun.CellSeq.create(responses); + button.Callback(); + + % Check training day added + toTrian = dat.loadParamProfiles('WeekendWater'); + testCase.verifyEqual(toTrian.(subject), [now+3, now+5], 'RelTol', 0.1, ... + 'Failed to add training dates to saved params') + + % Check water posted to Alyx + wr = testCase.Panel.AlyxInstance.getData(['subjects/', subject]); + last = wr.water_administrations(end); + testCase.verifyEqual(Alyx.datenum(last.date_time), now+4, 'AbsTol', 0.01, ... + 'Date of post incorrect') + testCase.verifyEqual(last.water_administered, amount, 'RelTol', 0.1, ... + 'Incorrect amount posted to Alyx') + + % Check log + logPanel = findobj(testCase.hPanel, 'Tag', 'Logging Display'); + expected = contains(logPanel.String{end}, sprintf('%.2f for %s', amount, subject)) ... + && endsWith(logPanel.String{end}, datestr(now+4, 'dddd dd mmm yyyy')); + testCase.verifyTrue(expected, 'Water administration not logged') + expected = endsWith(logPanel.String{end-1}, ... + sprintf('%s marked for training on %s and %s', ... + subject, datestr(now+3, 'dddd'), datestr(now+5, 'dddd'))); + testCase.verifyTrue(expected, 'Training days not logged') + + % Check training day removed + button.Callback(); + toTrian = dat.loadParamProfiles('WeekendWater'); + testCase.verifyEqual(toTrian.(subject), now+3, 'RelTol', 0.1, ... + 'Failed to add training dates to saved params') end function test_round(testCase) diff --git a/tests/fixtures/util/MockDialog.m b/tests/fixtures/util/MockDialog.m index e5b26896..bedd41be 100644 --- a/tests/fixtures/util/MockDialog.m +++ b/tests/fixtures/util/MockDialog.m @@ -24,10 +24,11 @@ InTest logical = false % Containers map of user input, whose keys are either dialog titles or % function call number - Dialogs = containers.Map('KeyType', 'uint32') - % + Dialogs = containers.Map('KeyType', 'uint32', 'ValueType', 'any') + % When true use default values for all dialogs (equivalent to user + % pressing return key) UseDefaults logical = true - % Number of calls to newCall % TODO must be uint + % Number of calls to newCall NumCalls uint32 {mustBeInteger, mustBeNonnegative} = 0 end @@ -40,9 +41,9 @@ end if nargin > 0 && ~strcmp(inst.Dialogs.KeyType, keyType) warning('MockDialog:Instance:SetKeyType', ... - 'KeyType change from to %s. Resetting object', keyType) + 'KeyType change to %s. Resetting object', keyType) inst.reset(); - inst.Dialogs = containers.Map('KeyType', keyType); + inst.Dialogs = containers.Map('KeyType', keyType, 'ValueType', 'any'); end obj = inst; @@ -57,6 +58,7 @@ function reset(obj) remove(obj.Dialogs,keySet); obj.NumCalls = 0; obj.InTest = false; + obj.UseDefaults = true; end function answer = newCall(obj, type, varargin) @@ -120,9 +122,13 @@ function reset(obj) case 'inputdlg' % Find key if ~strcmp(obj.Dialogs.KeyType, 'char') + if ~obj.UseDefaults + assert(obj.Dialogs.Count > 0, 'MockDialog:newCall:NoValuesSet', ... + 'No values saved in Dialogs property') + end key = obj.NumCalls; if key > obj.Dialogs.Count - key = key - obj.Dialogs.Count*floor(key/obj.Dialogs.Count); + key = key - obj.Dialogs.Count*floor(key/uint32(obj.Dialogs.Count)); key = typecast(key, obj.Dialogs.KeyType); end elseif isempty(varargin) From 83ae2b6973e90ebebc324b691242842aeb2deaeb Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Sun, 24 Mar 2019 19:42:06 +0200 Subject: [PATCH 343/393] Bug fixes for round and launchSessionUrl --- +eui/AlyxPanel.m | 27 ++++++---- tests/AlyxPanel_test.m | 69 ++++++++++++++++-------- tests/fixtures/data/viewSubjectData.mat | Bin 8840 -> 8437 bytes tests/fixtures/util/MockDialog.m | 35 +++++++----- 4 files changed, 85 insertions(+), 46 deletions(-) diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index f7c253b9..db767569 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -210,7 +210,7 @@ function delete(obj) delete(obj.LoginTimer) % ... delete it... obj.LoginTimer = []; % ... and remove it end - if ~isempty(obj.WeightTimer) % If there is a timer object + if ~isempty(obj.WeightTimer) && isvalid(obj.WeightTimer) stop(obj.WeightTimer) % Stop the timer... delete(obj.WeightTimer) % ... delete it... obj.WeightTimer = []; % ... and remove it @@ -387,16 +387,22 @@ function recordWeight(obj, weight, subject) obj.dispWaterReq end - function launchSessionURL(obj) + function [stat, url] = launchSessionURL(obj) % Launch the Webpage for the current base session in the % default Web browser. If no session exists for today's date, % a new base session is created accordingly. % + % Outputs: + % stat (double) - returns the status of the operation: + % 0 if successful, 1 or 2 if unsuccessful. + % url (char) - the url for the subject page + % % See also LAUNCHSUBJECTURL ai = obj.AlyxInstance; % determine whether there is a session for this subj and date thisDate = ai.datestr(now); sessions = ai.getData(['sessions?type=Base&subject=' obj.Subject]); + stat = -1; url = []; % If the date of this latest base session is not the same date % as today, then create a new one for today @@ -416,7 +422,8 @@ function launchSessionURL(obj) d.narrative = 'auto-generated session'; d.start_time = thisDate; d.type = 'Base'; - + d.users = {obj.AlyxInstance.User}; + thisSess = ai.postData('sessions', d); if ~isfield(thisSess,'subject') % fail warning('Submitted base session did not return appropriate values'); @@ -432,7 +439,7 @@ function launchSessionURL(obj) return end else - thisSess = sessions{end}; + thisSess = sessions(end); end % parse the uuid from the url in the session object @@ -440,10 +447,10 @@ function launchSessionURL(obj) uuid = u(find(u=='/', 1, 'last')+1:end); % make the admin url - adminURL = fullfile(ai.BaseURL, 'admin', 'actions', 'session', uuid, 'change'); + url = [ai.BaseURL, '/admin/actions/session/', uuid, '/change']; % launch the website - web(adminURL, '-browser'); + stat = web(url, '-browser'); end function [stat, url] = launchSubjectURL(obj) @@ -749,15 +756,17 @@ function log(obj, varargin) % See also ROUND if nargin < 2; direction = 'nearest'; end if nargin < 3; N = 2; end - c = 10^(N-ceil(log10(a))); + c = 10.^(N-ceil(log10(a))); + c(c==Inf) = 0; switch direction case 'up' - A = ceil(a*c)/c; + A = ceil(a.*c)./c; case 'down' - A = floor(a*c)/c; + A = floor(a.*c)./c; otherwise A = round(a, N, 'significant'); end + A(a == 0) = 0; end end end diff --git a/tests/AlyxPanel_test.m b/tests/AlyxPanel_test.m index 0817f9a4..8ff3dea3 100644 --- a/tests/AlyxPanel_test.m +++ b/tests/AlyxPanel_test.m @@ -17,7 +17,7 @@ % Figure handle for any extra figures opened during tests Figure % List of subjects returned by the test database - Subjects = {'ZM_1085'; 'ZM_1087'; 'ZM_1094'; 'ZM_1098'; 'ZM_335'} + Subjects = {'ZM_1085'; 'ZM_1087'; 'ZM_1094'; 'ZM_1098'; 'ZM_335'; 'algernon'} % bui.Selector for setting the subject list in tests SubjectUI % Expected Y-axis labels for the viewSubjectHistory plots @@ -90,7 +90,7 @@ function setupPanel(testCase) 'Failed to log into Alyx'); % Verify subject folders created - present = ismember([{'default'}; testCase.Subjects], dat.listSubjects); + present = ismember([{'default'}; testCase.Subjects(1:end-1)], dat.listSubjects); testCase.verifyTrue(all(present), 'Failed to create missing subject folders') % Ensure local Alyx queue set up @@ -107,6 +107,12 @@ function restoreFigures(testCase) set(0,'DefaultFigureVisible',testCase.FigureVisibleDefault); close(testCase.hPanel) delete(testCase.Panel) + % Double check no figures left + figHandles = findobj('Type', 'figure'); + if ~isempty(figHandles) + idx = cellfun(@(n)any(strcmp(n, testCase.Subjects)),{figHandles.Name}); + close(figHandles(idx)) + end % Remove subject directories dataRepo = getOr(dat.paths, 'mainRepository'); assert(rmdir(dataRepo, 's'), 'Failed to remove test data directory') @@ -118,6 +124,12 @@ function restoreFigures(testCase) methods (TestMethodTeardown) function methodTaredown(testCase) + % Ensure still logged in + if ~testCase.Panel.AlyxInstance.IsLoggedIn + testCase.Panel.login('test_user', 'TapetesBloc18'); + testCase.fatalAssertTrue(testCase.Panel.AlyxInstance.IsLoggedIn,... + 'Failed to log into Alyx'); + end % Ensure local Alyx queue set up alyxQ = getOr(dat.paths,'localAlyxQueue', ['fixtures' filesep 'alyxQ']); testCase.resetQueue(alyxQ); @@ -159,8 +171,9 @@ function test_viewSubjectHistory(testCase) size(testCase.GraphData{i}{1},1)); xData = vertcat(ax_h(i).Children(:).XData); yData = vertcat(ax_h(i).Children(:).YData); - testCase.verifyEqual(xData, testCase.GraphData{i}{1}); - testCase.verifyEqual(yData, testCase.GraphData{i}{2}); + expected = testCase.GraphData{i}; + testCase.verifyEqual(xData(:,1:size(expected{1},2)), expected{1}); + testCase.verifyEqual(yData(:,1:size(expected{2},2)), expected{2}); end end @@ -169,14 +182,12 @@ function test_viewAllSubjects(testCase) testCase.Figure = gcf(); child_handle = testCase.Figure.Children.Children; tableData = child_handle.Data; - expected = {'algernon', '0.00', '<html><body bgcolor=#FFFFFF>0.00</body></html>'}; - testCase.verifyTrue(isequal(tableData, expected)); + testCase.verifyEqual(size(tableData), [1 3], 'Unexpected number of table entries') end function test_dispWaterReq(testCase) % Set subject on water restriction - testCase.SubjectUI.Option{end+1} = 'algernon'; - testCase.SubjectUI.Selected = testCase.SubjectUI.Option{end}; + testCase.SubjectUI.Selected = 'algernon'; % Find label for weight labels = findall(testCase.Parent, 'Style', 'text'); weight_text = labels(cellfun(@(ch)size(ch,1)==2,{labels.String})); @@ -198,18 +209,27 @@ function test_dispWaterReq(testCase) function test_launchSessionURL(testCase) % Test the launch of the session page in the admin Web interface p = testCase.Panel; + testCase.Mock.InTest = true; + testCase.Mock.UseDefaults = false; % Set new subject testCase.SubjectUI.Selected = testCase.SubjectUI.Option{2}; - todaySession = p.AlyxInstance.getSessions(testCase.SubjectUI.Selected, now); - testCase.assertEmpty(todaySession) + todaySession = p.AlyxInstance.getSessions(testCase.SubjectUI.Selected, 'start_date', now); % Add mock user response - mockDialog = MockDialog.instance; - mockDialog.UseDefaults = false; - mockDialog.Dialogs(0) = 'No'; - mockDialog.Dialogs(1) = 'Yes'; + key = 'Would you like to create a new base session?'; + testCase.Mock.Dialogs(key) = iff(isempty(todaySession), 'Yes', 'No'); - testCase.Panel.launchSessionURL() + [failed, url] = testCase.assertWarningFree(@()p.launchSessionURL); + testCase.verifyTrue(~failed, 'Failed to launch subject page in browser') + if isempty(todaySession) + expected = url; + else + uuid = todaySession.url(find(todaySession.url=='/', 1, 'last')+1:end); + expected = ['https://test.alyx.internationalbrainlab.org/admin/', ... + 'actions/session/', uuid, '/change']; + end + + testCase.verifyEqual(url, expected, 'Unexpected url') end function test_launchSubjectURL(testCase) @@ -229,8 +249,7 @@ function test_recordWeight(testCase) testCase.Mock.InTest = true; testCase.Mock.UseDefaults = false; % Set subject on water restriction - testCase.SubjectUI.Option{end+1} = 'algernon'; - testCase.SubjectUI.Selected = testCase.SubjectUI.Option{end}; + testCase.SubjectUI.Selected = 'algernon'; % Find label for weight labels = findall(testCase.Parent, 'Style', 'text'); weight_text = labels(cellfun(@(ch)size(ch,1)==2,{labels.String})); @@ -274,6 +293,7 @@ function test_recordWeight(testCase) src.readGrams = randi(35) + rand; testCase.Panel.updateWeightButton(src,[]) button.Callback() + tic expected = sprintf('Weight today: %.2f', src.readGrams); testCase.verifyTrue(startsWith(strip(weight_text.String(2,:)), expected),... 'Failed to update weight label value') @@ -288,6 +308,9 @@ function test_recordWeight(testCase) % Check post was saved savedPost = dir([getOr(dat.paths, 'localAlyxQueue') filesep '*.post']); testCase.assertNotEmpty(savedPost, 'Post not saved') + + % Ensure button reset + while toc < 10 && ~strcmp(button.String, 'Manual weighing'); end end function test_login(testCase) @@ -301,7 +324,7 @@ function test_login(testCase) testCase.verifyEmpty(findobj(testCase.Parent, 'Enable', 'off')) % Check labels testCase.verifyEqual(labels(3).String, 'You are logged in as test_user') - testCase.verifyTrue(~contains(labels(2).String, 'Log in')) + testCase.verifyTrue(~contains(labels(2).String(1,:), 'Log in')) % Log out testCase.Panel.login; @@ -313,7 +336,7 @@ function test_login(testCase) 'Unexpected number of enabled UI elements') % Check labels testCase.verifyEqual(labels(3).String, 'Not logged in') - testCase.verifyEqual(labels(2).String, 'Log in to see water requirements') + testCase.verifyEqual(labels(2).String(1,:), 'Log in to see water requirements') end function test_updateWeightButton(testCase) @@ -323,13 +346,14 @@ function test_updateWeightButton(testCase) callbk_fn = button.Callback; src.readGrams = randi(35) + rand; testCase.Panel.updateWeightButton(src,[]) + tic % Check button updated testCase.verifyEqual(button.String, sprintf('Record %.1fg',src.readGrams)) testCase.verifyTrue(~isequal(button.Callback, callbk_fn), 'Callback unchanged') callbk_fn = button.Callback; % Check button resets - pause(10) + while toc < 10 && ~strcmp(button.String, 'Manual weighing'); end testCase.verifyEqual(button.String, 'Manual weighing', 'Button failed to reset') testCase.verifyTrue(~isequal(button.Callback, callbk_fn), 'Callback unchanged') end @@ -337,8 +361,7 @@ function test_updateWeightButton(testCase) function test_giveWater(testCase) testCase.Panel; % Set subject on water restriction - testCase.SubjectUI.Option{end+1} = 'algernon'; - testCase.SubjectUI.Selected = testCase.SubjectUI.Option{end}; + testCase.SubjectUI.Selected = 'algernon'; % Ensure there's a weight for today testCase.Panel.recordWeight(20) % Find input for water @@ -393,7 +416,7 @@ function test_giveWater(testCase) function test_giveFutureWater(testCase) testCase.Panel; - subject = testCase.SubjectUI.Option{end}; + subject = 'ZM_335'; testCase.SubjectUI.Selected = subject; testCase.Mock.InTest = true; diff --git a/tests/fixtures/data/viewSubjectData.mat b/tests/fixtures/data/viewSubjectData.mat index 7bd663b95c8630a659a07ceec01f7ad4a4d5371f..a6ad0f926659d0c49830798e02f986edaa324834 100644 GIT binary patch delta 2566 zcmb`^_dnYS1Hf^rwj_2UCDu`t8dZs{)Obp5ZO!0XO4T?@jOHu$nRTwXM^b8>+EjZ* zt%%X0l0H3KNlO#g*dswmp4aQSAD>^IAD+*j@OkIh9$tH52=jOpsdO{wCnW<zC4Hoc zfsu*6o|1u{zHzaeYH=Wcahy1yCc(pl!LQ8;Mtj=I3sP&41)z=5R*m?LT&VO&>zs^d zFz<#$$4B4wzHIRGGK%s6nAh6gtzG<WwX|mZZsD5xS_v#TFnW|biHu^N#j}{q<-Uq! zH=LEyJy#`HRGc>a7u_cTnanWgJCv+wL%<cIR=dnOpANaRYU`}0T!6lN%raKo8ffqg z;SL?>!rYC~Gl`>FC$r$vch%tU)rjvkyzpuXc#ZHsRS+L2@;VgB77F|!MRt>dP@qT< zD02Q3na>mj^Czj#)-#va)BD%6Ij~fm!?2RrsJPgOp4gaB{;)>Ao#bes_GplqdZ3DW zkiqXtavu7iPx^eo6@Af8eZg3L$bJWpXopZ<ahlql+<OiqKQ#~DXdVi0Ci*m!k}-;m zU0#b}frw%IBUmNXBf=VPL82d>Lw~)LQV_MH(@zRXDN=0b_w4{J?yANIUW^YyVu#2_ z`~zQ+r-ha~7nw@pC0-V5_Po;9m8I<@q;2)3?S<uWsuDnEO|)Slu>%EGa}ya`J&(^R zi8gR1HlRQsQ3|0{CwyjS40viJ5v5oc!+ZI<oL@#>jPT`=dtLIktF&8(y}IDDR3E== zMhsu%b>pQBrC?VCa#hxIu%!zm6JxqHNazwxSVb)5v7;@uzVMpmWbf!9h&33oqE%r} zI~lW|f%+Z8Mip5y)!Qta?gpB6<I|<u_GXmriUf>2$RROa{ET!Jjryw7Tad7GIlSio z4g&m_$o~Lhb7UNqZ6L_OBTpl`!+*=STUQKJa(pPtbP}3nQaQ1Voo^E!q}ThY`!AE5 z9GsGx?mHzV+fz8x%$cpK#Jgw}I?EEEC63kKrU9Ig&vcv0i}qttt-toF-AQ>I?AQI( zw^L!;OfdQJ{0DU1S+9RxQv^ue%c;rV;ID1Qgr?;ZyTSwE%^LW;^6u!}ZXg;xRk>)E z@<;GOR^X9VUg1jl2S(@&XSVY3RuA_<<u+H9+}ukmW^dqV%jnqg*dBm>aAy2xVkC>} z3ZS{&XOAZs^paV#wr$HdZHi&Z`D?{7^95W{9v&`tqnrpD(i5{aJ{M{fl*$}c7bB|c zoiCv%i-@LL2?>K=JhaXTTdR$SgSlvxy&OrlDY)9iut(;pT*9JVS#a>Hh(|gbunALw z<v!yUtMV`&82_ucD3-B#pS4dYdbR>!!3Da=H!&xBj>if8h>x86$I$TxAWq@ip95Gf z;XW3{C4DDpmO3(u-d6f?-fok}Be4NYHrJ3dV0QQgv&ANk=pMQx0kvmWjIe|g=gmrw zaOZ7HuLz*{Clm6aZec73Y>sX`S)Ee6Q2EM&y>F_siHc7NGW)!JA&g4_-b1(?Pzzaa zia;ih8Kbv270H14R3BvF>~+Zim(u%>6~Rbglj9a8%l3L~$Z!F+Em42hWmM1IPEG&2 z2m45#|NNR0_ACgKAKQI(M?|hTNb+OtH96(G4aB(+r+j^}d98aBCZh^r$~xB43%}mb za<m4jUwKQp&yPils|JTR|EL`s_2Xz+Jqw>qCK!j(@QLIzyz>BiiGH?1fBcNzPoJh> z*wGDUh{bRRdF}vr1v}8XG|Ys<r@sLY#=VjW3@UJQEtmN+DQe+1P~TU&^ddZp^~ru2 z`>bUdy%c1DNxg`{A+=L8seE~Z(9^*ZO<^%eDM>^qOZ<*-Q{dGMYgk6fbzw9hhZ40M zG)6(7R29FpiD>@Tp`vI|D+#25tB4lGrY*XPBv3E^N;L0+SlSqS4(jgkAcj;iVlGt8 zQxP&1i2>_U)q2l?-Xa@u>i2O*>sF6m@Lqi`yPk&lGt0ykdhi_4D|krcfm_+=W!UXl z7-p&O@WQTn=w*da<ejScfmi1}j>IRcWXehQ6!?ObZ#$3UeL+Q=<n}Z$MZyGkH7@Hi zMJA%^LR>b2A{9XdTi^mH7fuH2DbSN^0E~z4%atQ}0U|UIk{%?KB5d>J5=;P1iiYv7 z3_8I0!Fuq~!8<U)RiXrbP)<Env}@>k`T3F{5hs3Rxxl9&fk`04Tkk@Drtyx#B)?n= zWPebBmM(}%gJMYM?g&}53#AYtr^-CCHcHyKx^}U&K;!1rKq1`Ic9B~)8X4D6*R6hN z#%bhay%y)|5Eo=9A!cmO{a`H_ky_9$Y-D2)fs>vSSqwC-9RfR~6WT@prOX#^qki|A z(VEre4P0M4gb9#%Q9_B)LVI8I<Z(5wJY!YjxmB=2k}yiRo@#h{e5zd2y+>9If6xDJ z(cVs@(0$SJS@u%Wnc3Q3O~CDdNOt7%TKBwJ#X+()cJAfFm4K4rQ)1f7v%=r+SuY<r zy=);*FIq>|#WveDF8eD({Cwl0Lf<PW&-T_mT1L7|`U3%mrMI<cSvsr#(n`CLv2-&2 zWGGZ&m}y?E>uemXLUUieHTKDhQ|$iyENgyC)*q7FjSc`}-$dvSG#vSyjqKm}=I@MO zAV_J^a%X6f!Ox_^<N6L=_1k*$k;6%NxTc-+<R#r%N&Pa2Pwvw~%;(NkRmJYoyiG4c zCEYs76mX(=ae5p+gjUSI{f^fbr$4+Ao!-0&4(7PDnVLhJ{N~kSmTDJ}S0@mL+HS{8 z_rx^&U!rDJ4%u#xPQ5nm>3i*iKEHkCJn0#F)6)5Kx#MAMN}M$&qI7TnqKj`#uou0e z{7u7#1D@My5N@gSn=*}lSE26@uN?MTs<_9oBM@O}xBFT^YoNb&0ZJEeDrCC$J2xar zPrIaO&a{2+I)-8vXF*czvhgT*p+@`^G<Nfs?$2LG^SVRwi|cN=9J1<i1XZt~w_XGT z@dj@w3KwZ7<{M_8K#g;BweK$RH~WI%by&h^ORG+SqX(x)%%Er8)6mu|%9hlzS9Wp; zIM8f<{2e+aIs3Yik#OW~IsaN~3t3AmYJB8#0cupS;NNT?zVgJ!K7GuWjPLQAY-2Ji zq}Qvk1eWh5ElGx(=clgRm2WRDL8MIS8bH8UJk#4HZMw}P+<XQ(px3?RaQyiFU`vVi zJLk#HiF#z)b}N?PzG_6zY$_ycePbEb4X_e!9j~Ch^TLdpVu#sySli9K{7b`ZoEY8o sUP#SIuYWJrt)c0J1yPS(%%AQ*2<hX3I}OEz1kX1g*AY6cq#Pjs1}R|gGXMYp delta 2963 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 JJ+rM5NDo#u@b3Ts diff --git a/tests/fixtures/util/MockDialog.m b/tests/fixtures/util/MockDialog.m index bedd41be..52d7773e 100644 --- a/tests/fixtures/util/MockDialog.m +++ b/tests/fixtures/util/MockDialog.m @@ -113,24 +113,12 @@ function reset(obj) if strcmp(obj.Dialogs.KeyType, 'char') key = varargin{1}; % Key is prompt else - key = obj.NumCalls; - if key > obj.Dialogs.Count - key = key - obj.Dialogs.Count*floor(key/obj.Dialogs.Count); - key = typecast(key, obj.Dialogs.KeyType); - end + key = obj.fromCount; end case 'inputdlg' % Find key if ~strcmp(obj.Dialogs.KeyType, 'char') - if ~obj.UseDefaults - assert(obj.Dialogs.Count > 0, 'MockDialog:newCall:NoValuesSet', ... - 'No values saved in Dialogs property') - end - key = obj.NumCalls; - if key > obj.Dialogs.Count - key = key - obj.Dialogs.Count*floor(key/uint32(obj.Dialogs.Count)); - key = typecast(key, obj.Dialogs.KeyType); - end + key = obj.fromCount; elseif isempty(varargin) key = 'Input'; elseif length(varargin) == 1 % Use prompt string @@ -161,5 +149,24 @@ function reset(obj) end end + + methods (Access = private) + + function key = fromCount() + if strcmp(obj.Dialogs.KeyType, 'char') + key = []; + return + elseif ~obj.UseDefaults + assert(obj.Dialogs.Count > 0, 'MockDialog:newCall:NoValuesSet', ... + 'No values saved in Dialogs property') + end + key = obj.NumCalls; + if key > obj.Dialogs.Count + key = key - obj.Dialogs.Count*floor(key/uint32(obj.Dialogs.Count)); + key = typecast(key, obj.Dialogs.KeyType); + end + end + + end end \ No newline at end of file From 3b810958744d4e96c56c48772c9dacd524250491 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Mon, 25 Mar 2019 19:26:38 +0200 Subject: [PATCH 344/393] Added contributing guidlines --- CONTRIBUTING.md | 104 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..df338d6e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,104 @@ +# Contributing + +When contributing to this repository, please first discuss the change you wish to make via issue, +email, or any other method with the owners of this repository before making a change. + +Please note we have a code of conduct, please follow it in all your interactions with the project. + +## Pull Request Process + +We have two main branches: the dev branch where we deploy new features and the master branch which contains the stable build that most users will work with. Below is the general for adding a new feature: + +1. Ensure any new code is accompanied by a test that adequately covers all expected use cases, and [MATLAB documentation](https://www.mathworks.com/help/matlab/matlab_prog/add-help-for-your-program.html) +2. Update the README.md with details of changes to the interface, wherever relavent. This includes changes to any major UIs or install scripts. It's worth also ensuring the configuration scripts and tutorials in the docs/ dirctory are up to date. +3. Create a pull request to merge into the dev branch. Before merging the code must pass all tests and be approved by one reviewer. +4. Once merged into dev the changes may be summerized in the release notes. +5. Once there have been sufficient changes to the dev branch to be considered a major version and all changes have been deployed for at least a week, the team will open a pull request to merge into the master branch. +6. Before merging into master the submodule dependencies should be updated and checked first. The previous code is archived in the releases tab. +3. Increase the version numbers in any examples files and the README.md to the new version that this + Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/). +4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you do not have permission to do that, you may request the second reviewer to merge it for you. + +## Style guidelines + +Although there aren't any strict guidelines we suggest making your code as consistent with the rest of the repository as possible. Some examples: +* Variables and function names generally follow the MATLAB convention of 'Dromedary case' (`expRef`, `inferParameters`) and when defining a commonly used variable be consistent with the names in other files. If assigning an output of a function, try to use name defined in that functions' header (`[expRef, expDate, expSequence] = listExps(subjects)`). +* Class names and properties are in 'camel case' (`AlyxPanel`, `obj.Token`). +* In general clarity > brevity. Don't be afraid to spread things out over a number of lines and to add in-line comments. Long variable names are often much clearer (`inputSensorPosCount` vs `iputPosN`). +* There are a number of utility functions in [cb-tools](https://github.com/cortex-lab/Rigbox/tree/master/cb-tools/burgbox) that we encourage the use of. Many of these implementations of functional programming methods. +* Information on the physical organization of the repository can be found [here](https://github.com/cortex-lab/Rigbox/issues/123#issue-422187511). + +## Code of Conduct + +### Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +### Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or +advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +### Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +### Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +### Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at [INSERT EMAIL ADDRESS]. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +### Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ From cb6f91e6bdc6416d7bb53a118d3aacea338336a8 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Mon, 25 Mar 2019 19:27:42 +0200 Subject: [PATCH 345/393] Moved UML to doc folder --- Rigbox UML.pdf => doc/Rigbox UML.pdf | Bin 1 file changed, 0 insertions(+), 0 deletions(-) rename Rigbox UML.pdf => doc/Rigbox UML.pdf (100%) diff --git a/Rigbox UML.pdf b/doc/Rigbox UML.pdf similarity index 100% rename from Rigbox UML.pdf rename to doc/Rigbox UML.pdf From ff248c973cf58039f98d71291fe9f4b34473d544 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 27 Mar 2019 13:22:33 +0000 Subject: [PATCH 346/393] added images branch from signals --- signals | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/signals b/signals index bb6086a0..ca936d51 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit bb6086a019e0382a937cbe4ccf0925c1fcd8d6e5 +Subproject commit ca936d515388ae45c6f185f353e157ffab2cb509 From f3bdf554f5e38ba8b6ef97282e48d97dcbffc85f Mon Sep 17 00:00:00 2001 From: kevin-j-miller <kjmd10@gmail.com> Date: Wed, 27 Mar 2019 13:46:34 +0000 Subject: [PATCH 347/393] corrected submodule update to only change signals, not alyx-matlab --- alyx-matlab | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alyx-matlab b/alyx-matlab index 9c2f6b5b..55e3e2ad 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit 9c2f6b5b2583a4e5c610b7469e1251a12ad5d743 +Subproject commit 55e3e2adc57c42a1c84dbc0b8570c428f246f65d From b7c406b681807071b4a3268178630c2a1daae9de Mon Sep 17 00:00:00 2001 From: kevin-j-miller <kjmd10@gmail.com> Date: Wed, 27 Mar 2019 13:50:20 +0000 Subject: [PATCH 348/393] trying again to get the submodules right --- alyx-matlab | 2 +- signals | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/alyx-matlab b/alyx-matlab index 55e3e2ad..9c2f6b5b 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit 55e3e2adc57c42a1c84dbc0b8570c428f246f65d +Subproject commit 9c2f6b5b2583a4e5c610b7469e1251a12ad5d743 diff --git a/signals b/signals index ca936d51..85dcef2f 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit ca936d515388ae45c6f185f353e157ffab2cb509 +Subproject commit 85dcef2fa003040ee2037c497387c92b968e4ed3 From b3197892ddb60c93b1eba7c3c81cfb706503f9c7 Mon Sep 17 00:00:00 2001 From: kevin-j-miller <kjmd10@gmail.com> Date: Wed, 27 Mar 2019 14:04:40 +0000 Subject: [PATCH 349/393] Hack-fixed a bug that caused string parameters to not be editable --- +eui/ParamEditor.m | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/+eui/ParamEditor.m b/+eui/ParamEditor.m index 85d62b6c..3cd1322c 100644 --- a/+eui/ParamEditor.m +++ b/+eui/ParamEditor.m @@ -218,6 +218,8 @@ function addEmptyConditionToParam(obj, name) % and notifies listeners of the change via the Changed event. % % See also EUI.FIELDPANEL/ONEDIT, EUI.CONDITIONPANEL/ONEDIT + + if nargin < 4; row = 1; end currValue = obj.Parameters.Struct.(name)(:,row); if iscell(currValue) @@ -226,7 +228,11 @@ function addEmptyConditionToParam(obj, name) obj.Parameters.Struct.(name){:,row} = newValue; else newValue = obj.controlValue2Param(currValue, value); - obj.Parameters.Struct.(name)(:,row) = newValue; + if isstr(newValue) + obj.Parameters.Struct.(name) = newValue; + else + obj.Parameters.Struct.(name)(:,row) = newValue; + end end notify(obj, 'Changed'); end From 7259eefe28641ae6e558442166df495718206a41 Mon Sep 17 00:00:00 2001 From: Rig4 <p.zatka@ucl.ac.uk> Date: Wed, 27 Mar 2019 14:37:58 +0000 Subject: [PATCH 350/393] updated signals submodule (merged 'images' and 'voidsignal_fix' into 'dev') --- signals | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/signals b/signals index bb6086a0..696ea949 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit bb6086a019e0382a937cbe4ccf0925c1fcd8d6e5 +Subproject commit 696ea9493f7edfe70c12ea5496b604e112a5db63 From 64b81ab8c2618c5feda1981c991d18d126f84283 Mon Sep 17 00:00:00 2001 From: jaib1 <j.bhagat@ucl.ac.uk> Date: Fri, 29 Mar 2019 12:33:53 +0000 Subject: [PATCH 351/393] bug fix for uicontextmenu in Condition and Field Panels --- +eui/ConditionPanel.m | 5 +++-- +eui/FieldPanel.m | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/+eui/ConditionPanel.m b/+eui/ConditionPanel.m index 88247815..d29ee716 100644 --- a/+eui/ConditionPanel.m +++ b/+eui/ConditionPanel.m @@ -43,8 +43,9 @@ % See also EUI.PARAMEDITOR, EUI.FIELDPANEL obj.ParamEditor = ParamEditor; obj.UIPanel = uix.VBox('Parent', f); - % Create a child menu for the uiContextMenus - c = uicontextmenu; + % Create a child menu for the uiContextMenus. The input arg is the + % figure holding the panel + c = uicontextmenu(ancestor(f, 'Figure')); obj.UIPanel.UIContextMenu = c; obj.ContextMenus = uimenu(c, 'Label', 'Make Global', ... 'MenuSelectedFcn', @(~,~)obj.makeGlobal, 'Enable', 'off'); diff --git a/+eui/FieldPanel.m b/+eui/FieldPanel.m index d0e48c37..fb8774e1 100644 --- a/+eui/FieldPanel.m +++ b/+eui/FieldPanel.m @@ -61,7 +61,7 @@ % See also ONEDIT, EXP.PARAMETERS/TITLE, EUI.PARAMEDITOR/BUILDUI if nargin < 3; type = 'edit'; end if isempty(obj.ContextMenu) - obj.ContextMenu = uicontextmenu; + obj.ContextMenu = uicontextmenu(ancestor(obj.UIPanel, 'Figure')); uimenu(obj.ContextMenu, 'Label', 'Make Conditional', ... 'MenuSelectedFcn', @(~,~)obj.makeConditional); end From 5795d47d88212e64a392c5308485f548a3026f3f Mon Sep 17 00:00:00 2001 From: Jai Bhagat <j.bhagat@ucl.ac.uk> Date: Mon, 1 Apr 2019 14:55:55 +0100 Subject: [PATCH 352/393] updated 'signals' (hotfix updates) and 'alyx-matlab' (merged 'Tests' into 'dev') submodules --- alyx-matlab | 2 +- signals | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/alyx-matlab b/alyx-matlab index 9c2f6b5b..085d702d 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit 9c2f6b5b2583a4e5c610b7469e1251a12ad5d743 +Subproject commit 085d702d4f1498b319265e440c5dbce9cca5767f diff --git a/signals b/signals index 696ea949..c684fdba 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit 696ea9493f7edfe70c12ea5496b604e112a5db63 +Subproject commit c684fdba60d2b5717ce0d52e56dd7a80da8a3f4e From 4cf439b12a622068aed288655fab254d80d40ce9 Mon Sep 17 00:00:00 2001 From: jaib1 <j.bhagat@ucl.ac.uk> Date: Wed, 17 Apr 2019 14:23:41 +0100 Subject: [PATCH 353/393] updated signals (merged 'cleanup' into 'dev') submodule --- signals | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/signals b/signals index c684fdba..6c4107d1 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit c684fdba60d2b5717ce0d52e56dd7a80da8a3f4e +Subproject commit 6c4107d1aa7ca0335f3cc59a703471c072c16084 From 9521b66bf8d3bc4e37f703b141213d097dbd9777 Mon Sep 17 00:00:00 2001 From: Jai Bhagat <j.bhagat@ucl.ac.uk> Date: Wed, 24 Apr 2019 10:45:18 +0100 Subject: [PATCH 354/393] updated signals submodule --- signals | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/signals b/signals index 6c4107d1..259fbaf3 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit 6c4107d1aa7ca0335f3cc59a703471c072c16084 +Subproject commit 259fbaf34316bc4e77a1089e8b972c60d5dab3a1 From 816b2ff205a2581519d563c3b102e83ccda42954 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Mon, 29 Apr 2019 14:41:22 +0300 Subject: [PATCH 355/393] Fix for expStop error and bug where event updates twice --- +exp/SignalsExp.m | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/+exp/SignalsExp.m b/+exp/SignalsExp.m index d2fb2631..45e520b7 100644 --- a/+exp/SignalsExp.m +++ b/+exp/SignalsExp.m @@ -151,7 +151,6 @@ obj.Time = net.origin('t'); obj.Events.expStart = net.origin('expStart'); obj.Events.newTrial = net.origin('newTrial'); - obj.Events.expStop = net.origin('expStop'); obj.Inputs.wheel = net.origin('wheel'); obj.Inputs.wheelMM = obj.Inputs.wheel.map(@... (x)obj.Wheel.MillimetresFactor*(x-obj.Wheel.ZeroOffset)).skipRepeats(); @@ -171,7 +170,6 @@ globalPars, allCondPars, advanceTrial); obj.Events.trialNum = obj.Events.newTrial.scan(@plus, 0); % track trial number lastTrialOver = then(~hasNext, true); -% obj.Events.expStop = then(~hasNext, true); % run experiment definition if ischar(paramStruct.defFunction) expDefFun = fileFunction(paramStruct.defFunction); @@ -183,14 +181,14 @@ fprintf('takes %i args\n', nargout(expDefFun)); expDefFun(obj.Time, obj.Events, obj.Params, obj.Visual, obj.Inputs,... obj.Outputs, obj.Audio); + obj.Events.expStop = iff(isfield(obj.Events, 'expStop'),... + @()merge(obj.Events.expStop, lastTrialOver), lastTrialOver); % listeners obj.Listeners = [ obj.Events.expStart.map(true).into(advanceTrial) %expStart signals advance obj.Events.endTrial.into(advanceTrial) %endTrial signals advance advanceTrial.map(true).keepWhen(hasNext).into(obj.Events.newTrial) %newTrial if more - lastTrialOver.into(obj.Events.expStop) %newTrial if more - onValue(obj.Events.expStop, @(~)quit(obj));]; -% obj.Events.trialNum.onValue(fun.partial(@fprintf, 'trial %i started\n'))]; + obj.Events.expStop.onValue(@(~)quit(obj))]; % initialise the parameter signals globalPars.post(rmfield(globalStruct, 'defFunction')); allCondPars.post(allCondStruct); @@ -407,7 +405,7 @@ function log(obj, field, value) function quit(obj, immediately) if isempty(obj.Events.expStop.Node.CurrValue) - obj.Events.expStop.post(true); + obj.Events.expStop = net.origin('expStop').post(true); end %stop delay timers. todo: need to use a less global tag tmrs = timerfind('Tag', 'sig.delay'); From f442efaad0cab7868ab21c383cee9af56ab736f3 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Mon, 29 Apr 2019 19:42:42 +0300 Subject: [PATCH 356/393] Storing/referencing figure now more efficient --- +eui/ConditionPanel.m | 2 +- +eui/FieldPanel.m | 2 +- +eui/ParamEditor.m | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/+eui/ConditionPanel.m b/+eui/ConditionPanel.m index d29ee716..cbbac3c1 100644 --- a/+eui/ConditionPanel.m +++ b/+eui/ConditionPanel.m @@ -45,7 +45,7 @@ obj.UIPanel = uix.VBox('Parent', f); % Create a child menu for the uiContextMenus. The input arg is the % figure holding the panel - c = uicontextmenu(ancestor(f, 'Figure')); + c = uicontextmenu(ParamEditor.Root); obj.UIPanel.UIContextMenu = c; obj.ContextMenus = uimenu(c, 'Label', 'Make Global', ... 'MenuSelectedFcn', @(~,~)obj.makeGlobal, 'Enable', 'off'); diff --git a/+eui/FieldPanel.m b/+eui/FieldPanel.m index fb8774e1..a409af1d 100644 --- a/+eui/FieldPanel.m +++ b/+eui/FieldPanel.m @@ -61,7 +61,7 @@ % See also ONEDIT, EXP.PARAMETERS/TITLE, EUI.PARAMEDITOR/BUILDUI if nargin < 3; type = 'edit'; end if isempty(obj.ContextMenu) - obj.ContextMenu = uicontextmenu(ancestor(obj.UIPanel, 'Figure')); + obj.ContextMenu = uicontextmenu(obj.ParamEditor.Root); uimenu(obj.ContextMenu, 'Label', 'Make Conditional', ... 'MenuSelectedFcn', @(~,~)obj.makeConditional); end diff --git a/+eui/ParamEditor.m b/+eui/ParamEditor.m index 3cd1322c..7244bcb9 100644 --- a/+eui/ParamEditor.m +++ b/+eui/ParamEditor.m @@ -54,8 +54,7 @@ parent = figure('Name', 'Parameters', 'NumberTitle', 'off',... 'Toolbar', 'none', 'Menubar', 'none', 'DeleteFcn', @(~,~)obj.delete); end - obj.Root = parent; - while ~isa(obj.Root, 'matlab.ui.Figure'); obj.Root = obj.Root.Parent; end + obj.Root = ancestor(parent, 'Figure'); % obj.Listener = event.listener(parent, 'SizeChanged', @(~,~)obj.onResize); obj.Parent = uix.HBox('Parent', parent); obj.GlobalUI = eui.FieldPanel(obj.Parent, obj); From 0206c4d6c7f34331f52035921ef75bde5714d0cd Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Mon, 29 Apr 2019 21:45:52 +0300 Subject: [PATCH 357/393] Added full string support in ParamEditor --- +eui/ParamEditor.m | 14 ++++++++------ tests/ParamEditor_test.m | 10 +++++++++- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/+eui/ParamEditor.m b/+eui/ParamEditor.m index 7244bcb9..b7c7d6d1 100644 --- a/+eui/ParamEditor.m +++ b/+eui/ParamEditor.m @@ -217,8 +217,6 @@ function addEmptyConditionToParam(obj, name) % and notifies listeners of the change via the Changed event. % % See also EUI.FIELDPANEL/ONEDIT, EUI.CONDITIONPANEL/ONEDIT - - if nargin < 4; row = 1; end currValue = obj.Parameters.Struct.(name)(:,row); if iscell(currValue) @@ -227,10 +225,10 @@ function addEmptyConditionToParam(obj, name) obj.Parameters.Struct.(name){:,row} = newValue; else newValue = obj.controlValue2Param(currValue, value); - if isstr(newValue) + if ischar(newValue) obj.Parameters.Struct.(name) = newValue; else - obj.Parameters.Struct.(name)(:,row) = newValue; + obj.Parameters.Struct.(name)(:,row) = newValue; end end notify(obj, 'Changed'); @@ -347,13 +345,14 @@ function onResize(obj) case 'logical' data = data ~= 0; % If logical do nothing, basically. case 'string' + data = arrayfun(@(n) strjoin(data(:,n), ', '), 1:size(data,2)); data = char(data); % Strings not allowed in condition table data otherwise if isnumeric(data) % format numeric types as string number list strlist = mapToCell(@num2str, data); data = strJoin(strlist, ', '); - elseif iscellstr(data) + elseif iscellstr(data) %#ok<ISCLSTR> data = strJoin(data, ', '); end end @@ -374,7 +373,10 @@ function onResize(obj) case 'logical' data = data ~= 0; case 'char' - % do nothing - strings stay as strings + % do nothing - chars stay as they are + case 'string' + if ischar(data); data = strsplit(string(data), {', ' ','}); end + data = data(:); otherwise if isnumeric(currParam) % parse string as numeric vector diff --git a/tests/ParamEditor_test.m b/tests/ParamEditor_test.m index 783e70f0..ff2263cf 100644 --- a/tests/ParamEditor_test.m +++ b/tests/ParamEditor_test.m @@ -156,8 +156,16 @@ function test_paramValue2Control(testCase) end % Test string data - % TODO Outcome will change in near future + % Strings converted to char arrays due to Table limitations testCase.verifyEqual(PE.paramValue2Control("hello"), 'hello') + % Strings should be joined across rows + testCase.verifyEqual(PE.paramValue2Control(["hello";"hi"]), 'hello, hi') + % Verify conditional string parameters are parsed correctly + actual = PE.paramValue2Control(["hello","goodbye";"hi","bye"]); + testCase.verifyEqual(size(actual), [1 12 2]) + % Check that string control values are converted correctly + actual = PE.controlValue2Param(["hello","hi"],actual(:,:,2)); + testCase.verifyEqual(["goodbye";"bye"], actual) % Test numeric data testCase.verifyEqual(PE.paramValue2Control(pi), '3.1416') From 6c52421a2e23553f11dc6e46ff910548b33264d1 Mon Sep 17 00:00:00 2001 From: Jai <j.bhagat@ucl.ac.uk> Date: Mon, 6 May 2019 17:48:55 +0100 Subject: [PATCH 358/393] merge 'devUpdateContributing' into 'dev' --- CONTRIBUTING.md | 70 ++++++++++++------------- readme.md => README.md | 10 +++- {doc => docs}/Rigbox UML.pdf | Bin {doc => docs}/setup/Alyx_config.m | 0 {doc => docs}/setup/hardware_config.m | 0 {doc => docs}/setup/websocket_config.m | 0 {doc => docs}/using_dat_package.m | 0 {doc => docs}/using_parameters.m | 0 8 files changed, 43 insertions(+), 37 deletions(-) rename readme.md => README.md (96%) rename {doc => docs}/Rigbox UML.pdf (100%) rename {doc => docs}/setup/Alyx_config.m (100%) rename {doc => docs}/setup/hardware_config.m (100%) rename {doc => docs}/setup/websocket_config.m (100%) rename {doc => docs}/using_dat_package.m (100%) rename {doc => docs}/using_parameters.m (100%) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index df338d6e..0456b577 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,43 +1,45 @@ -# Contributing +When contributing to this repository, please first discuss the change you wish to make via creation of a [github issue](https://github.com/cortex-lab/Rigbox/issues) (preferred), or email with the [project maintainers](#project-maintainers) -When contributing to this repository, please first discuss the change you wish to make via issue, -email, or any other method with the owners of this repository before making a change. +Please adhere to our [Code of Conduct](#code-of-conduct). -Please note we have a code of conduct, please follow it in all your interactions with the project. +We roughly follow a [gitflow workflow](https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow) without using 'git-flow' extensions. We also support [forking workflows](https://www.atlassian.com/git/tutorials/comparing-workflows/forking-workflow) for contributors who wish to fork this repository and maintain their own local versions. -## Pull Request Process +If you are unfamiliar with repositories with submodules, please first read this helpful [blog post.](https://github.blog/2016-02-01-working-with-submodules/) -We have two main branches: the dev branch where we deploy new features and the master branch which contains the stable build that most users will work with. Below is the general for adding a new feature: +## Contributing - Our Pull Request Process -1. Ensure any new code is accompanied by a test that adequately covers all expected use cases, and [MATLAB documentation](https://www.mathworks.com/help/matlab/matlab_prog/add-help-for-your-program.html) -2. Update the README.md with details of changes to the interface, wherever relavent. This includes changes to any major UIs or install scripts. It's worth also ensuring the configuration scripts and tutorials in the docs/ dirctory are up to date. -3. Create a pull request to merge into the dev branch. Before merging the code must pass all tests and be approved by one reviewer. -4. Once merged into dev the changes may be summerized in the release notes. -5. Once there have been sufficient changes to the dev branch to be considered a major version and all changes have been deployed for at least a week, the team will open a pull request to merge into the master branch. -6. Before merging into master the submodule dependencies should be updated and checked first. The previous code is archived in the releases tab. -3. Increase the version numbers in any examples files and the README.md to the new version that this - Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/). -4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you do not have permission to do that, you may request the second reviewer to merge it for you. +Following the gitflow workflow, Rigbox and its main submodules (signals, alyx-matlab, and wheelAnalysis) each have two main branches: the dev branch is where new features are deployed, and the master branch contains the stable build that most users will work with. Contributors should create a new branch for any changes/additions they wish to make. Contributors should then create a pull request for the contributed branch. If making a change to a submodule, a pull request should be sent to that submodule's repository. (e.g. if a user is making a change to a file within the signals repository, a pull request should be made to the [signals repository](https://github.com/cortex-lab/signals/pulls), not to the Rigbox repository.) **All pull requests should be made to the dev branch of the appropriate repository.** All pull requests will be reviewed by the project maintainers. Below are procedural guidelines to follow for contributing via a pull request: -## Style guidelines +1. Ensure any new file follows [MATLAB documentation guidelines](https://www.mathworks.com/help/matlab/matlab_prog/add-help-for-your-program.html) and is accompanied by a test file that adequately covers all expected use cases. This test file should be placed in the appropriate repository's 'tests' folder, and follow the naming convention of `<newFile>_test`. If the contributor is not adding a new file but instead changing/adding to an exisiting file that already has an accompanying test file, a test that accompanies the contributor's code should be added to the existing test file. See the [Rigbox/tests folder](https://github.com/cortex-lab/Rigbox/tree/dev/tests) for examples. +2. Ensure all existing tests for the entire repo pass. To do so, in MATLAB within the `Rigbox\tests` folder, run +> results = runtests(pwd,'IncludeSubfolders',true) +3. When making changes to the interface, update the README.md with relevant details. This includes changes to any major UIs or installation scripts. It's also worth ensuring the configuration scripts and tutorials in the 'docs' folder for Rigbox, and the 'docs' folder for any submodule(s) which may have been changed, are up-to-date. +4. Create a pull request to merge the contributed branch into the dev branch. The submodule dependencies should be first checked and updated, if necessary. The branch will then be merged upon approval by at least one authorized reviewer. Once merged into dev, the changes may be summarized in the release notes. +5. Once the dev branch has accumulated sufficient changes for it to be considered a new major version, and all changes have been deployed for at least a week, the project maintainers will open a pull request to merge dev into the master branch. The project maintainers should ensure that the version numbers in any relevant files and the README.md are up-to-date. The versioning specification numbering used is [SemVer](http://semver.org/). Previous versions are archived in [releases](https://github.com/cortex-lab/Rigbox/releases). -Although there aren't any strict guidelines we suggest making your code as consistent with the rest of the repository as possible. Some examples: -* Variables and function names generally follow the MATLAB convention of 'Dromedary case' (`expRef`, `inferParameters`) and when defining a commonly used variable be consistent with the names in other files. If assigning an output of a function, try to use name defined in that functions' header (`[expRef, expDate, expSequence] = listExps(subjects)`). -* Class names and properties are in 'camel case' (`AlyxPanel`, `obj.Token`). -* In general clarity > brevity. Don't be afraid to spread things out over a number of lines and to add in-line comments. Long variable names are often much clearer (`inputSensorPosCount` vs `iputPosN`). -* There are a number of utility functions in [cb-tools](https://github.com/cortex-lab/Rigbox/tree/master/cb-tools/burgbox) that we encourage the use of. Many of these implementations of functional programming methods. +## Style Guidelines + +Although there aren't any strict guidelines, we suggest making your code as consistent with the rest of the repository as possible. Some examples: +* For a particularly well-documented function, see ['sig.timeplot'](https://github.com/cortex-lab/signals/blob/259fbaf34316bc4e77a1089e8b972c60d5dab3a1/%2Bsig/timeplot.m). For a particularly well-documented class, see ['hw.Timeline'](https://github.com/cortex-lab/Rigbox/blob/dev/%2Bhw/Timeline.m) +* Variables and function names generally follow the MATLAB convention of 'Dromedary case' (e.g. `expRef`, `inferParameters`). +* Class names and properties are in 'camel case' (e.g. `AlyxPanel`, `obj.Token`). +* When defining commonly used variables, be consistent with the names across files. +* If assigning an output of a function, try to use the name defined in that functions' header (e.g. `[expRef, expDate, expSequence] = listExps(subjects)`). +* In general, clarity > brevity. Don't be afraid to spread things out over a number of lines and to add in-line comments. Long variable names are often much clearer (e.g. `inputSensorPosCount` vs `inpPosN`). +* There are a number of utility functions in [cb-tools](https://github.com/cortex-lab/Rigbox/tree/master/cb-tools/burgbox) that we encourage the use of. Many of these are implementations of functional programming methods. * Information on the physical organization of the repository can be found [here](https://github.com/cortex-lab/Rigbox/issues/123#issue-422187511). +## Project Maintainers + +Rigbox is currently maintained and developed by Miles Wells (miles.wells@ucl.ac.uk), Jai Bhagat (j.bhagat@ucl.ac.uk), and a number of others at [CortexLab](https://www.ucl.ac.uk/cortexlab). The majority of the code was written by [Chris Burgess](https://github.com/dendritic/). + ## Code of Conduct ### Our Pledge In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, gender identity and expression, level of experience, -nationality, personal appearance, race, religion, or sexual identity and -orientation. +maintainers and contributors pledge to make participation in our project and +our community an enjoyable and harassment-free experience for everyone. ### Our Standards @@ -64,14 +66,14 @@ advances ### Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in +behavior, and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. +reject comments, commits, code, wiki edits, issues, and other contributions, particularly those +that are not aligned to this Code of Conduct. + +Project maintainers may ban temporarily or permanently any contributor for behaviors that are deemed inappropriate, threatening, offensive, or harmful. ### Scope @@ -85,11 +87,7 @@ further defined and clarified by project maintainers. ### Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at [INSERT EMAIL ADDRESS]. All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. +reported by contacting the project maintainers. All complaints will be reviewed and investigated, and will result in a response that is deemed necessary and appropriate to the circumstances. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other diff --git a/readme.md b/README.md similarity index 96% rename from readme.md rename to README.md index e238d412..82329293 100644 --- a/readme.md +++ b/README.md @@ -140,6 +140,14 @@ The "tests" directory contains code for running unit tests within Rigbox. Additional information on the [alyx-matlab](https://github.com/cortex-lab/alyx-matlab), [npy-matlab](https://github.com/kwikteam/npy-matlab), [signals](https://github.com/cortex-lab/signals) and [wheelAnalysis](https://github.com/cortex-lab/wheelAnalysis) submodules can be found in their respective github repositories. +## Acknowledgements + +<Add links to third party code> + +## Contributing + +Please read [CONTRIBUTING.md]() for details on our code of conduct, and the process of contributing code to this repository via pull requests. + ## Authors -The majority of the Rigbox code was written by [Chris Burgess](https://github.com/dendritic/) in 2013. It is now maintained and developed by a number of people at [CortexLab](https://www.ucl.ac.uk/cortexlab). +The majority of the Rigbox code was written by [Chris Burgess](https://github.com/dendritic/) in 2013. It is now maintained and developed by Miles Wells, Jai Bhagat and a number of others at [CortexLab](https://www.ucl.ac.uk/cortexlab). diff --git a/doc/Rigbox UML.pdf b/docs/Rigbox UML.pdf similarity index 100% rename from doc/Rigbox UML.pdf rename to docs/Rigbox UML.pdf diff --git a/doc/setup/Alyx_config.m b/docs/setup/Alyx_config.m similarity index 100% rename from doc/setup/Alyx_config.m rename to docs/setup/Alyx_config.m diff --git a/doc/setup/hardware_config.m b/docs/setup/hardware_config.m similarity index 100% rename from doc/setup/hardware_config.m rename to docs/setup/hardware_config.m diff --git a/doc/setup/websocket_config.m b/docs/setup/websocket_config.m similarity index 100% rename from doc/setup/websocket_config.m rename to docs/setup/websocket_config.m diff --git a/doc/using_dat_package.m b/docs/using_dat_package.m similarity index 100% rename from doc/using_dat_package.m rename to docs/using_dat_package.m diff --git a/doc/using_parameters.m b/docs/using_parameters.m similarity index 100% rename from doc/using_parameters.m rename to docs/using_parameters.m From d8d691e199206c51144f9ea026e25d7f6d9ed257 Mon Sep 17 00:00:00 2001 From: Jai Bhagat <j.bhagat@ucl.ac.uk> Date: Mon, 6 May 2019 17:59:10 +0100 Subject: [PATCH 359/393] add 'contributing.md' link and author emails to 'readme' --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 82329293..b1f1337f 100644 --- a/README.md +++ b/README.md @@ -146,8 +146,8 @@ Additional information on the [alyx-matlab](https://github.com/cortex-lab/alyx-m ## Contributing -Please read [CONTRIBUTING.md]() for details on our code of conduct, and the process of contributing code to this repository via pull requests. +Please read [CONTRIBUTING.md](https://github.com/cortex-lab/Rigbox/blob/dev/CONTRIBUTING.md) for details on how to contribute code to this repository and our code of conduct. ## Authors -The majority of the Rigbox code was written by [Chris Burgess](https://github.com/dendritic/) in 2013. It is now maintained and developed by Miles Wells, Jai Bhagat and a number of others at [CortexLab](https://www.ucl.ac.uk/cortexlab). +The majority of the Rigbox code was written by [Chris Burgess](https://github.com/dendritic/) in 2013. It is now maintained and developed by Miles Wells (miles.wells@ucl.ac.uk), Jai Bhagat (j.bhagat@ucl.ac.uk) and a number of others at [CortexLab](https://www.ucl.ac.uk/cortexlab). From 89f1854cd951d00483f49673869da86de3aed8ee Mon Sep 17 00:00:00 2001 From: Jai <j.bhagat@ucl.ac.uk> Date: Tue, 7 May 2019 14:02:29 +0100 Subject: [PATCH 360/393] updated submodule signals (merged 'tutorialsForPaper' into 'dev') --- signals | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/signals b/signals index 259fbaf3..74a5fe55 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit 259fbaf34316bc4e77a1089e8b972c60d5dab3a1 +Subproject commit 74a5fe554e1c13d7e1acbd46f5fcf812436919db From c7dd26c182421b914eebd5f765a38118af692240 Mon Sep 17 00:00:00 2001 From: Jai <j.bhagat@ucl.ac.uk> Date: Tue, 7 May 2019 14:52:52 +0100 Subject: [PATCH 361/393] Revert "Merge pull request #149 from cortex-lab/expStop" This reverts commit 3c48d5703a736991bfb4a307db5f851e3196b4d3, reversing changes made to d8d691e199206c51144f9ea026e25d7f6d9ed257. --- +exp/SignalsExp.m | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/+exp/SignalsExp.m b/+exp/SignalsExp.m index 45e520b7..d2fb2631 100644 --- a/+exp/SignalsExp.m +++ b/+exp/SignalsExp.m @@ -151,6 +151,7 @@ obj.Time = net.origin('t'); obj.Events.expStart = net.origin('expStart'); obj.Events.newTrial = net.origin('newTrial'); + obj.Events.expStop = net.origin('expStop'); obj.Inputs.wheel = net.origin('wheel'); obj.Inputs.wheelMM = obj.Inputs.wheel.map(@... (x)obj.Wheel.MillimetresFactor*(x-obj.Wheel.ZeroOffset)).skipRepeats(); @@ -170,6 +171,7 @@ globalPars, allCondPars, advanceTrial); obj.Events.trialNum = obj.Events.newTrial.scan(@plus, 0); % track trial number lastTrialOver = then(~hasNext, true); +% obj.Events.expStop = then(~hasNext, true); % run experiment definition if ischar(paramStruct.defFunction) expDefFun = fileFunction(paramStruct.defFunction); @@ -181,14 +183,14 @@ fprintf('takes %i args\n', nargout(expDefFun)); expDefFun(obj.Time, obj.Events, obj.Params, obj.Visual, obj.Inputs,... obj.Outputs, obj.Audio); - obj.Events.expStop = iff(isfield(obj.Events, 'expStop'),... - @()merge(obj.Events.expStop, lastTrialOver), lastTrialOver); % listeners obj.Listeners = [ obj.Events.expStart.map(true).into(advanceTrial) %expStart signals advance obj.Events.endTrial.into(advanceTrial) %endTrial signals advance advanceTrial.map(true).keepWhen(hasNext).into(obj.Events.newTrial) %newTrial if more - obj.Events.expStop.onValue(@(~)quit(obj))]; + lastTrialOver.into(obj.Events.expStop) %newTrial if more + onValue(obj.Events.expStop, @(~)quit(obj));]; +% obj.Events.trialNum.onValue(fun.partial(@fprintf, 'trial %i started\n'))]; % initialise the parameter signals globalPars.post(rmfield(globalStruct, 'defFunction')); allCondPars.post(allCondStruct); @@ -405,7 +407,7 @@ function log(obj, field, value) function quit(obj, immediately) if isempty(obj.Events.expStop.Node.CurrValue) - obj.Events.expStop = net.origin('expStop').post(true); + obj.Events.expStop.post(true); end %stop delay timers. todo: need to use a less global tag tmrs = timerfind('Tag', 'sig.delay'); From bc3f9c0b104be58bca76501314f9aa1c5d36dd68 Mon Sep 17 00:00:00 2001 From: Jai Bhagat <j.bhagat@ucl.ac.uk> Date: Wed, 8 May 2019 14:48:47 +0100 Subject: [PATCH 362/393] updated 'expStop' issue after testing --- +exp/SignalsExp.m | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/+exp/SignalsExp.m b/+exp/SignalsExp.m index d2fb2631..347be45d 100644 --- a/+exp/SignalsExp.m +++ b/+exp/SignalsExp.m @@ -151,7 +151,6 @@ obj.Time = net.origin('t'); obj.Events.expStart = net.origin('expStart'); obj.Events.newTrial = net.origin('newTrial'); - obj.Events.expStop = net.origin('expStop'); obj.Inputs.wheel = net.origin('wheel'); obj.Inputs.wheelMM = obj.Inputs.wheel.map(@... (x)obj.Wheel.MillimetresFactor*(x-obj.Wheel.ZeroOffset)).skipRepeats(); @@ -171,7 +170,6 @@ globalPars, allCondPars, advanceTrial); obj.Events.trialNum = obj.Events.newTrial.scan(@plus, 0); % track trial number lastTrialOver = then(~hasNext, true); -% obj.Events.expStop = then(~hasNext, true); % run experiment definition if ischar(paramStruct.defFunction) expDefFun = fileFunction(paramStruct.defFunction); @@ -182,15 +180,21 @@ end fprintf('takes %i args\n', nargout(expDefFun)); expDefFun(obj.Time, obj.Events, obj.Params, obj.Visual, obj.Inputs,... - obj.Outputs, obj.Audio); + obj.Outputs, obj.Audio); + % if user defined 'expStop' in their exp def, allow 'expStop' to also + % take value at 'lastTrialOver', else just set to 'lastTrialOver' + if isfield(obj.Events, 'expStop') + obj.Events.expStop = merge(obj.Events.expStop, lastTrialOver); + else + obj.Events.expStop = lastTrialOver; + end % listeners obj.Listeners = [ obj.Events.expStart.map(true).into(advanceTrial) %expStart signals advance obj.Events.endTrial.into(advanceTrial) %endTrial signals advance advanceTrial.map(true).keepWhen(hasNext).into(obj.Events.newTrial) %newTrial if more lastTrialOver.into(obj.Events.expStop) %newTrial if more - onValue(obj.Events.expStop, @(~)quit(obj));]; -% obj.Events.trialNum.onValue(fun.partial(@fprintf, 'trial %i started\n'))]; + obj.Events.expStop.onValue(@(~)quit(obj))]; % initialise the parameter signals globalPars.post(rmfield(globalStruct, 'defFunction')); allCondPars.post(allCondStruct); @@ -406,7 +410,10 @@ function log(obj, field, value) end function quit(obj, immediately) + % if the experiment was stopped via 'mc' or 'q' key if isempty(obj.Events.expStop.Node.CurrValue) + % re-assign 'expStop' as an origin signal and post to it + obj.Events.expStop = obj.Net.origin('expStop'); obj.Events.expStop.post(true); end %stop delay timers. todo: need to use a less global tag From e5683cdb5e13342e621bf4323a7b3f01400bc01d Mon Sep 17 00:00:00 2001 From: Jai Bhagat <j.bhagat@ucl.ac.uk> Date: Thu, 9 May 2019 00:20:15 +0100 Subject: [PATCH 363/393] Update README.md --- README.md | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index b1f1337f..b931f26d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ---------- # Rigbox -Rigbox is a (mostly) object-oriented MATLAB software package for designing and controlling neurophysiological behavioural experiments (principally, the [steering wheel setup](https://www.ucl.ac.uk/cortexlab/tools/wheel) which [we](https://www.ucl.ac.uk/cortexlab) developed to probe mouse behaviour). Rigbox requires two machines, one for stimulus presentation ('the stimulus server') and another for controlling and monitoring the experiment ('mc'). +Rigbox is a a high-performance, open-source software toolbox for managing behavioral neuroscience experiments. Initially developed to probe mouse behavior for the [Steering Wheel Setup](https://www.ucl.ac.uk/cortexlab/tools/wheel), Rigbox is under active, test-driven development to encompass a variety of experimental paradigms across behavioral neuroscience. Rigbox simplifies hardware/software interfacing, synchronizes data streams from multiple sources, manages experimental data via communication with a remote database, and creates an environment where experimental parameters can be easily monitored and manipulated. Rigbox’s object-oriented paradigm facilitates a modular approach to designing experiments. Rigbox requires two machines, one for stimulus presentation ('the stimulus computer' or 'sc') and another for controlling and monitoring the experiment ('the master computer' or 'mc'). ## Getting Started @@ -11,7 +11,7 @@ The following is a brief description of how to install Rigbox on your experiment Rigbox has the following software dependencies: * Windows Operating System (7 or later, 64-bit) -* MATLAB (2016b or later) +* MATLAB (2017b or later) * The following MathWorks MATLAB toolboxes: * Data Acquisition Toolbox * Signal Processing Toolbox @@ -22,13 +22,10 @@ Rigbox has the following software dependencies: * [Psychophsics Toolbox](http://psychtoolbox.org/download.html) (v3 or later) * [NI-DAQmx support package](https://uk.mathworks.com/hardware-support/nidaqmx.html) -(* *Note*: You can download all required MathWorks MATLAB toolboxes directly within MATLAB via the "Add-Ons" button in the top Toolstrip with the "Home" tab selected.) -![MATLAB Home Toolstrip](http://i67.tinypic.com/k0zue.png) - -Afterwards, you can use the MATLAB "ver" command to bring up the list of installed MathWorks toolboxes. +All required MathWorks MATLAB toolboxes can be downloaded and installed directly within MATLAB via the "Add-Ons" button in the "Home" top toolstrip. Additionally, Rigbox works with a number of extra submodules (included): -* [signals](https://github.com/cortex-lab/signals) (for running bespoke experiment designs) +* [signals](https://github.com/cortex-lab/signals) (for designing bespoke experiments) * [alyx-matlab](https://github.com/cortex-lab/alyx-matlab) (for registering data to, and retrieving from, an Alyx database) * [npy-matlab](https://github.com/kwikteam/npy-matlab) (for saving data in binary NPY format) * [wheelAnalysis](https://github.com/cortex-lab/wheelAnalysis) (for analyzing data from the steering wheel task) From 781d3668845e5b8e316fb75d5823689411224b8e Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 12 Apr 2019 13:07:41 +0300 Subject: [PATCH 364/393] merged 'documentation' into dev --- +dat/delParamProfile.m | 2 +- +dat/expLogRequest.m | 2 +- +dat/reposPath.m | 2 +- +dat/saveParamProfile.m | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/+dat/delParamProfile.m b/+dat/delParamProfile.m index ee46eeb6..324f9b0c 100644 --- a/+dat/delParamProfile.m +++ b/+dat/delParamProfile.m @@ -15,7 +15,7 @@ function delParamProfile(expType, profileName) %remove the params with the field named by profile profiles = rmfield(profiles, profileName); %wrap in a struct for saving -set.(expType) = profiles; %#ok<STRNU> +set.(expType) = profiles; %save the updated set of profiles to each repos %where files exist already, append diff --git a/+dat/expLogRequest.m b/+dat/expLogRequest.m index 02d7308e..45efbc15 100644 --- a/+dat/expLogRequest.m +++ b/+dat/expLogRequest.m @@ -6,7 +6,7 @@ if nargin < 2 args = struct; -elseif nargin == 2 && isstruct(varargin{1}); +elseif nargin == 2 && isstruct(varargin{1}) else args = varargin2struct(varargin{:}); end diff --git a/+dat/reposPath.m b/+dat/reposPath.m index e2991ebc..ade5d65a 100644 --- a/+dat/reposPath.m +++ b/+dat/reposPath.m @@ -66,7 +66,7 @@ case {'local' 'l'} p = paths.localRepository; otherwise - error('"%s" is not a recognised repository location.', location{1}); + error('"%s" is not a recognised repository location.', location); end end \ No newline at end of file diff --git a/+dat/saveParamProfile.m b/+dat/saveParamProfile.m index e76ffdf9..8a08f3bb 100644 --- a/+dat/saveParamProfile.m +++ b/+dat/saveParamProfile.m @@ -17,7 +17,7 @@ function saveParamProfile(expType, profileName, params) profiles.(profileName) = params; %wrap in a struct for saving set = struct; -set.(expType) = profiles; %#ok<STRNU> +set.(expType) = profiles; %save the updated set of profiles to each repos %where files exist already, append From 7cc8fd44fce7de30a95ba98b01081fd64489232e Mon Sep 17 00:00:00 2001 From: jaib1 <j.bhagat@ucl.ac.uk> Date: Fri, 10 May 2019 14:02:52 +0100 Subject: [PATCH 365/393] updated signals and alyx-matlab submodules (just documentation) --- alyx-matlab | 2 +- signals | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/alyx-matlab b/alyx-matlab index 085d702d..86a9fa73 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit 085d702d4f1498b319265e440c5dbce9cca5767f +Subproject commit 86a9fa7342bcedab4144fdbfe93da1bbc356fb64 diff --git a/signals b/signals index 74a5fe55..0b1c9930 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit 74a5fe554e1c13d7e1acbd46f5fcf812436919db +Subproject commit 0b1c9930fdc76c12e17eb654fc2b560efe1801a6 From a0478f14a10ab1314c109241ce356c7b85b67621 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Mon, 13 May 2019 17:10:00 +0300 Subject: [PATCH 366/393] Update to submodule --- alyx-matlab | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alyx-matlab b/alyx-matlab index 38d33275..6496bb3e 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit 38d332754402aad2c608890bb50347d0ad969e0e +Subproject commit 6496bb3e71814ba55bf0a7d5cee5a6aa41b393ef From a1947e374761a0868e0908593328f36237db8fc3 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Mon, 13 May 2019 17:11:55 +0300 Subject: [PATCH 367/393] Update to submodule --- alyx-matlab | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alyx-matlab b/alyx-matlab index 86a9fa73..62f4e8cf 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit 86a9fa7342bcedab4144fdbfe93da1bbc356fb64 +Subproject commit 62f4e8cf6ceb141b1f640b0891c2fbaee35d9d3e From b6fd9a22e0f325f7fa1df359f704ba92ffc281c7 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 14 May 2019 12:08:17 +0300 Subject: [PATCH 368/393] Weight changed for test; may be an Alyx bug --- tests/AlyxPanel_test.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/AlyxPanel_test.m b/tests/AlyxPanel_test.m index 8ff3dea3..1df3a8a5 100644 --- a/tests/AlyxPanel_test.m +++ b/tests/AlyxPanel_test.m @@ -32,7 +32,7 @@ methods (TestClassSetup) function killFigures(testCase) testCase.FigureVisibleDefault = get(0,'DefaultFigureVisible'); -% set(0,'DefaultFigureVisible','off'); + set(0,'DefaultFigureVisible','off'); end function loadData(testCase) @@ -264,7 +264,7 @@ function test_recordWeight(testCase) 'Failed to update weight label color') % Post weight < 80 - weight = 16 + rand; + weight = 25 + rand; testCase.Panel.recordWeight(weight) expected = sprintf('Weight today: %.2f (< 80%%)', weight); testCase.verifyTrue(startsWith(strip(weight_text.String(2,:)), expected),... @@ -278,7 +278,7 @@ function test_recordWeight(testCase) testCase.verifyTrue(endsWith(logPanel.String{end}, expected)) % Test manual weight dialog - weight = 25 + rand; + weight = 30 + rand; button = findobj(testCase.Parent, 'String', 'Manual weighing'); testCase.assertTrue(~isempty(button), 'Unable to find button object') testCase.Mock.Dialogs('Manual weight logging') = num2str(weight); @@ -299,7 +299,7 @@ function test_recordWeight(testCase) 'Failed to update weight label value') % Test weight post when logged out - testCase.Panel.login + testCase.Panel.login % log out testCase.Panel.recordWeight() expected = 'Warning: Weight not posted to Alyx; will be posted upon login.'; From 6b727d30d55cfc1c489afc96bc6001eb0df85881 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 3 May 2019 13:29:32 +0300 Subject: [PATCH 369/393] Merged 'paramUI' into 'dev' after adding 'sortByColumn' and 'randomiseConditions' functionality Added sort by column functionality Fix for set values function Added tests for sortByColumn and randomiseConditions --- +eui/ConditionPanel.m | 41 ++++++++++++++++++++----- tests/ParamEditor_test.m | 66 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 8 deletions(-) diff --git a/+eui/ConditionPanel.m b/+eui/ConditionPanel.m index cbbac3c1..ff9ab9b8 100644 --- a/+eui/ConditionPanel.m +++ b/+eui/ConditionPanel.m @@ -2,7 +2,6 @@ %CONDITIONPANEL Deals with formatting trial conditions UI table % Designed to be an element of the EUI.PARAMEDITOR class that manages % the UI elements associated with all Conditional parameters. - % TODO Add sort by column % TODO Add set condition idx properties @@ -53,8 +52,8 @@ obj.ContextMenus(2) = uimenu(c, 'Label', 'Randomize conditions', ... 'MenuSelectedFcn', fcn, 'Checked', 'on', 'Tag', 'randomize button'); obj.ContextMenus(3) = uimenu(c, 'Label', 'Sort by selected column', ... - 'MenuSelectedFcn', @(~,~)disp('feature not yet implemented'), ... - 'Tag', 'sort by', 'Enable', 'off'); % TODO Implement sort by column + 'MenuSelectedFcn', @(~,~)obj.sortByColumn, ... + 'Tag', 'sort by', 'Enable', 'off'); % Create condition table p = uix.Panel('Parent', obj.UIPanel, 'BorderType', 'none'); obj.ConditionTable = uitable('Parent', p,... @@ -218,12 +217,13 @@ function setSelectedValues(obj) % false % Sets all selected rows to false % % See also SETNEWVALS, ONEDIT + PE = obj.ParamEditor; cols = obj.SelectedCells(:,2); % selected columns uCol = unique(obj.SelectedCells(:,2)); rows = obj.SelectedCells(:,1); % selected rows % get current values of selected cells currVals = arrayfun(@(u)obj.ConditionTable.Data(rows(cols==u),u), uCol, 'UniformOutput', 0); - names = obj.ConditionTable.ColumnName(uCol); % selected column names + names = PE.Parameters.TrialSpecificNames(uCol); % selected column names promt = cellfun(@(a,b) [a ' (' num2str(sum(cols==b)) ')'],... names, num2cell(uCol), 'UniformOutput', 0); % names of columns & num selected rows defaultans = cellfun(@(c) c(1), currVals); @@ -260,19 +260,44 @@ function setSelectedValues(obj) elseif length(newVals)<length(currVals) % too few new values % populate as many cells as possible newVals = [newVals ... - cellfun(@(a)obj.ParamEditor.controlValue2Param(2,a),... + cellfun(@(a)PE.controlValue2Param(2,a),... currVals(length(newVals)+1:end),'UniformOutput',0)]; end - ic = strcmp(obj.ConditionTable.ColumnName, paramName); % find edited param names + ic = strcmp(PE.Parameters.TrialSpecificNames, paramName); % find edited param names % update param struct - obj.ParamEditor.Parameters.Struct.(paramName)(:,rows(cols==find(ic))) = cell2mat(newVals); + PE.Parameters.Struct.(paramName)(:,rows(cols==find(ic))) = cell2mat(newVals); % update condtion table with strings obj.ConditionTable.Data(rows(cols==find(ic)),ic)... - = cellfun(@(a)ui.ParamEditor.paramValue2Control(a), newVals', 'UniformOutput', 0); + = cellfun(@(a)PE.paramValue2Control(a), newVals', 'UniformOutput', 0); end notify(obj.ParamEditor, 'Changed'); end + function sortByColumn(obj) + % SORTBYCOLUMN Sort all conditions by selected column + % If the selected column is already sorted in ascended order then + % the conditions are ordered in descending order instead. + % TODO Sort by multiple columns + % @body currently all conditions are sorted by first selected column + if isempty(obj.SelectedCells) + disp('nothing selected') + return + end + PE = obj.ParamEditor; + % Get selected column name and retrieve data + cols = unique(obj.SelectedCells(:,2)); + names = PE.Parameters.TrialSpecificNames(cols); + toSort = PE.Parameters.Struct.(names{1}); + direction = iff(issorted(toSort','rows'), 'descend', 'ascend'); + [~, I] = sortrows(toSort', direction); + % Update parameters with new permutation + for p = PE.Parameters.TrialSpecificNames' + data = PE.Parameters.Struct.(p{:}); + PE.Parameters.Struct.(p{:}) = data(:,I); + end + obj.fillConditionTable % Redraw table + end + function fillConditionTable(obj) % FILLCONDITIONTABLE Build the condition table % Populates the UI Table with trial specific parameters, where each diff --git a/tests/ParamEditor_test.m b/tests/ParamEditor_test.m index ff2263cf..25f94598 100644 --- a/tests/ParamEditor_test.m +++ b/tests/ParamEditor_test.m @@ -266,6 +266,72 @@ function test_setValues(testCase) testCase.assertTrue(false, 'Test not implemented') end + function test_sortByColumn(testCase) + testCase.assertTrue(~testCase.Changed, 'Changed flag incorrect') + PE = testCase.ParamEditor; + callback_fn = pick(findobj(testCase.Figure,... + 'Tag', 'sort by'), 'MenuSelectedFcn'); + + % Select a single row parameter column to sort + event.Indices = [1 find(strcmp(testCase.Table.ColumnName, 'Num repeats'),1)]; + selection_fn = testCase.Table.CellSelectionCallback; + selection_fn([],event) + + % Sort ascending + callback_fn() + expected = [zeros(1,8) ones(1,4)*250]; + testCase.verifyEqual(PE.Parameters.Struct.numRepeats, expected, ... + 'Failed to sort ascending') + + % Verify params listeners not notified. NB: Behaviour may change in + % future as underlying params struct is changed + testCase.verifyTrue(~testCase.Changed, 'Changed flag incorrect') + + % Sort descending + callback_fn() + testCase.verifyEqual(PE.Parameters.Struct.numRepeats, fliplr(expected), ... + 'Failed to sort descending') + + % Repeat test for multi-row parameter + event.Indices = [1 find(strcmp(testCase.Table.ColumnName, ... + 'Feedback for response'),1)]; + selection_fn([],event) + + % Sort ascending + callback_fn() + expected = [-ones(1,6) ones(1,6); ones(1,6) -ones(1,6); -ones(1,12)]; + expected(3,[6 end]) = 1; + testCase.verifyEqual(PE.Parameters.Struct.feedbackForResponse, expected, ... + 'Failed to sort multi-row parameter') + end + + function test_randomiseConditions(testCase) + testCase.assertTrue(~testCase.Changed, 'Changed flag incorrect') + PE = testCase.ParamEditor; + button = findobj(testCase.Figure, 'Tag', 'randomize button'); + testCase.assertEqual(button.Checked, 'on', 'Conditions not randomized by default') + + % Deselect randomize + button.MenuSelectedFcn(button); + testCase.verifyEqual(testCase.Table.RowName, 'numbered', ... + 'Condition indicies not shown in table') + testCase.assertTrue(ismember('randomiseConditions',PE.Parameters.GlobalNames),... + 'randomiseConditions parameter not present') + testCase.verifyTrue(~PE.Parameters.Struct.randomiseConditions, ... + 'randomiseConditions parameter not set correctly') + testCase.verifyEqual(button.Checked, 'off', 'Context menu failed to update') + % Verify that randomiseConditions parameter is hidden from table + testCase.verifyEmpty(findobj(testCase.Figure, 'String', 'Randomise conditions'),... + 'Unexpected parameter label') + + % Reselect randomize + button.MenuSelectedFcn(button); + testCase.verifyEmpty(testCase.Table.RowName, 'Condition indicies shown in table') + testCase.verifyTrue(PE.Parameters.Struct.randomiseConditions, ... + 'randomiseConditions parameter not set correctly') + testCase.verifyEqual(button.Checked, 'on', 'Context menu failed to update') + end + function test_globaliseParam(testCase) PE = testCase.ParamEditor; testCase.assertTrue(~testCase.Changed, 'Changed flag incorrect') From 19018b4133c33be2947301dd3d0122fcc6a38e24 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 21 May 2019 16:55:42 +0300 Subject: [PATCH 370/393] Updated submodules --- alyx-matlab | 2 +- signals | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/alyx-matlab b/alyx-matlab index 8b4f4f96..2c66768e 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit 8b4f4f96f7cfe4094cb4b2b8690aeb454f22c7b7 +Subproject commit 2c66768e17cc94840fb0e37ad74953f46769a473 diff --git a/signals b/signals index 0c9a2bea..0152a842 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit 0c9a2bea861352758c77a03978a874cd286835c9 +Subproject commit 0152a8424340f45b29b0fd8388c3bedc601d641b From d6fc06557b131fce398121ed0ddcb845118509b1 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 22 May 2019 13:21:33 +0300 Subject: [PATCH 371/393] HOTFIX: Revert mistaken changes --- +eui/FieldPanel.m | 3 ++- +eui/MControl.m | 8 ++++---- +eui/ParamEditor.m | 3 ++- +exp/Parameters.m | 10 +++++----- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/+eui/FieldPanel.m b/+eui/FieldPanel.m index fc930a4f..a409af1d 100644 --- a/+eui/FieldPanel.m +++ b/+eui/FieldPanel.m @@ -222,4 +222,5 @@ function onResize(obj, ~, ~) end end -end \ No newline at end of file +end + diff --git a/+eui/MControl.m b/+eui/MControl.m index 0810a594..ff44471b 100644 --- a/+eui/MControl.m +++ b/+eui/MControl.m @@ -247,10 +247,10 @@ function saveParamProfile(obj) % Called by 'Save...' button press, save a new pa end function loadParamProfile(obj, profile) + set(obj.ParamProfileLabel, 'String', 'loading...', 'ForegroundColor', [1 0 0]); % Red 'Loading...' while new set loads if ~isempty(obj.ParamEditor) - %delete existing parameters control - delete(obj.ParamEditor); - set(obj.ParamProfileLabel, 'String', 'loading...', 'ForegroundColor', [1 0 0]); % Red 'Loading...' while new set loads + % Clear existing parameters control + clear(obj.ParamEditor) end factory = obj.NewExpFactory; % Find which 'world' we are in @@ -810,4 +810,4 @@ function buildUI(obj, parent) % Parent here is the MC window (figure) end end -end +end \ No newline at end of file diff --git a/+eui/ParamEditor.m b/+eui/ParamEditor.m index c16a45aa..b7c7d6d1 100644 --- a/+eui/ParamEditor.m +++ b/+eui/ParamEditor.m @@ -409,4 +409,5 @@ function onResize(obj) end end -end \ No newline at end of file +end + diff --git a/+exp/Parameters.m b/+exp/Parameters.m index c0a5ed82..80579cd2 100644 --- a/+exp/Parameters.m +++ b/+exp/Parameters.m @@ -71,9 +71,9 @@ function set(obj, name, value, description, units) n = numel(obj.pNames); obj.IsTrialSpecific = struct; isTrialSpecificDefault = @(n) ... - strcmp(n, 'numRepeats') ||... % numRepeats always trail specific - (ischar(obj.pStruct.(n)) && size(obj.pStruct.(n), 1) > 1) ||... % Number of rows > 1 for chars - (~ischar(obj.pStruct.(n)) && size(obj.pStruct.(n), 2) > 1); % Number of columns > 1 for all others + ~strcmp(n, 'randomiseConditions') &&... % randomiseConditions always global + ((ischar(obj.pStruct.(n)) && size(obj.pStruct.(n), 1) > 1) ||... % Number of rows > 1 for chars + (~ischar(obj.pStruct.(n)) && size(obj.pStruct.(n), 2) > 1)); % Number of columns > 1 for all others for i = 1:n name = obj.pNames{i}; obj.IsTrialSpecific.(name) = isTrialSpecificDefault(name); @@ -195,8 +195,8 @@ function makeGlobal(obj, name, newValue) 'UniformOutput', false); % concatenate trial parameter trialParamValues = cat(1, trialParamValues{:}); - if isempty(trialParamValues) - trialParamValues = {1}; + if isempty(trialParamValues) % Removed MW 30.01.19 + trialParamValues = {}; end trialParams = cell2struct(trialParamValues, trialParamNames, 1)'; globalParams = cell2struct(globalParamValues, globalParamNames, 1); From ad95aad6fe39e923416df57120bb014b9f632ca4 Mon Sep 17 00:00:00 2001 From: jaib1 <j.bhagat@ucl.ac.uk> Date: Fri, 24 May 2019 13:14:18 +0100 Subject: [PATCH 372/393] added readthedocs files --- docs/readthedocsFiles/Makefile | 20 + .../_build/doctrees/environment.pickle | Bin 0 -> 1569317 bytes .../_build/doctrees/root.doctree | Bin 0 -> 4764 bytes docs/readthedocsFiles/_build/html/.buildinfo | 4 + docs/readthedocsFiles/_build/html/.nojekyll | 0 .../_build/html/_sources/root.rst.txt | 20 + .../_build/html/_static/ajax-loader.gif | Bin 0 -> 673 bytes .../_build/html/_static/alabaster.css | 688 ++ .../_build/html/_static/basic.css | 665 + .../_build/html/_static/comment-bright.png | Bin 0 -> 756 bytes .../_build/html/_static/comment-close.png | Bin 0 -> 829 bytes .../_build/html/_static/comment.png | Bin 0 -> 641 bytes .../_build/html/_static/custom.css | 1 + .../_build/html/_static/doctools.js | 313 + .../html/_static/documentation_options.js | 9 + .../_build/html/_static/down-pressed.png | Bin 0 -> 222 bytes .../_build/html/_static/down.png | Bin 0 -> 202 bytes .../_build/html/_static/file.png | Bin 0 -> 286 bytes .../_build/html/_static/jquery-3.2.1.js | 10253 ++++++++++++++++ .../_build/html/_static/jquery.js | 4 + .../_build/html/_static/minus.png | Bin 0 -> 90 bytes .../_build/html/_static/plus.png | Bin 0 -> 90 bytes .../_build/html/_static/pygments.css | 69 + .../_build/html/_static/searchtools.js | 761 ++ .../_build/html/_static/underscore-1.3.1.js | 999 ++ .../_build/html/_static/underscore.js | 31 + .../_build/html/_static/up-pressed.png | Bin 0 -> 214 bytes .../_build/html/_static/up.png | Bin 0 -> 203 bytes .../_build/html/_static/websupport.js | 808 ++ .../_build/html/genindex.html | 96 + docs/readthedocsFiles/_build/html/objects.inv | Bin 0 -> 248 bytes docs/readthedocsFiles/_build/html/root.html | 105 + docs/readthedocsFiles/_build/html/search.html | 107 + .../_build/html/searchindex.js | 1 + docs/readthedocsFiles/conf.py | 197 + docs/readthedocsFiles/make.bat | 36 + docs/readthedocsFiles/root.rst | 20 + 37 files changed, 15207 insertions(+) create mode 100644 docs/readthedocsFiles/Makefile create mode 100644 docs/readthedocsFiles/_build/doctrees/environment.pickle create mode 100644 docs/readthedocsFiles/_build/doctrees/root.doctree create mode 100644 docs/readthedocsFiles/_build/html/.buildinfo create mode 100644 docs/readthedocsFiles/_build/html/.nojekyll create mode 100644 docs/readthedocsFiles/_build/html/_sources/root.rst.txt create mode 100644 docs/readthedocsFiles/_build/html/_static/ajax-loader.gif create mode 100644 docs/readthedocsFiles/_build/html/_static/alabaster.css create mode 100644 docs/readthedocsFiles/_build/html/_static/basic.css create mode 100644 docs/readthedocsFiles/_build/html/_static/comment-bright.png create mode 100644 docs/readthedocsFiles/_build/html/_static/comment-close.png create mode 100644 docs/readthedocsFiles/_build/html/_static/comment.png create mode 100644 docs/readthedocsFiles/_build/html/_static/custom.css create mode 100644 docs/readthedocsFiles/_build/html/_static/doctools.js create mode 100644 docs/readthedocsFiles/_build/html/_static/documentation_options.js create mode 100644 docs/readthedocsFiles/_build/html/_static/down-pressed.png create mode 100644 docs/readthedocsFiles/_build/html/_static/down.png create mode 100644 docs/readthedocsFiles/_build/html/_static/file.png create mode 100644 docs/readthedocsFiles/_build/html/_static/jquery-3.2.1.js create mode 100644 docs/readthedocsFiles/_build/html/_static/jquery.js create mode 100644 docs/readthedocsFiles/_build/html/_static/minus.png create mode 100644 docs/readthedocsFiles/_build/html/_static/plus.png create mode 100644 docs/readthedocsFiles/_build/html/_static/pygments.css create mode 100644 docs/readthedocsFiles/_build/html/_static/searchtools.js create mode 100644 docs/readthedocsFiles/_build/html/_static/underscore-1.3.1.js create mode 100644 docs/readthedocsFiles/_build/html/_static/underscore.js create mode 100644 docs/readthedocsFiles/_build/html/_static/up-pressed.png create mode 100644 docs/readthedocsFiles/_build/html/_static/up.png create mode 100644 docs/readthedocsFiles/_build/html/_static/websupport.js create mode 100644 docs/readthedocsFiles/_build/html/genindex.html create mode 100644 docs/readthedocsFiles/_build/html/objects.inv create mode 100644 docs/readthedocsFiles/_build/html/root.html create mode 100644 docs/readthedocsFiles/_build/html/search.html create mode 100644 docs/readthedocsFiles/_build/html/searchindex.js create mode 100644 docs/readthedocsFiles/conf.py create mode 100644 docs/readthedocsFiles/make.bat create mode 100644 docs/readthedocsFiles/root.rst diff --git a/docs/readthedocsFiles/Makefile b/docs/readthedocsFiles/Makefile new file mode 100644 index 00000000..008e178e --- /dev/null +++ b/docs/readthedocsFiles/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +SPHINXPROJ = Rigbox +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file diff --git a/docs/readthedocsFiles/_build/doctrees/environment.pickle b/docs/readthedocsFiles/_build/doctrees/environment.pickle new file mode 100644 index 0000000000000000000000000000000000000000..5b4e7604e73011a2f052bc313234e53881c11422 GIT binary patch literal 1569317 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�~r$?(G2`x0+rAwOdVg}r< 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�{;M=wX&ceF~yOcLx9 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	To}e95NwKnPnd#%2Ja+a)U)>$W}ZPGrGlv<4O- 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pKc^ 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�W=*_} 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 literal 0 HcmV?d00001 diff --git a/docs/readthedocsFiles/_build/doctrees/root.doctree b/docs/readthedocsFiles/_build/doctrees/root.doctree new file mode 100644 index 0000000000000000000000000000000000000000..d8006b2050acade832c6ee45566b9565ce97f730 GIT binary patch literal 4764 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 MnZ8t|X8nWz0Er+T?f?J) literal 0 HcmV?d00001 diff --git a/docs/readthedocsFiles/_build/html/.buildinfo b/docs/readthedocsFiles/_build/html/.buildinfo new file mode 100644 index 00000000..eae1f52b --- /dev/null +++ b/docs/readthedocsFiles/_build/html/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: 20c7f121df0d240593df77635760ec12 +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/readthedocsFiles/_build/html/.nojekyll b/docs/readthedocsFiles/_build/html/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/docs/readthedocsFiles/_build/html/_sources/root.rst.txt b/docs/readthedocsFiles/_build/html/_sources/root.rst.txt new file mode 100644 index 00000000..22c68b15 --- /dev/null +++ b/docs/readthedocsFiles/_build/html/_sources/root.rst.txt @@ -0,0 +1,20 @@ +.. Rigbox documentation master file, created by + sphinx-quickstart on Fri May 24 13:07:11 2019. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to Rigbox's documentation! +================================== + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/readthedocsFiles/_build/html/_static/ajax-loader.gif b/docs/readthedocsFiles/_build/html/_static/ajax-loader.gif new file mode 100644 index 0000000000000000000000000000000000000000..61faf8cab23993bd3e1560bff0668bd628642330 GIT binary patch literal 673 zcmZ?wbhEHb6krfw_{6~Q|Nno%(3)e{?)x>&1u}A`t?OF7Z|1gRivOgXi&7IyQd1Pl zGfOfQ60;I3a`F>X^fL3(@);C=vM_KlFfb_o=k{|A33hf2a5d61U}gjg=>Rd%XaNQW zW@C<Bcm5fi^2`=a=CI<BoWt%nBaPE_qv4@lA~O$e(@QvVsPKYrw1nl|W$cy`JnUZC z&pm)ff{kWGHpc{Hj$e<Wf^-Yd?hVhnTne26LlO)n6%u@0qor2V$ZRdW|29#Ay+Pr+ z#G^K6$xW&%T0&5Rn2-%J<Je`StbNMy#Dp_b!t~i%lV$k6Ncw&BbV{7Dx<KXw*O|?G zWsa@TW{P|({)e&oFu&2t6sh_9S)fKSBO3+uTav2wDWkTDZ{~!>w{|b%Y*pl8F?4B9 zlo4Fz*0kZGJabY|>}Okf0}CCg{u4`zEPY^pV?j2@h+|igy0+Kz6p;@SpM4s6)XEMg z#3Y4GX>Hjlml5ftdH$4x0JGdn8~MX(U~_^d!Hi)=HU{V%g+mi8#UGbE-*ao8f#h+S z2a0-5+vc7MU$e-NhmBjLIC1v|)9+Im8x1yacJ7{^tLX(ZhYi^rpmXm0`@ku9b53aN zEXH@Y3JaztblgpxbJt{AtE1ad1Ca>{v$rwwvK(>{m~Gf_=-Ro7Fk{#;i~+{{>QtvI yb2P8Zac~?~=sRA>$6{!(^3;ZP0TPFR(G_-UDU(8Jl0?(IXu$~#4A!880|o%~Al1tN literal 0 HcmV?d00001 diff --git a/docs/readthedocsFiles/_build/html/_static/alabaster.css b/docs/readthedocsFiles/_build/html/_static/alabaster.css new file mode 100644 index 00000000..25e77387 --- /dev/null +++ b/docs/readthedocsFiles/_build/html/_static/alabaster.css @@ -0,0 +1,688 @@ +@import url("basic.css"); + +/* -- page layout ----------------------------------------------------------- */ + +body { + font-family: Georgia, serif; + font-size: 17px; + background-color: #fff; + color: #000; + margin: 0; + padding: 0; +} + + +div.document { + width: 940px; + margin: 30px auto 0 auto; +} + +div.documentwrapper { + float: left; + width: 100%; +} + +div.bodywrapper { + margin: 0 0 0 220px; +} + +div.sphinxsidebar { + width: 220px; + font-size: 14px; + line-height: 1.5; +} + +hr { + border: 1px solid #B1B4B6; +} + +div.body { + background-color: #fff; + color: #3E4349; + padding: 0 30px 0 30px; +} + +div.body > .section { + text-align: left; +} + +div.footer { + width: 940px; + margin: 20px auto 30px auto; + font-size: 14px; + color: #888; + text-align: right; +} + +div.footer a { + color: #888; +} + +p.caption { + font-family: inherit; + font-size: inherit; +} + + +div.relations { + display: none; +} + + +div.sphinxsidebar a { + color: #444; + text-decoration: none; + border-bottom: 1px dotted #999; +} + +div.sphinxsidebar a:hover { + border-bottom: 1px solid #999; +} + +div.sphinxsidebarwrapper { + padding: 18px 10px; +} + +div.sphinxsidebarwrapper p.logo { + padding: 0; + margin: -10px 0 0 0px; + text-align: center; +} + +div.sphinxsidebarwrapper h1.logo { + margin-top: -10px; + text-align: center; + margin-bottom: 5px; + text-align: left; +} + +div.sphinxsidebarwrapper h1.logo-name { + margin-top: 0px; +} + +div.sphinxsidebarwrapper p.blurb { + margin-top: 0; + font-style: normal; +} + +div.sphinxsidebar h3, +div.sphinxsidebar h4 { + font-family: Georgia, serif; + color: #444; + font-size: 24px; + font-weight: normal; + margin: 0 0 5px 0; + padding: 0; +} + +div.sphinxsidebar h4 { + font-size: 20px; +} + +div.sphinxsidebar h3 a { + color: #444; +} + +div.sphinxsidebar p.logo a, +div.sphinxsidebar h3 a, +div.sphinxsidebar p.logo a:hover, +div.sphinxsidebar h3 a:hover { + border: none; +} + +div.sphinxsidebar p { + color: #555; + margin: 10px 0; +} + +div.sphinxsidebar ul { + margin: 10px 0; + padding: 0; + color: #000; +} + +div.sphinxsidebar ul li.toctree-l1 > a { + font-size: 120%; +} + +div.sphinxsidebar ul li.toctree-l2 > a { + font-size: 110%; +} + +div.sphinxsidebar input { + border: 1px solid #CCC; + font-family: Georgia, serif; + font-size: 1em; +} + +div.sphinxsidebar hr { + border: none; + height: 1px; + color: #AAA; + background: #AAA; + + text-align: left; + margin-left: 0; + width: 50%; +} + +/* -- body styles ----------------------------------------------------------- */ + +a { + color: #004B6B; + text-decoration: underline; +} + +a:hover { + color: #6D4100; + text-decoration: underline; +} + +div.body h1, +div.body h2, +div.body h3, +div.body h4, +div.body h5, +div.body h6 { + font-family: Georgia, serif; + font-weight: normal; + margin: 30px 0px 10px 0px; + padding: 0; +} + +div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } +div.body h2 { font-size: 180%; } +div.body h3 { font-size: 150%; } +div.body h4 { font-size: 130%; } +div.body h5 { font-size: 100%; } +div.body h6 { font-size: 100%; } + +a.headerlink { + color: #DDD; + padding: 0 4px; + text-decoration: none; +} + +a.headerlink:hover { + color: #444; + background: #EAEAEA; +} + +div.body p, div.body dd, div.body li { + line-height: 1.4em; +} + +div.admonition { + margin: 20px 0px; + padding: 10px 30px; + background-color: #EEE; + border: 1px solid #CCC; +} + +div.admonition tt.xref, div.admonition code.xref, div.admonition a tt { + background-color: #FBFBFB; + border-bottom: 1px solid #fafafa; +} + +div.admonition p.admonition-title { + font-family: Georgia, serif; + font-weight: normal; + font-size: 24px; + margin: 0 0 10px 0; + padding: 0; + line-height: 1; +} + +div.admonition p.last { + margin-bottom: 0; +} + +div.highlight { + background-color: #fff; +} + +dt:target, .highlight { + background: #FAF3E8; +} + +div.warning { + background-color: #FCC; + border: 1px solid #FAA; +} + +div.danger { + background-color: #FCC; + border: 1px solid #FAA; + -moz-box-shadow: 2px 2px 4px #D52C2C; + -webkit-box-shadow: 2px 2px 4px #D52C2C; + box-shadow: 2px 2px 4px #D52C2C; +} + +div.error { + background-color: #FCC; + border: 1px solid #FAA; + -moz-box-shadow: 2px 2px 4px #D52C2C; + -webkit-box-shadow: 2px 2px 4px #D52C2C; + box-shadow: 2px 2px 4px #D52C2C; +} + +div.caution { + background-color: #FCC; + border: 1px solid #FAA; +} + +div.attention { + background-color: #FCC; + border: 1px solid #FAA; +} + +div.important { + background-color: #EEE; + border: 1px solid #CCC; +} + +div.note { + background-color: #EEE; + border: 1px solid #CCC; +} + +div.tip { + background-color: #EEE; + border: 1px solid #CCC; +} + +div.hint { + background-color: #EEE; + border: 1px solid #CCC; +} + +div.seealso { + background-color: #EEE; + border: 1px solid #CCC; +} + +div.topic { + background-color: #EEE; +} + +p.admonition-title { + display: inline; +} + +p.admonition-title:after { + content: ":"; +} + +pre, tt, code { + font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; + font-size: 0.9em; +} + +.hll { + background-color: #FFC; + margin: 0 -12px; + padding: 0 12px; + display: block; +} + +img.screenshot { +} + +tt.descname, tt.descclassname, code.descname, code.descclassname { + font-size: 0.95em; +} + +tt.descname, code.descname { + padding-right: 0.08em; +} + +img.screenshot { + -moz-box-shadow: 2px 2px 4px #EEE; + -webkit-box-shadow: 2px 2px 4px #EEE; + box-shadow: 2px 2px 4px #EEE; +} + +table.docutils { + border: 1px solid #888; + -moz-box-shadow: 2px 2px 4px #EEE; + -webkit-box-shadow: 2px 2px 4px #EEE; + box-shadow: 2px 2px 4px #EEE; +} + +table.docutils td, table.docutils th { + border: 1px solid #888; + padding: 0.25em 0.7em; +} + +table.field-list, table.footnote { + border: none; + -moz-box-shadow: none; + -webkit-box-shadow: none; + box-shadow: none; +} + +table.footnote { + margin: 15px 0; + width: 100%; + border: 1px solid #EEE; + background: #FDFDFD; + font-size: 0.9em; +} + +table.footnote + table.footnote { + margin-top: -15px; + border-top: none; +} + +table.field-list th { + padding: 0 0.8em 0 0; +} + +table.field-list td { + padding: 0; +} + +table.field-list p { + margin-bottom: 0.8em; +} + +/* Cloned from + * https://github.com/sphinx-doc/sphinx/commit/ef60dbfce09286b20b7385333d63a60321784e68 + */ +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +table.footnote td.label { + width: .1px; + padding: 0.3em 0 0.3em 0.5em; +} + +table.footnote td { + padding: 0.3em 0.5em; +} + +dl { + margin: 0; + padding: 0; +} + +dl dd { + margin-left: 30px; +} + +blockquote { + margin: 0 0 0 30px; + padding: 0; +} + +ul, ol { + /* Matches the 30px from the narrow-screen "li > ul" selector below */ + margin: 10px 0 10px 30px; + padding: 0; +} + +pre { + background: #EEE; + padding: 7px 30px; + margin: 15px 0px; + line-height: 1.3em; +} + +div.viewcode-block:target { + background: #ffd; +} + +dl pre, blockquote pre, li pre { + margin-left: 0; + padding-left: 30px; +} + +tt, code { + background-color: #ecf0f3; + color: #222; + /* padding: 1px 2px; */ +} + +tt.xref, code.xref, a tt { + background-color: #FBFBFB; + border-bottom: 1px solid #fff; +} + +a.reference { + text-decoration: none; + border-bottom: 1px dotted #004B6B; +} + +/* Don't put an underline on images */ +a.image-reference, a.image-reference:hover { + border-bottom: none; +} + +a.reference:hover { + border-bottom: 1px solid #6D4100; +} + +a.footnote-reference { + text-decoration: none; + font-size: 0.7em; + vertical-align: top; + border-bottom: 1px dotted #004B6B; +} + +a.footnote-reference:hover { + border-bottom: 1px solid #6D4100; +} + +a:hover tt, a:hover code { + background: #EEE; +} + + +@media screen and (max-width: 870px) { + + div.sphinxsidebar { + display: none; + } + + div.document { + width: 100%; + + } + + div.documentwrapper { + margin-left: 0; + margin-top: 0; + margin-right: 0; + margin-bottom: 0; + } + + div.bodywrapper { + margin-top: 0; + margin-right: 0; + margin-bottom: 0; + margin-left: 0; + } + + ul { + margin-left: 0; + } + + li > ul { + /* Matches the 30px from the "ul, ol" selector above */ + margin-left: 30px; + } + + .document { + width: auto; + } + + .footer { + width: auto; + } + + .bodywrapper { + margin: 0; + } + + .footer { + width: auto; + } + + .github { + display: none; + } + + + +} + + + +@media screen and (max-width: 875px) { + + body { + margin: 0; + padding: 20px 30px; + } + + div.documentwrapper { + float: none; + background: #fff; + } + + div.sphinxsidebar { + display: block; + float: none; + width: 102.5%; + margin: 50px -30px -20px -30px; + padding: 10px 20px; + background: #333; + color: #FFF; + } + + div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, + div.sphinxsidebar h3 a { + color: #fff; + } + + div.sphinxsidebar a { + color: #AAA; + } + + div.sphinxsidebar p.logo { + display: none; + } + + div.document { + width: 100%; + margin: 0; + } + + div.footer { + display: none; + } + + div.bodywrapper { + margin: 0; + } + + div.body { + min-height: 0; + padding: 0; + } + + .rtd_doc_footer { + display: none; + } + + .document { + width: auto; + } + + .footer { + width: auto; + } + + .footer { + width: auto; + } + + .github { + display: none; + } +} + + +/* misc. */ + +.revsys-inline { + display: none!important; +} + +/* Make nested-list/multi-paragraph items look better in Releases changelog + * pages. Without this, docutils' magical list fuckery causes inconsistent + * formatting between different release sub-lists. + */ +div#changelog > div.section > ul > li > p:only-child { + margin-bottom: 0; +} + +/* Hide fugly table cell borders in ..bibliography:: directive output */ +table.docutils.citation, table.docutils.citation td, table.docutils.citation th { + border: none; + /* Below needed in some edge cases; if not applied, bottom shadows appear */ + -moz-box-shadow: none; + -webkit-box-shadow: none; + box-shadow: none; +} + + +/* relbar */ + +.related { + line-height: 30px; + width: 100%; + font-size: 0.9rem; +} + +.related.top { + border-bottom: 1px solid #EEE; + margin-bottom: 20px; +} + +.related.bottom { + border-top: 1px solid #EEE; +} + +.related ul { + padding: 0; + margin: 0; + list-style: none; +} + +.related li { + display: inline; +} + +nav#rellinks { + float: right; +} + +nav#rellinks li+li:before { + content: "|"; +} + +nav#breadcrumbs li+li:before { + content: "\00BB"; +} + +/* Hide certain items when printing */ +@media print { + div.related { + display: none; + } +} \ No newline at end of file diff --git a/docs/readthedocsFiles/_build/html/_static/basic.css b/docs/readthedocsFiles/_build/html/_static/basic.css new file mode 100644 index 00000000..19ced105 --- /dev/null +++ b/docs/readthedocsFiles/_build/html/_static/basic.css @@ -0,0 +1,665 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2018 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li div.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: 450px; + max-width: 800px; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px 7px 0 7px; + background-color: #ffe; + width: 40%; + float: right; +} + +p.sidebar-title { + font-weight: bold; +} + +/* -- topics ---------------------------------------------------------------- */ + +div.topic { + border: 1px solid #ccc; + padding: 7px 7px 0 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +div.admonition dl { + margin-bottom: 0; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +table.footnote td, table.footnote th { + border: 0 !important; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +dl { + margin-bottom: 15px; +} + +dd p { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; +} + +td.linenos pre { + padding: 5px 0px; + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + margin-left: 0.5em; +} + +table.highlighttable td { + padding: 0 0.5em 0 0.5em; +} + +div.code-block-caption { + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +div.code-block-caption + div > div.highlight > pre { + margin-top: 0; +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + padding: 1em 1em 0; +} + +div.literal-block-wrapper div.highlight { + margin: 0; +} + +code.descname { + background-color: transparent; + font-weight: bold; + font-size: 1.2em; +} + +code.descclassname { + background-color: transparent; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: relative; + left: 0px; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/docs/readthedocsFiles/_build/html/_static/comment-bright.png b/docs/readthedocsFiles/_build/html/_static/comment-bright.png new file mode 100644 index 0000000000000000000000000000000000000000..15e27edb12ac25701ac0ac21b97b52bb4e45415e GIT binary patch literal 756 zcmV<Q0t@|#P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV0008JNkl<Zcmaiz z1Hj}w5Qg8Gq}}~(+qP}nwsE#?+qP}nwr!&y+D+z~J^P8B5f1Z2p}9E7<MeddjkkYX zd-EIb)!Fph8#Oll|7xX;pFUA;-4hpnGx`#oio0{nM4Efs-uiF5ty3!w*}J>gfIX78 z$8Pzv({A~p%??+>KickCb#0FM1rYN=mBmQ&Nwp<#JXUhU;{|)}%&s>suq6lXw*~s{ zvHx}3C%<;wE5CH!BR{p<CEvB*W&e*ae5&H6TsQ>5@ml9ws}y)=QN-kL2?#`S5d*6j zk`h<}j1>tD$b?4D^N9w}-k)bx<r``%#N@BubkHRi`8NiUJ1QcYNoj^+f{t5;yOfCu z025_fkz23uZryUlH?8%X)iz&u&N=GVtpN}jfHhN+0n&st2CyuMEP+D?lNAvnV{r9d zcU*Hd8>XxFg>+#kme^xx#qg6FI-%iv2U{<p;QMgJt&^`Ag7_W6#X2)I$;?RfMG1!X z06%V(20R6@MOywSVz5Sxg}7Rrcw!_)k>0h(Y)cs%5a|m%Pn_K3X_bDJ>EH#(Fb73Z zfUt2Q3B>N+ot3qb*DqbTZpFIn4a!#_R-}{?-~Hs=xSS6p&$sZ-k1zDdtqU`Y@`#qL z&zv-~)Q#JCU(dI)Hf;$CEnK=6CK50}q7~wdbI->?E07bJ0R;!GSQTs<U-;V35W~fW z36@m9t>5Am`#;*WHjvHRvY?&$Lm-vq1a_BzocI^ULXV!lbMd%|^B#fY;XX)n<&R^L z=84u1e_3ziq;Hz-*k5~zwY3*oDKt0;bM@M@@89;@m*4RFgvvM_4;5LB!@OB@^WbVT zjl{t;<hC4hUbrL2lKb#<%ZqWCYg*v6+?;pq-M_TowLXr*A-JpI`8bZ>a8_>od-~P4 m{5|DvB&z#xT;*OnJqG}gk~_7HcNkCr0000<MNUMnLSTXfh<FSD literal 0 HcmV?d00001 diff --git a/docs/readthedocsFiles/_build/html/_static/comment-close.png b/docs/readthedocsFiles/_build/html/_static/comment-close.png new file mode 100644 index 0000000000000000000000000000000000000000..4d91bcf57de866a901a89a2a68c0f36af1114841 GIT binary patch literal 829 zcmV-D1H$}?P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00097Nkl<ZcmZwB z1Dy3Z6o&ElB<&b$^RsQ+wr$(CZQHhO+qT_7?@i8TaM%C-o;rX}=B^hIClh5G@FY>W zanA~u9RIXo;n7c96&U)YLgs-FGlx~*_c{Jgvesu1E5(8YEf&5wF=YFPcRe@1=MJmi zag(L*xc2<lF}aNwyuSNG>r0(slpcN!vC5CUju;vHJkHc*&70_n2OZsK%O~A=!+YIw z<wtI?<OA1V_MYo5e9JW#z16MEgjt6?ZHst>7zLLl7~Z+~RgWOQ=MI6$#0pvpu$Q43 zP@36QAmu6!_9NPM?o<1_!+stoVRRZbW9#SPe!n;#A_6m8f}|xN1;H{`0RoXQ2LM47 zt(g;iZ6|pCb@h2xk&(}S3=EVBUO0e90m2Lp5CB<(SPIaB;n4))3JB87Or#XPOPcum z?<^(g+m9}VNn4Y&B`g8h{t_$+R<w(NPp`pR;bZXUAU_*$1^F@H_u_HjklAf2Sdp#@ zi1e@(?k`~3fS<Wa3$P{d`>B1%HKRY6fjtd-<7&EsU;vs0GM(Lmbhi%Gwcfs0FTF}T zL{_M6Go&E0Eg8FuB*(Yn+Z*RVTBE@10eIOb3El^MhO`GabDll(V0&FlJi2k^;q8af zkENdk2}x2)_KVp`5OAwXZM;dG0?M-S)xE1IKDi6BY@5%Or?#aZ9$gcX)dPZ&wA1a< z$rFXHPn|TBf`e?>Are8sKtKrKcjF$i^lp!zkL?C|y^vlHr1HXeVJd;1I~g&Ob-q)& z(fn7s-KI}G{wnK<H<)KWVxE4VdETD8{2*;k)&R4~T%#E%j&$o0>zg_U5G(V%bX6uk zIa+<@>rdmZYd!9Y=C0cuchrbIjuRB_Wq{-RXlic?flu1*_ux}x%(HDH&nT`k^xCeC ziHi1!ChH*sQ6|UqJpTTzX$aw8e(UfcS^f;6yB<A{y1b}M;$`2cPs0I(nH<v~(<#$j z(=R3u{_U4$r@s5W+3{rXALYlulK55cnX3D?%s|HYcaeVp)Om4$BmFtO00000NkvXX Hu0mjf1x1rJ literal 0 HcmV?d00001 diff --git a/docs/readthedocsFiles/_build/html/_static/comment.png b/docs/readthedocsFiles/_build/html/_static/comment.png new file mode 100644 index 0000000000000000000000000000000000000000..dfbc0cbd512bdeefcb1984c99d8e577efb77f006 GIT binary patch literal 641 zcmV-{0)G98P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV0006=Nkl<Zcmaiw z1B@MC6vpR|+E!HCR@|_TQOC6vt435es%^Vn@9aE}%}s;a?Y%wcJ|ydte7XF72V6cT zZcVcF@Zn4UJS$fQ6bbsQH-fg}6|b#&$*T*WmdO*(Ka6+c>Wd+(1-70zU(rtxtqR%j z-lsH|CKQJXqD{+F7V0OTv8@{~(wp(`oIP^ZykMWgR>&|RsklFMCnOo&Bd{le<WL>} zV5F6424Qzl;o2G%oVvmHgRDP9!=rK8fy^!yV8y*4p=??uIRrrr0?>O!(z*g5Av<N7 z9bjgfR9%u#U0Oo`L>L2!4z0{sq%vhG*Po}`a<6%<Pg--|g3yQn3VWI{Jf@U}Du56| zc#*aA;RUY^;9$z*60-XpjVP=_G=rKle45s4K(Lh`;GMuduTZ8zf4`a8$eLw4pbALM zt+G`Eg6-g7ze4q+xrfElKq%=0lu6(d!Oxl#Qr!)y;YPS3r~qN@C@#(*d{QcR<Idg_ zT0$>kTK5TNhtC8}rXNu&h^QH4A&Sk~Autm*s~45(H7+0bi^MraaRVzr05hQ3iK?j` zR#U@^i0WhkIHTg29u~|ypU?sXCQEQgXfObPW;+0YAF;|5XyaMAEM0sQ@4-xCZe=0e z7r$ofiAxn@O5#RodD8rh5D@nKQ;?lcf@tg4o+Wp44aMl~c47azN_(im0N)7OqdPBC zGw;353_o$DqGRDhuhU$Eaj!@m0<HM3c<s^gb7gJ08nJ?FGHyIi^#lz$dc34LhtZ?q bY#4t557cxjxf?U>00000NkvXXu0mjfjZ7Z_ literal 0 HcmV?d00001 diff --git a/docs/readthedocsFiles/_build/html/_static/custom.css b/docs/readthedocsFiles/_build/html/_static/custom.css new file mode 100644 index 00000000..2a924f1d --- /dev/null +++ b/docs/readthedocsFiles/_build/html/_static/custom.css @@ -0,0 +1 @@ +/* This file intentionally left blank. */ diff --git a/docs/readthedocsFiles/_build/html/_static/doctools.js b/docs/readthedocsFiles/_build/html/_static/doctools.js new file mode 100644 index 00000000..d8928926 --- /dev/null +++ b/docs/readthedocsFiles/_build/html/_static/doctools.js @@ -0,0 +1,313 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for all documentation. + * + * :copyright: Copyright 2007-2018 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/** + * select a different prefix for underscore + */ +$u = _.noConflict(); + +/** + * make the code below compatible with browsers without + * an installed firebug like debugger +if (!window.console || !console.firebug) { + var names = ["log", "debug", "info", "warn", "error", "assert", "dir", + "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", + "profile", "profileEnd"]; + window.console = {}; + for (var i = 0; i < names.length; ++i) + window.console[names[i]] = function() {}; +} + */ + +/** + * small helper function to urldecode strings + */ +jQuery.urldecode = function(x) { + return decodeURIComponent(x).replace(/\+/g, ' '); +}; + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s === 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node, addItems) { + if (node.nodeType === 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && + !jQuery(node.parentNode).hasClass(className) && + !jQuery(node.parentNode).hasClass("nohighlight")) { + var span; + var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.className = className; + } + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + if (isInSVG) { + var bbox = span.getBBox(); + var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute('class', className); + var parentOfText = node.parentNode.parentNode; + addItems.push({ + "parent": node.parentNode, + "target": rect}); + } + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this, addItems); + }); + } + } + var addItems = []; + var result = this.each(function() { + highlight(this, addItems); + }); + for (var i = 0; i < addItems.length; ++i) { + jQuery(addItems[i].parent).before(addItems[i].target); + } + return result; +}; + +/* + * backward compatibility for jQuery.browser + * This will be supported until firefox bug is fixed. + */ +if (!jQuery.browser) { + jQuery.uaMatch = function(ua) { + ua = ua.toLowerCase(); + + var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || + /(webkit)[ \/]([\w.]+)/.exec(ua) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || + /(msie) ([\w.]+)/.exec(ua) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || + []; + + return { + browser: match[ 1 ] || "", + version: match[ 2 ] || "0" + }; + }; + jQuery.browser = {}; + jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; +} + +/** + * Small JavaScript module for the documentation. + */ +var Documentation = { + + init : function() { + this.fixFirefoxAnchorBug(); + this.highlightSearchWords(); + this.initIndexTable(); + + }, + + /** + * i18n support + */ + TRANSLATIONS : {}, + PLURAL_EXPR : function(n) { return n === 1 ? 0 : 1; }, + LOCALE : 'unknown', + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext : function(string) { + var translated = Documentation.TRANSLATIONS[string]; + if (typeof translated === 'undefined') + return string; + return (typeof translated === 'string') ? translated : translated[0]; + }, + + ngettext : function(singular, plural, n) { + var translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated === 'undefined') + return (n == 1) ? singular : plural; + return translated[Documentation.PLURALEXPR(n)]; + }, + + addTranslations : function(catalog) { + for (var key in catalog.messages) + this.TRANSLATIONS[key] = catalog.messages[key]; + this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); + this.LOCALE = catalog.locale; + }, + + /** + * add context elements like header anchor links + */ + addContextElements : function() { + $('div[id] > :header:first').each(function() { + $('<a class="headerlink">\u00B6</a>'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this headline')). + appendTo(this); + }); + $('dt[id]').each(function() { + $('<a class="headerlink">\u00B6</a>'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this definition')). + appendTo(this); + }); + }, + + /** + * workaround a firefox stupidity + * see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075 + */ + fixFirefoxAnchorBug : function() { + if (document.location.hash && $.browser.mozilla) + window.setTimeout(function() { + document.location.href += ''; + }, 10); + }, + + /** + * highlight the search words provided in the url in the text + */ + highlightSearchWords : function() { + var params = $.getQueryParameters(); + var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; + if (terms.length) { + var body = $('div.body'); + if (!body.length) { + body = $('body'); + } + window.setTimeout(function() { + $.each(terms, function() { + body.highlightText(this.toLowerCase(), 'highlighted'); + }); + }, 10); + $('<p class="highlight-link"><a href="javascript:Documentation.' + + 'hideSearchWords()">' + _('Hide Search Matches') + '</a></p>') + .appendTo($('#searchbox')); + } + }, + + /** + * init the domain index toggle buttons + */ + initIndexTable : function() { + var togglers = $('img.toggler').click(function() { + var src = $(this).attr('src'); + var idnum = $(this).attr('id').substr(7); + $('tr.cg-' + idnum).toggle(); + if (src.substr(-9) === 'minus.png') + $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); + else + $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); + }).css('display', ''); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { + togglers.click(); + } + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords : function() { + $('#searchbox .highlight-link').fadeOut(300); + $('span.highlighted').removeClass('highlighted'); + }, + + /** + * make the url absolute + */ + makeURL : function(relativeURL) { + return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; + }, + + /** + * get the current relative url + */ + getCurrentURL : function() { + var path = document.location.pathname; + var parts = path.split(/\//); + $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { + if (this === '..') + parts.pop(); + }); + var url = parts.join('/'); + return path.substring(url.lastIndexOf('/') + 1, path.length - 1); + }, + + initOnKeyListeners: function() { + $(document).keyup(function(event) { + var activeElementType = document.activeElement.tagName; + // don't navigate when in search box or textarea + if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT') { + switch (event.keyCode) { + case 37: // left + var prevHref = $('link[rel="prev"]').prop('href'); + if (prevHref) { + window.location.href = prevHref; + return false; + } + case 39: // right + var nextHref = $('link[rel="next"]').prop('href'); + if (nextHref) { + window.location.href = nextHref; + return false; + } + } + } + }); + } +}; + +// quick alias for translations +_ = Documentation.gettext; + +$(document).ready(function() { + Documentation.init(); +}); \ No newline at end of file diff --git a/docs/readthedocsFiles/_build/html/_static/documentation_options.js b/docs/readthedocsFiles/_build/html/_static/documentation_options.js new file mode 100644 index 00000000..5cb7e4e6 --- /dev/null +++ b/docs/readthedocsFiles/_build/html/_static/documentation_options.js @@ -0,0 +1,9 @@ +var DOCUMENTATION_OPTIONS = { + URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), + VERSION: '2.1', + LANGUAGE: 'None', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt' +}; \ No newline at end of file diff --git a/docs/readthedocsFiles/_build/html/_static/down-pressed.png b/docs/readthedocsFiles/_build/html/_static/down-pressed.png new file mode 100644 index 0000000000000000000000000000000000000000..5756c8cad8854722893dc70b9eb4bb0400343a39 GIT binary patch literal 222 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`OFdm2Ln;`PZ^+1>KjR?B@S0W7 z%OS_REiHONoJ6{+Ks@6k3590|7k9F+ddB6!zw3#&!aw#S`x}3V3&=A(a#84O-&F7T z^k3tZB;&iR9siw0|F|E|DAL<8r-F4!1H-;1{e*~yAKZN5f0|Ei6yUmR#Is)EM(Po_ zi`qJR6|P<~+)N+kSDgL7AjdIC_!O7Q?eGb+L+qOjm{~LLinM4NHn7U%HcK%uoMYO5 VJ~8zD2B3o(JYD@<);T3K0RV0%P>BEl literal 0 HcmV?d00001 diff --git a/docs/readthedocsFiles/_build/html/_static/down.png b/docs/readthedocsFiles/_build/html/_static/down.png new file mode 100644 index 0000000000000000000000000000000000000000..1b3bdad2ceffae91cee61b32f3295f9bbe646e48 GIT binary patch literal 202 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6CVIL!hEy=F?b*7pIY7kW{q%Rg zx!yQ<9v8bmJwa`TQk7YSw}WVQ()mRdQ;TC;*<T*?<?OIJF4)K~*`+^EdvW@{fRyJ( z?weB&PpGe(dw+-5<oNH&JW|0z@kgId=L!~9O|3`?{k1Umt465Dp9i1pCaQ@{=WpF| zklotk)=&P-c9F*_Q}bpRPq*XX{4Fk5IYl~|@#V*s^(ig4#DI=t@O1TaS?83{1OQnY BO{f3> literal 0 HcmV?d00001 diff --git a/docs/readthedocsFiles/_build/html/_static/file.png b/docs/readthedocsFiles/_build/html/_static/file.png new file mode 100644 index 0000000000000000000000000000000000000000..a858a410e4faa62ce324d814e4b816fff83a6fb3 GIT binary patch literal 286 zcmV+(0pb3MP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV0002xNkl<Zcmb`G zgHi?o6ovOGdxdP*AltSE*&JruJwUGI3!FN?xxO>s`hMrGg#P~ix$^RISR_I47Y|r1 z_CyJOe}D1){SET-^Amu_i71Lt6eYfZjRyw@I6OQAIXXHD<M{a4P!N^sPbQKi=?mBx zoos%BSoiGXjr-;%$QixXMOVNSUNp6L0a1Oz&cgu)wqE?07u5I7qrQIu4Fij)Y3c&0 z@0u_#NH6I?Mk(n;dT}d~^J<WkTLqp|RW-hV56tKpXqu)k@V{?amI+5DOlEU@funz+ kySsbM>fiX^GbOlHe=Ae4>0m)d(f|Me07*qoM6N<$f}vM^LjV8( literal 0 HcmV?d00001 diff --git a/docs/readthedocsFiles/_build/html/_static/jquery-3.2.1.js b/docs/readthedocsFiles/_build/html/_static/jquery-3.2.1.js new file mode 100644 index 00000000..d2d8ca47 --- /dev/null +++ b/docs/readthedocsFiles/_build/html/_static/jquery-3.2.1.js @@ -0,0 +1,10253 @@ +/*! + * jQuery JavaScript Library v3.2.1 + * https://jquery.com/ + * + * Includes Sizzle.js + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://jquery.org/license + * + * Date: 2017-03-20T18:59Z + */ +( function( global, factory ) { + + "use strict"; + + if ( typeof module === "object" && typeof module.exports === "object" ) { + + // For CommonJS and CommonJS-like environments where a proper `window` + // is present, execute the factory and get jQuery. + // For environments that do not have a `window` with a `document` + // (such as Node.js), expose a factory as module.exports. + // This accentuates the need for the creation of a real `window`. + // e.g. var jQuery = require("jquery")(window); + // See ticket #14549 for more info. + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +// Pass this if window is not defined yet +} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 +// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode +// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common +// enough that all such attempts are guarded in a try block. +"use strict"; + +var arr = []; + +var document = window.document; + +var getProto = Object.getPrototypeOf; + +var slice = arr.slice; + +var concat = arr.concat; + +var push = arr.push; + +var indexOf = arr.indexOf; + +var class2type = {}; + +var toString = class2type.toString; + +var hasOwn = class2type.hasOwnProperty; + +var fnToString = hasOwn.toString; + +var ObjectFunctionString = fnToString.call( Object ); + +var support = {}; + + + + function DOMEval( code, doc ) { + doc = doc || document; + + var script = doc.createElement( "script" ); + + script.text = code; + doc.head.appendChild( script ).parentNode.removeChild( script ); + } +/* global Symbol */ +// Defining this global in .eslintrc.json would create a danger of using the global +// unguarded in another place, it seems safer to define global only for this module + + + +var + version = "3.2.1", + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + + // The jQuery object is actually just the init constructor 'enhanced' + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init( selector, context ); + }, + + // Support: Android <=4.0 only + // Make sure we trim BOM and NBSP + rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, + + // Matches dashed string for camelizing + rmsPrefix = /^-ms-/, + rdashAlpha = /-([a-z])/g, + + // Used by jQuery.camelCase as callback to replace() + fcamelCase = function( all, letter ) { + return letter.toUpperCase(); + }; + +jQuery.fn = jQuery.prototype = { + + // The current version of jQuery being used + jquery: version, + + constructor: jQuery, + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + + // Return all the elements in a clean array + if ( num == null ) { + return slice.call( this ); + } + + // Return just the one element from the set + return num < 0 ? this[ num + this.length ] : this[ num ]; + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + each: function( callback ) { + return jQuery.each( this, callback ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map( this, function( elem, i ) { + return callback.call( elem, i, elem ); + } ) ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); + }, + + end: function() { + return this.prevObject || this.constructor(); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: arr.sort, + splice: arr.splice +}; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[ 0 ] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + + // Skip the boolean and the target + target = arguments[ i ] || {}; + i++; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction( target ) ) { + target = {}; + } + + // Extend jQuery itself if only one argument is passed + if ( i === length ) { + target = this; + i--; + } + + for ( ; i < length; i++ ) { + + // Only deal with non-null/undefined values + if ( ( options = arguments[ i ] ) != null ) { + + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject( copy ) || + ( copyIsArray = Array.isArray( copy ) ) ) ) { + + if ( copyIsArray ) { + copyIsArray = false; + clone = src && Array.isArray( src ) ? src : []; + + } else { + clone = src && jQuery.isPlainObject( src ) ? src : {}; + } + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend( { + + // Unique for each copy of jQuery on the page + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + // Assume jQuery is ready without the ready module + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + isFunction: function( obj ) { + return jQuery.type( obj ) === "function"; + }, + + isWindow: function( obj ) { + return obj != null && obj === obj.window; + }, + + isNumeric: function( obj ) { + + // As of jQuery 3.0, isNumeric is limited to + // strings and numbers (primitives or objects) + // that can be coerced to finite numbers (gh-2662) + var type = jQuery.type( obj ); + return ( type === "number" || type === "string" ) && + + // parseFloat NaNs numeric-cast false positives ("") + // ...but misinterprets leading-number strings, particularly hex literals ("0x...") + // subtraction forces infinities to NaN + !isNaN( obj - parseFloat( obj ) ); + }, + + isPlainObject: function( obj ) { + var proto, Ctor; + + // Detect obvious negatives + // Use toString instead of jQuery.type to catch host objects + if ( !obj || toString.call( obj ) !== "[object Object]" ) { + return false; + } + + proto = getProto( obj ); + + // Objects with no prototype (e.g., `Object.create( null )`) are plain + if ( !proto ) { + return true; + } + + // Objects with prototype are plain iff they were constructed by a global Object function + Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; + return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; + }, + + isEmptyObject: function( obj ) { + + /* eslint-disable no-unused-vars */ + // See https://github.com/eslint/eslint/issues/6125 + var name; + + for ( name in obj ) { + return false; + } + return true; + }, + + type: function( obj ) { + if ( obj == null ) { + return obj + ""; + } + + // Support: Android <=2.3 only (functionish RegExp) + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call( obj ) ] || "object" : + typeof obj; + }, + + // Evaluates a script in a global context + globalEval: function( code ) { + DOMEval( code ); + }, + + // Convert dashed to camelCase; used by the css and data modules + // Support: IE <=9 - 11, Edge 12 - 13 + // Microsoft forgot to hump their vendor prefix (#9572) + camelCase: function( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); + }, + + each: function( obj, callback ) { + var length, i = 0; + + if ( isArrayLike( obj ) ) { + length = obj.length; + for ( ; i < length; i++ ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } else { + for ( i in obj ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } + + return obj; + }, + + // Support: Android <=4.0 only + trim: function( text ) { + return text == null ? + "" : + ( text + "" ).replace( rtrim, "" ); + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArrayLike( Object( arr ) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + return arr == null ? -1 : indexOf.call( arr, elem, i ); + }, + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + merge: function( first, second ) { + var len = +second.length, + j = 0, + i = first.length; + + for ( ; j < len; j++ ) { + first[ i++ ] = second[ j ]; + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var length, value, + i = 0, + ret = []; + + // Go through the array, translating each of the items to their new values + if ( isArrayLike( elems ) ) { + length = elems.length; + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + // Flatten any nested arrays + return concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // Bind a function to a context, optionally partially applying any + // arguments. + proxy: function( fn, context ) { + var tmp, args, proxy; + + if ( typeof context === "string" ) { + tmp = fn[ context ]; + context = fn; + fn = tmp; + } + + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if ( !jQuery.isFunction( fn ) ) { + return undefined; + } + + // Simulated bind + args = slice.call( arguments, 2 ); + proxy = function() { + return fn.apply( context || this, args.concat( slice.call( arguments ) ) ); + }; + + // Set the guid of unique handler to the same of original handler, so it can be removed + proxy.guid = fn.guid = fn.guid || jQuery.guid++; + + return proxy; + }, + + now: Date.now, + + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support: support +} ); + +if ( typeof Symbol === "function" ) { + jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; +} + +// Populate the class2type map +jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), +function( i, name ) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +} ); + +function isArrayLike( obj ) { + + // Support: real iOS 8.2 only (not reproducible in simulator) + // `in` check used to prevent JIT error (gh-2145) + // hasOwn isn't used here due to false negatives + // regarding Nodelist length in IE + var length = !!obj && "length" in obj && obj.length, + type = jQuery.type( obj ); + + if ( type === "function" || jQuery.isWindow( obj ) ) { + return false; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} +var Sizzle = +/*! + * Sizzle CSS Selector Engine v2.3.3 + * https://sizzlejs.com/ + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: 2016-08-08 + */ +(function( window ) { + +var i, + support, + Expr, + getText, + isXML, + tokenize, + compile, + select, + outermostContext, + sortInput, + hasDuplicate, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + 1 * new Date(), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + // Instance methods + hasOwn = ({}).hasOwnProperty, + arr = [], + pop = arr.pop, + push_native = arr.push, + push = arr.push, + slice = arr.slice, + // Use a stripped-down indexOf as it's faster than native + // https://jsperf.com/thor-indexof-vs-for/5 + indexOf = function( list, elem ) { + var i = 0, + len = list.length; + for ( ; i < len; i++ ) { + if ( list[i] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + + // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier + identifier = "(?:\\\\.|[\\w-]|[^\0-\\xa0])+", + + // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + + // Operator (capture 2) + "*([*^$|!~]?=)" + whitespace + + // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + + "*\\]", + + pseudos = ":(" + identifier + ")(?:\\((" + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: + // 1. quoted (capture 3; capture 4 or capture 5) + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + // 2. simple (capture 6) + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + // 3. anything else (capture 2) + ".*" + + ")\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rwhitespace = new RegExp( whitespace + "+", "g" ), + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), + + rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + identifier + ")" ), + "CLASS": new RegExp( "^\\.(" + identifier + ")" ), + "TAG": new RegExp( "^(" + identifier + "|[*])" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + + // CSS escapes + // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), + funescape = function( _, escaped, escapedWhitespace ) { + var high = "0x" + escaped - 0x10000; + // NaN means non-codepoint + // Support: Firefox<24 + // Workaround erroneous numeric interpretation of +"0x" + return high !== high || escapedWhitespace ? + escaped : + high < 0 ? + // BMP codepoint + String.fromCharCode( high + 0x10000 ) : + // Supplemental Plane codepoint (surrogate pair) + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }, + + // CSS string/identifier serialization + // https://drafts.csswg.org/cssom/#common-serializing-idioms + rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, + fcssescape = function( ch, asCodePoint ) { + if ( asCodePoint ) { + + // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER + if ( ch === "\0" ) { + return "\uFFFD"; + } + + // Control characters and (dependent upon position) numbers get escaped as code points + return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + } + + // Other potentially-special ASCII characters get backslash-escaped + return "\\" + ch; + }, + + // Used for iframes + // See setDocument() + // Removing the function wrapper causes a "Permission Denied" + // error in IE + unloadHandler = function() { + setDocument(); + }, + + disabledAncestor = addCombinator( + function( elem ) { + return elem.disabled === true && ("form" in elem || "label" in elem); + }, + { dir: "parentNode", next: "legend" } + ); + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + (arr = slice.call( preferredDoc.childNodes )), + preferredDoc.childNodes + ); + // Support: Android<4.0 + // Detect silently failing push.apply + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + push_native.apply( target, slice.call(els) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + var j = target.length, + i = 0; + // Can't trust NodeList.length + while ( (target[j++] = els[i++]) ) {} + target.length = j - 1; + } + }; +} + +function Sizzle( selector, context, results, seed ) { + var m, i, elem, nid, match, groups, newSelector, + newContext = context && context.ownerDocument, + + // nodeType defaults to 9, since context defaults to document + nodeType = context ? context.nodeType : 9; + + results = results || []; + + // Return early from calls with invalid selector or context + if ( typeof selector !== "string" || !selector || + nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { + + return results; + } + + // Try to shortcut find operations (as opposed to filters) in HTML documents + if ( !seed ) { + + if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { + setDocument( context ); + } + context = context || document; + + if ( documentIsHTML ) { + + // If the selector is sufficiently simple, try using a "get*By*" DOM method + // (excepting DocumentFragment context, where the methods don't exist) + if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) { + + // ID selector + if ( (m = match[1]) ) { + + // Document context + if ( nodeType === 9 ) { + if ( (elem = context.getElementById( m )) ) { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + + // Element context + } else { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( newContext && (elem = newContext.getElementById( m )) && + contains( context, elem ) && + elem.id === m ) { + + results.push( elem ); + return results; + } + } + + // Type selector + } else if ( match[2] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Class selector + } else if ( (m = match[3]) && support.getElementsByClassName && + context.getElementsByClassName ) { + + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // Take advantage of querySelectorAll + if ( support.qsa && + !compilerCache[ selector + " " ] && + (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { + + if ( nodeType !== 1 ) { + newContext = context; + newSelector = selector; + + // qSA looks outside Element context, which is not what we want + // Thanks to Andrew Dupont for this workaround technique + // Support: IE <=8 + // Exclude object elements + } else if ( context.nodeName.toLowerCase() !== "object" ) { + + // Capture the context ID, setting it first if necessary + if ( (nid = context.getAttribute( "id" )) ) { + nid = nid.replace( rcssescape, fcssescape ); + } else { + context.setAttribute( "id", (nid = expando) ); + } + + // Prefix every selector in the list + groups = tokenize( selector ); + i = groups.length; + while ( i-- ) { + groups[i] = "#" + nid + " " + toSelector( groups[i] ); + } + newSelector = groups.join( "," ); + + // Expand context for sibling selectors + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || + context; + } + + if ( newSelector ) { + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch ( qsaError ) { + } finally { + if ( nid === expando ) { + context.removeAttribute( "id" ); + } + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {function(string, object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key + " " ) > Expr.cacheLength ) { + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return (cache[ key + " " ] = value); + } + return cache; +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created element and returns a boolean result + */ +function assert( fn ) { + var el = document.createElement("fieldset"); + + try { + return !!fn( el ); + } catch (e) { + return false; + } finally { + // Remove from its parent by default + if ( el.parentNode ) { + el.parentNode.removeChild( el ); + } + // release memory in IE + el = null; + } +} + +/** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ +function addHandle( attrs, handler ) { + var arr = attrs.split("|"), + i = arr.length; + + while ( i-- ) { + Expr.attrHandle[ arr[i] ] = handler; + } +} + +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + a.sourceIndex - b.sourceIndex; + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( (cur = cur.nextSibling) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for :enabled/:disabled + * @param {Boolean} disabled true for :disabled; false for :enabled + */ +function createDisabledPseudo( disabled ) { + + // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable + return function( elem ) { + + // Only certain elements can match :enabled or :disabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled + if ( "form" in elem ) { + + // Check for inherited disabledness on relevant non-disabled elements: + // * listed form-associated elements in a disabled fieldset + // https://html.spec.whatwg.org/multipage/forms.html#category-listed + // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled + // * option elements in a disabled optgroup + // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled + // All such elements have a "form" property. + if ( elem.parentNode && elem.disabled === false ) { + + // Option elements defer to a parent optgroup if present + if ( "label" in elem ) { + if ( "label" in elem.parentNode ) { + return elem.parentNode.disabled === disabled; + } else { + return elem.disabled === disabled; + } + } + + // Support: IE 6 - 11 + // Use the isDisabled shortcut property to check for disabled fieldset ancestors + return elem.isDisabled === disabled || + + // Where there is no isDisabled, check manually + /* jshint -W018 */ + elem.isDisabled !== !disabled && + disabledAncestor( elem ) === disabled; + } + + return elem.disabled === disabled; + + // Try to winnow out elements that can't be disabled before trusting the disabled property. + // Some victims get caught in our net (label, legend, menu, track), but it shouldn't + // even exist on them, let alone have a boolean value. + } else if ( "label" in elem ) { + return elem.disabled === disabled; + } + + // Remaining elements are neither :enabled nor :disabled + return false; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction(function( argument ) { + argument = +argument; + return markFunction(function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ (j = matchIndexes[i]) ] ) { + seed[j] = !(matches[j] = seed[j]); + } + } + }); + }); +} + +/** + * Checks a node for validity as a Sizzle context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ +function testContext( context ) { + return context && typeof context.getElementsByTagName !== "undefined" && context; +} + +// Expose support vars for convenience +support = Sizzle.support = {}; + +/** + * Detects XML nodes + * @param {Element|Object} elem An element or a document + * @returns {Boolean} True iff elem is a non-HTML XML node + */ +isXML = Sizzle.isXML = function( elem ) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = elem && (elem.ownerDocument || elem).documentElement; + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var hasCompare, subWindow, + doc = node ? node.ownerDocument || node : preferredDoc; + + // Return early if doc is invalid or already selected + if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Update global variables + document = doc; + docElem = document.documentElement; + documentIsHTML = !isXML( document ); + + // Support: IE 9-11, Edge + // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) + if ( preferredDoc !== document && + (subWindow = document.defaultView) && subWindow.top !== subWindow ) { + + // Support: IE 11, Edge + if ( subWindow.addEventListener ) { + subWindow.addEventListener( "unload", unloadHandler, false ); + + // Support: IE 9 - 10 only + } else if ( subWindow.attachEvent ) { + subWindow.attachEvent( "onunload", unloadHandler ); + } + } + + /* Attributes + ---------------------------------------------------------------------- */ + + // Support: IE<8 + // Verify that getAttribute really returns attributes and not properties + // (excepting IE8 booleans) + support.attributes = assert(function( el ) { + el.className = "i"; + return !el.getAttribute("className"); + }); + + /* getElement(s)By* + ---------------------------------------------------------------------- */ + + // Check if getElementsByTagName("*") returns only elements + support.getElementsByTagName = assert(function( el ) { + el.appendChild( document.createComment("") ); + return !el.getElementsByTagName("*").length; + }); + + // Support: IE<9 + support.getElementsByClassName = rnative.test( document.getElementsByClassName ); + + // Support: IE<10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programmatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert(function( el ) { + docElem.appendChild( el ).id = expando; + return !document.getElementsByName || !document.getElementsByName( expando ).length; + }); + + // ID filter and find + if ( support.getById ) { + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute("id") === attrId; + }; + }; + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var elem = context.getElementById( id ); + return elem ? [ elem ] : []; + } + }; + } else { + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== "undefined" && + elem.getAttributeNode("id"); + return node && node.value === attrId; + }; + }; + + // Support: IE 6 - 7 only + // getElementById is not reliable as a find shortcut + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var node, i, elems, + elem = context.getElementById( id ); + + if ( elem ) { + + // Verify the id attribute + node = elem.getAttributeNode("id"); + if ( node && node.value === id ) { + return [ elem ]; + } + + // Fall back on getElementsByName + elems = context.getElementsByName( id ); + i = 0; + while ( (elem = elems[i++]) ) { + node = elem.getAttributeNode("id"); + if ( node && node.value === id ) { + return [ elem ]; + } + } + } + + return []; + } + }; + } + + // Tag + Expr.find["TAG"] = support.getElementsByTagName ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( tag ); + + // DocumentFragment nodes don't have gEBTN + } else if ( support.qsa ) { + return context.querySelectorAll( tag ); + } + } : + + function( tag, context ) { + var elem, + tmp = [], + i = 0, + // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( (elem = results[i++]) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Class + Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21) + // We allow this because of a bug in IE8/9 that throws an error + // whenever `document.activeElement` is accessed on an iframe + // So, we allow :focus to pass through QSA all the time to avoid the IE error + // See https://bugs.jquery.com/ticket/13378 + rbuggyQSA = []; + + if ( (support.qsa = rnative.test( document.querySelectorAll )) ) { + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert(function( el ) { + // Select is set to empty string on purpose + // This is to test IE's treatment of not explicitly + // setting a boolean content attribute, + // since its presence should be enough + // https://bugs.jquery.com/ticket/12359 + docElem.appendChild( el ).innerHTML = "<a id='" + expando + "'></a>" + + "<select id='" + expando + "-\r\\' msallowcapture=''>" + + "<option selected=''></option></select>"; + + // Support: IE8, Opera 11-12.16 + // Nothing should be selected when empty strings follow ^= or $= or *= + // The test attribute must be unknown in Opera but "safe" for WinRT + // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section + if ( el.querySelectorAll("[msallowcapture^='']").length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } + + // Support: IE8 + // Boolean attributes and "value" are not treated correctly + if ( !el.querySelectorAll("[selected]").length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ + if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { + rbuggyQSA.push("~="); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !el.querySelectorAll(":checked").length ) { + rbuggyQSA.push(":checked"); + } + + // Support: Safari 8+, iOS 8+ + // https://bugs.webkit.org/show_bug.cgi?id=136851 + // In-page `selector#id sibling-combinator selector` fails + if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { + rbuggyQSA.push(".#.+[+~]"); + } + }); + + assert(function( el ) { + el.innerHTML = "<a href='' disabled='disabled'></a>" + + "<select disabled='disabled'><option/></select>"; + + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + var input = document.createElement("input"); + input.setAttribute( "type", "hidden" ); + el.appendChild( input ).setAttribute( "name", "D" ); + + // Support: IE8 + // Enforce case-sensitivity of name attribute + if ( el.querySelectorAll("[name=d]").length ) { + rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( el.querySelectorAll(":enabled").length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: IE9-11+ + // IE's :disabled selector does not pick up the children of disabled fieldsets + docElem.appendChild( el ).disabled = true; + if ( el.querySelectorAll(":disabled").length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Opera 10-11 does not throw on post-comma invalid pseudos + el.querySelectorAll("*,:x"); + rbuggyQSA.push(",.*:"); + }); + } + + if ( (support.matchesSelector = rnative.test( (matches = docElem.matches || + docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector) )) ) { + + assert(function( el ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( el, "*" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( el, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + }); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); + + /* Contains + ---------------------------------------------------------------------- */ + hasCompare = rnative.test( docElem.compareDocumentPosition ); + + // Element contains another + // Purposefully self-exclusive + // As in, an element does not contain itself + contains = hasCompare || rnative.test( docElem.contains ) ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + )); + } : + function( a, b ) { + if ( b ) { + while ( (b = b.parentNode) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = hasCompare ? + function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + // Sort on method existence if only one input has compareDocumentPosition + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } + + // Calculate position if both inputs belong to the same document + compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + // Otherwise we know they are disconnected + 1; + + // Disconnected nodes + if ( compare & 1 || + (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { + + // Choose the first element that is related to our preferred document + if ( a === document || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) { + return -1; + } + if ( b === document || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + } : + function( a, b ) { + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Parentless nodes are either documents or disconnected + if ( !aup || !bup ) { + return a === document ? -1 : + b === document ? 1 : + aup ? -1 : + bup ? 1 : + sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( (cur = cur.parentNode) ) { + ap.unshift( cur ); + } + cur = b; + while ( (cur = cur.parentNode) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[i] === bp[i] ) { + i++; + } + + return i ? + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[i], bp[i] ) : + + // Otherwise nodes in our document sort first + ap[i] === preferredDoc ? -1 : + bp[i] === preferredDoc ? 1 : + 0; + }; + + return document; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + // Make sure that attribute selectors are quoted + expr = expr.replace( rattributeQuotes, "='$1']" ); + + if ( support.matchesSelector && documentIsHTML && + !compilerCache[ expr + " " ] && + ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch (e) {} + } + + return Sizzle( expr, document, null, [ elem ] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + // Set document vars if needed + if ( ( context.ownerDocument || context ) !== document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + // Don't get fooled by Object.prototype properties (jQuery #13807) + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + return val !== undefined ? + val : + support.attributes || !documentIsHTML ? + elem.getAttribute( name ) : + (val = elem.getAttributeNode(name)) && val.specified ? + val.value : + null; +}; + +Sizzle.escape = function( sel ) { + return (sel + "").replace( rcssescape, fcssescape ); +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice( 0 ); + results.sort( sortOrder ); + + if ( hasDuplicate ) { + while ( (elem = results[i++]) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + // Clear input after sorting to release objects + // See https://github.com/jquery/sizzle/pull/225 + sortInput = null; + + return results; +}; + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + // If no nodeType, this is expected to be an array + while ( (node = elem[i++]) ) { + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent for elements + // innerText usage removed for consistency of new lines (jQuery #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[1] = match[1].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape ); + + if ( match[2] === "~=" ) { + match[3] = " " + match[3] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[1] = match[1].toLowerCase(); + + if ( match[1].slice( 0, 3 ) === "nth" ) { + // nth-* requires argument + if ( !match[3] ) { + Sizzle.error( match[0] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); + match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); + + // other types prohibit arguments + } else if ( match[3] ) { + Sizzle.error( match[0] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[6] && match[2]; + + if ( matchExpr["CHILD"].test( match[0] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[3] ) { + match[2] = match[4] || match[5] || ""; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + // Get excess from tokenize (recursively) + (excess = tokenize( unquoted, true )) && + // advance to the next closing parenthesis + (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { + + // excess is a negative index + match[0] = match[0].slice( 0, excess ); + match[2] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeNameSelector ) { + var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { return true; } : + function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && + classCache( className, function( elem ) { + return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" ); + }); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + }; + }, + + "CHILD": function( type, what, argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, context, xml ) { + var cache, uniqueCache, outerCache, node, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType, + diff = false; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( (node = node[ dir ]) ) { + if ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) { + + return false; + } + } + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + + // Seek `elem` from a previously-cached index + + // ...in a gzip-friendly way + node = parent; + outerCache = node[ expando ] || (node[ expando ] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + (outerCache[ node.uniqueID ] = {}); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex && cache[ 2 ]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( (node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + (diff = nodeIndex = 0) || start.pop()) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + } else { + // Use previously-cached element index if available + if ( useCache ) { + // ...in a gzip-friendly way + node = elem; + outerCache = node[ expando ] || (node[ expando ] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + (outerCache[ node.uniqueID ] = {}); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex; + } + + // xml :nth-child(...) + // or :nth-last-child(...) or :nth(-last)?-of-type(...) + if ( diff === false ) { + // Use the same loop as above to seek `elem` from the start + while ( (node = ++nodeIndex && node && node[ dir ] || + (diff = nodeIndex = 0) || start.pop()) ) { + + if ( ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) && + ++diff ) { + + // Cache the index of each encountered element + if ( useCache ) { + outerCache = node[ expando ] || (node[ expando ] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + (outerCache[ node.uniqueID ] = {}); + + uniqueCache[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction(function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf( seed, matched[i] ); + seed[ idx ] = !( matches[ idx ] = matched[i] ); + } + }) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + // Potentially complex pseudos + "not": markFunction(function( selector ) { + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction(function( seed, matches, context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( (elem = unmatched[i]) ) { + seed[i] = !(matches[i] = elem); + } + } + }) : + function( elem, context, xml ) { + input[0] = elem; + matcher( input, null, xml, results ); + // Don't keep the element (issue #299) + input[0] = null; + return !results.pop(); + }; + }), + + "has": markFunction(function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + }), + + "contains": markFunction(function( text ) { + text = text.replace( runescape, funescape ); + return function( elem ) { + return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; + }; + }), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + // lang value must be a valid identifier + if ( !ridentifier.test(lang || "") ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( (elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); + return false; + }; + }), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); + }, + + // Boolean properties + "enabled": createDisabledPseudo( false ), + "disabled": createDisabledPseudo( true ), + + "checked": function( elem ) { + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); + }, + + "selected": function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos["empty"]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + + // Support: IE<8 + // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" + ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" ); + }, + + // Position-in-collection + "first": createPositionalPseudo(function() { + return [ 0 ]; + }), + + "last": createPositionalPseudo(function( matchIndexes, length ) { + return [ length - 1 ]; + }), + + "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + }), + + "even": createPositionalPseudo(function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "odd": createPositionalPseudo(function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }) + } +}; + +Expr.pseudos["nth"] = Expr.pseudos["eq"]; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +tokenize = Sizzle.tokenize = function( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || (match = rcomma.exec( soFar )) ) { + if ( match ) { + // Don't consume trailing commas as valid + soFar = soFar.slice( match[0].length ) || soFar; + } + groups.push( (tokens = []) ); + } + + matched = false; + + // Combinators + if ( (match = rcombinators.exec( soFar )) ) { + matched = match.shift(); + tokens.push({ + value: matched, + // Cast descendant combinators to space + type: match[0].replace( rtrim, " " ) + }); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || + (match = preFilters[ type ]( match ))) ) { + matched = match.shift(); + tokens.push({ + value: matched, + type: type, + matches: match + }); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +}; + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[i].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + skip = combinator.next, + key = skip || dir, + checkNonElements = base && key === "parentNode", + doneName = done++; + + return combinator.first ? + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + return false; + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var oldCache, uniqueCache, outerCache, + newCache = [ dirruns, doneName ]; + + // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching + if ( xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || (elem[ expando ] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ elem.uniqueID ] || (outerCache[ elem.uniqueID ] = {}); + + if ( skip && skip === elem.nodeName.toLowerCase() ) { + elem = elem[ dir ] || elem; + } else if ( (oldCache = uniqueCache[ key ]) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + // Assign to newCache so results back-propagate to previous elements + return (newCache[ 2 ] = oldCache[ 2 ]); + } else { + // Reuse newcache so results back-propagate to previous elements + uniqueCache[ key ] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) { + return true; + } + } + } + } + } + return false; + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[i]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[0]; +} + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[i], results ); + } + return results; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( (elem = unmatched[i]) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction(function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( (elem = temp[i]) ) { + matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) ) { + // Restore matcherIn since elem is not yet a final match + temp.push( (matcherIn[i] = elem) ); + } + } + postFinder( null, (matcherOut = []), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) && + (temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) { + + seed[temp] = !(results[temp] = elem); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + }); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[0].type ], + implicitRelative = leadingRelative || Expr.relative[" "], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + (checkContext = context).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + // Avoid hanging onto element (issue #299) + checkContext = null; + return ret; + } ]; + + for ( ; i < len; i++ ) { + if ( (matcher = Expr.relative[ tokens[i].type ]) ) { + matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; + } else { + matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[j].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + // We must always have either seed elements or outermost context + elems = seed || byElement && Expr.find["TAG"]( "*", outermost ), + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1), + len = elems.length; + + if ( outermost ) { + outermostContext = context === document || context || outermost; + } + + // Add elements passing elementMatchers directly to results + // Support: IE<9, Safari + // Tolerate NodeList properties (IE: "length"; Safari: <number>) matching elements by id + for ( ; i !== len && (elem = elems[i]) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + if ( !context && elem.ownerDocument !== document ) { + setDocument( elem ); + xml = !documentIsHTML; + } + while ( (matcher = elementMatchers[j++]) ) { + if ( matcher( elem, context || document, xml) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + // They will have gone through all possible matchers + if ( (elem = !matcher && elem) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // `i` is now the count of elements visited above, and adding it to `matchedCount` + // makes the latter nonnegative. + matchedCount += i; + + // Apply set filters to unmatched elements + // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` + // equals `i`), unless we didn't visit _any_ elements in the above loop because we have + // no element matchers and no seed. + // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that + // case, which will result in a "00" `matchedCount` that differs from `i` but is also + // numerically zero. + if ( bySet && i !== matchedCount ) { + j = 0; + while ( (matcher = setMatchers[j++]) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !(unmatched[i] || setMatched[i]) ) { + setMatched[i] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + // Generate a function of recursive functions that can be used to check each element + if ( !match ) { + match = tokenize( selector ); + } + i = match.length; + while ( i-- ) { + cached = matcherFromTokens( match[i] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); + + // Save selector and tokenization + cached.selector = selector; + } + return cached; +}; + +/** + * A low-level selection function that works with Sizzle's compiled + * selector functions + * @param {String|Function} selector A selector or a pre-compiled + * selector function built with Sizzle.compile + * @param {Element} context + * @param {Array} [results] + * @param {Array} [seed] A set of elements to match against + */ +select = Sizzle.select = function( selector, context, results, seed ) { + var i, tokens, token, type, find, + compiled = typeof selector === "function" && selector, + match = !seed && tokenize( (selector = compiled.selector || selector) ); + + results = results || []; + + // Try to minimize operations if there is only one selector in the list and no seed + // (the latter of which guarantees us context) + if ( match.length === 1 ) { + + // Reduce context if the leading compound selector is an ID + tokens = match[0] = match[0].slice( 0 ); + if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && + context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[1].type ] ) { + + context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; + if ( !context ) { + return results; + + // Precompiled matchers will still verify ancestry, so step up a level + } else if ( compiled ) { + context = context.parentNode; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[i]; + + // Abort if we hit a combinator + if ( Expr.relative[ (type = token.type) ] ) { + break; + } + if ( (find = Expr.find[ type ]) ) { + // Search, expanding context for leading sibling combinators + if ( (seed = find( + token.matches[0].replace( runescape, funescape ), + rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context + )) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + + // Compile and execute a filtering function if one is not provided + // Provide `match` to avoid retokenization if we modified the selector above + ( compiled || compile( selector, match ) )( + seed, + context, + !documentIsHTML, + results, + !context || rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; +}; + +// One-time assignments + +// Sort stability +support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; + +// Support: Chrome 14-35+ +// Always assume duplicates if they aren't passed to the comparison function +support.detectDuplicates = !!hasDuplicate; + +// Initialize against the default document +setDocument(); + +// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) +// Detached nodes confoundingly follow *each other* +support.sortDetached = assert(function( el ) { + // Should return 1, but returns 4 (following) + return el.compareDocumentPosition( document.createElement("fieldset") ) & 1; +}); + +// Support: IE<8 +// Prevent attribute/property "interpolation" +// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !assert(function( el ) { + el.innerHTML = "<a href='#'></a>"; + return el.firstChild.getAttribute("href") === "#" ; +}) ) { + addHandle( "type|href|height|width", function( elem, name, isXML ) { + if ( !isXML ) { + return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); + } + }); +} + +// Support: IE<9 +// Use defaultValue in place of getAttribute("value") +if ( !support.attributes || !assert(function( el ) { + el.innerHTML = "<input/>"; + el.firstChild.setAttribute( "value", "" ); + return el.firstChild.getAttribute( "value" ) === ""; +}) ) { + addHandle( "value", function( elem, name, isXML ) { + if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { + return elem.defaultValue; + } + }); +} + +// Support: IE<9 +// Use getAttributeNode to fetch booleans when getAttribute lies +if ( !assert(function( el ) { + return el.getAttribute("disabled") == null; +}) ) { + addHandle( booleans, function( elem, name, isXML ) { + var val; + if ( !isXML ) { + return elem[ name ] === true ? name.toLowerCase() : + (val = elem.getAttributeNode( name )) && val.specified ? + val.value : + null; + } + }); +} + +return Sizzle; + +})( window ); + + + +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; + +// Deprecated +jQuery.expr[ ":" ] = jQuery.expr.pseudos; +jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; +jQuery.escapeSelector = Sizzle.escape; + + + + +var dir = function( elem, dir, until ) { + var matched = [], + truncate = until !== undefined; + + while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { + if ( elem.nodeType === 1 ) { + if ( truncate && jQuery( elem ).is( until ) ) { + break; + } + matched.push( elem ); + } + } + return matched; +}; + + +var siblings = function( n, elem ) { + var matched = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + matched.push( n ); + } + } + + return matched; +}; + + +var rneedsContext = jQuery.expr.match.needsContext; + + + +function nodeName( elem, name ) { + + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + +}; +var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); + + + +var risSimple = /^.[^:#\[\.,]*$/; + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + return !!qualifier.call( elem, i, elem ) !== not; + } ); + } + + // Single element + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + } ); + } + + // Arraylike of elements (jQuery, arguments, Array) + if ( typeof qualifier !== "string" ) { + return jQuery.grep( elements, function( elem ) { + return ( indexOf.call( qualifier, elem ) > -1 ) !== not; + } ); + } + + // Simple selector that can be filtered directly, removing non-Elements + if ( risSimple.test( qualifier ) ) { + return jQuery.filter( qualifier, elements, not ); + } + + // Complex selector, compare the two sets, removing non-Elements + qualifier = jQuery.filter( qualifier, elements ); + return jQuery.grep( elements, function( elem ) { + return ( indexOf.call( qualifier, elem ) > -1 ) !== not && elem.nodeType === 1; + } ); +} + +jQuery.filter = function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + if ( elems.length === 1 && elem.nodeType === 1 ) { + return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; + } + + return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + } ) ); +}; + +jQuery.fn.extend( { + find: function( selector ) { + var i, ret, + len = this.length, + self = this; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter( function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + } ) ); + } + + ret = this.pushStack( [] ); + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + return len > 1 ? jQuery.uniqueSort( ret ) : ret; + }, + filter: function( selector ) { + return this.pushStack( winnow( this, selector || [], false ) ); + }, + not: function( selector ) { + return this.pushStack( winnow( this, selector || [], true ) ); + }, + is: function( selector ) { + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + } +} ); + + +// Initialize a jQuery object + + +// A central reference to the root jQuery(document) +var rootjQuery, + + // A simple way to check for HTML strings + // Prioritize #id over <tag> to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + // Shortcut simple #id case for speed + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, + + init = jQuery.fn.init = function( selector, context, root ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Method init() accepts an alternate rootjQuery + // so migrate can support jQuery.sub (gh-2101) + root = root || rootjQuery; + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector[ 0 ] === "<" && + selector[ selector.length - 1 ] === ">" && + selector.length >= 3 ) { + + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && ( match[ 1 ] || !context ) ) { + + // HANDLE: $(html) -> $(array) + if ( match[ 1 ] ) { + context = context instanceof jQuery ? context[ 0 ] : context; + + // Option to run scripts is true for back-compat + // Intentionally let the error be thrown if parseHTML is not present + jQuery.merge( this, jQuery.parseHTML( + match[ 1 ], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + + // Properties of context are called as methods if possible + if ( jQuery.isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[ 2 ] ); + + if ( elem ) { + + // Inject the element directly into the jQuery object + this[ 0 ] = elem; + this.length = 1; + } + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || root ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this[ 0 ] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return root.ready !== undefined ? + root.ready( selector ) : + + // Execute immediately if ready is not present + selector( jQuery ); + } + + return jQuery.makeArray( selector, this ); + }; + +// Give the init function the jQuery prototype for later instantiation +init.prototype = jQuery.fn; + +// Initialize central reference +rootjQuery = jQuery( document ); + + +var rparentsprev = /^(?:parents|prev(?:Until|All))/, + + // Methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend( { + has: function( target ) { + var targets = jQuery( target, this ), + l = targets.length; + + return this.filter( function() { + var i = 0; + for ( ; i < l; i++ ) { + if ( jQuery.contains( this, targets[ i ] ) ) { + return true; + } + } + } ); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + matched = [], + targets = typeof selectors !== "string" && jQuery( selectors ); + + // Positional selectors never match, since there's no _selection_ context + if ( !rneedsContext.test( selectors ) ) { + for ( ; i < l; i++ ) { + for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { + + // Always skip document fragments + if ( cur.nodeType < 11 && ( targets ? + targets.index( cur ) > -1 : + + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector( cur, selectors ) ) ) { + + matched.push( cur ); + break; + } + } + } + } + + return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); + }, + + // Determine the position of an element within the set + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; + } + + // Index in selector + if ( typeof elem === "string" ) { + return indexOf.call( jQuery( elem ), this[ 0 ] ); + } + + // Locate the position of the desired element + return indexOf.call( this, + + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[ 0 ] : elem + ); + }, + + add: function( selector, context ) { + return this.pushStack( + jQuery.uniqueSort( + jQuery.merge( this.get(), jQuery( selector, context ) ) + ) + ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter( selector ) + ); + } +} ); + +function sibling( cur, dir ) { + while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} + return cur; +} + +jQuery.each( { + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return siblings( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return siblings( elem.firstChild ); + }, + contents: function( elem ) { + if ( nodeName( elem, "iframe" ) ) { + return elem.contentDocument; + } + + // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only + // Treat the template element as a regular one in browsers that + // don't support it. + if ( nodeName( elem, "template" ) ) { + elem = elem.content || elem; + } + + return jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var matched = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + matched = jQuery.filter( selector, matched ); + } + + if ( this.length > 1 ) { + + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + jQuery.uniqueSort( matched ); + } + + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + matched.reverse(); + } + } + + return this.pushStack( matched ); + }; +} ); +var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); + + + +// Convert String-formatted options into Object-formatted ones +function createOptions( options ) { + var object = {}; + jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { + object[ flag ] = true; + } ); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + createOptions( options ) : + jQuery.extend( {}, options ); + + var // Flag to know if list is currently firing + firing, + + // Last fire value for non-forgettable lists + memory, + + // Flag to know if list was already fired + fired, + + // Flag to prevent firing + locked, + + // Actual callback list + list = [], + + // Queue of execution data for repeatable lists + queue = [], + + // Index of currently firing callback (modified by add/remove as needed) + firingIndex = -1, + + // Fire callbacks + fire = function() { + + // Enforce single-firing + locked = locked || options.once; + + // Execute callbacks for all pending executions, + // respecting firingIndex overrides and runtime changes + fired = firing = true; + for ( ; queue.length; firingIndex = -1 ) { + memory = queue.shift(); + while ( ++firingIndex < list.length ) { + + // Run callback and check for early termination + if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && + options.stopOnFalse ) { + + // Jump to end and forget the data so .add doesn't re-fire + firingIndex = list.length; + memory = false; + } + } + } + + // Forget the data if we're done with it + if ( !options.memory ) { + memory = false; + } + + firing = false; + + // Clean up if we're done firing for good + if ( locked ) { + + // Keep an empty list if we have data for future add calls + if ( memory ) { + list = []; + + // Otherwise, this object is spent + } else { + list = ""; + } + } + }, + + // Actual Callbacks object + self = { + + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + + // If we have memory from a past run, we should fire after adding + if ( memory && !firing ) { + firingIndex = list.length - 1; + queue.push( memory ); + } + + ( function add( args ) { + jQuery.each( args, function( _, arg ) { + if ( jQuery.isFunction( arg ) ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && jQuery.type( arg ) !== "string" ) { + + // Inspect recursively + add( arg ); + } + } ); + } )( arguments ); + + if ( memory && !firing ) { + fire(); + } + } + return this; + }, + + // Remove a callback from the list + remove: function() { + jQuery.each( arguments, function( _, arg ) { + var index; + while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + + // Handle firing indexes + if ( index <= firingIndex ) { + firingIndex--; + } + } + } ); + return this; + }, + + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? + jQuery.inArray( fn, list ) > -1 : + list.length > 0; + }, + + // Remove all callbacks from the list + empty: function() { + if ( list ) { + list = []; + } + return this; + }, + + // Disable .fire and .add + // Abort any current/pending executions + // Clear all callbacks and values + disable: function() { + locked = queue = []; + list = memory = ""; + return this; + }, + disabled: function() { + return !list; + }, + + // Disable .fire + // Also disable .add unless we have memory (since it would have no effect) + // Abort any pending executions + lock: function() { + locked = queue = []; + if ( !memory && !firing ) { + list = memory = ""; + } + return this; + }, + locked: function() { + return !!locked; + }, + + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( !locked ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + queue.push( args ); + if ( !firing ) { + fire(); + } + } + return this; + }, + + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; + + +function Identity( v ) { + return v; +} +function Thrower( ex ) { + throw ex; +} + +function adoptValue( value, resolve, reject, noValue ) { + var method; + + try { + + // Check for promise aspect first to privilege synchronous behavior + if ( value && jQuery.isFunction( ( method = value.promise ) ) ) { + method.call( value ).done( resolve ).fail( reject ); + + // Other thenables + } else if ( value && jQuery.isFunction( ( method = value.then ) ) ) { + method.call( value, resolve, reject ); + + // Other non-thenables + } else { + + // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: + // * false: [ value ].slice( 0 ) => resolve( value ) + // * true: [ value ].slice( 1 ) => resolve() + resolve.apply( undefined, [ value ].slice( noValue ) ); + } + + // For Promises/A+, convert exceptions into rejections + // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in + // Deferred#then to conditionally suppress rejection. + } catch ( value ) { + + // Support: Android 4.0 only + // Strict mode functions invoked without .call/.apply get global-object context + reject.apply( undefined, [ value ] ); + } +} + +jQuery.extend( { + + Deferred: function( func ) { + var tuples = [ + + // action, add listener, callbacks, + // ... .then handlers, argument index, [final state] + [ "notify", "progress", jQuery.Callbacks( "memory" ), + jQuery.Callbacks( "memory" ), 2 ], + [ "resolve", "done", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 0, "resolved" ], + [ "reject", "fail", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 1, "rejected" ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + "catch": function( fn ) { + return promise.then( null, fn ); + }, + + // Keep pipe for back-compat + pipe: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + + return jQuery.Deferred( function( newDefer ) { + jQuery.each( tuples, function( i, tuple ) { + + // Map tuples (progress, done, fail) to arguments (done, fail, progress) + var fn = jQuery.isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; + + // deferred.progress(function() { bind to newDefer or newDefer.notify }) + // deferred.done(function() { bind to newDefer or newDefer.resolve }) + // deferred.fail(function() { bind to newDefer or newDefer.reject }) + deferred[ tuple[ 1 ] ]( function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && jQuery.isFunction( returned.promise ) ) { + returned.promise() + .progress( newDefer.notify ) + .done( newDefer.resolve ) + .fail( newDefer.reject ); + } else { + newDefer[ tuple[ 0 ] + "With" ]( + this, + fn ? [ returned ] : arguments + ); + } + } ); + } ); + fns = null; + } ).promise(); + }, + then: function( onFulfilled, onRejected, onProgress ) { + var maxDepth = 0; + function resolve( depth, deferred, handler, special ) { + return function() { + var that = this, + args = arguments, + mightThrow = function() { + var returned, then; + + // Support: Promises/A+ section 2.3.3.3.3 + // https://promisesaplus.com/#point-59 + // Ignore double-resolution attempts + if ( depth < maxDepth ) { + return; + } + + returned = handler.apply( that, args ); + + // Support: Promises/A+ section 2.3.1 + // https://promisesaplus.com/#point-48 + if ( returned === deferred.promise() ) { + throw new TypeError( "Thenable self-resolution" ); + } + + // Support: Promises/A+ sections 2.3.3.1, 3.5 + // https://promisesaplus.com/#point-54 + // https://promisesaplus.com/#point-75 + // Retrieve `then` only once + then = returned && + + // Support: Promises/A+ section 2.3.4 + // https://promisesaplus.com/#point-64 + // Only check objects and functions for thenability + ( typeof returned === "object" || + typeof returned === "function" ) && + returned.then; + + // Handle a returned thenable + if ( jQuery.isFunction( then ) ) { + + // Special processors (notify) just wait for resolution + if ( special ) { + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ) + ); + + // Normal processors (resolve) also hook into progress + } else { + + // ...and disregard older resolution values + maxDepth++; + + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ), + resolve( maxDepth, deferred, Identity, + deferred.notifyWith ) + ); + } + + // Handle all other returned values + } else { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Identity ) { + that = undefined; + args = [ returned ]; + } + + // Process the value(s) + // Default process is resolve + ( special || deferred.resolveWith )( that, args ); + } + }, + + // Only normal processors (resolve) catch and reject exceptions + process = special ? + mightThrow : + function() { + try { + mightThrow(); + } catch ( e ) { + + if ( jQuery.Deferred.exceptionHook ) { + jQuery.Deferred.exceptionHook( e, + process.stackTrace ); + } + + // Support: Promises/A+ section 2.3.3.3.4.1 + // https://promisesaplus.com/#point-61 + // Ignore post-resolution exceptions + if ( depth + 1 >= maxDepth ) { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Thrower ) { + that = undefined; + args = [ e ]; + } + + deferred.rejectWith( that, args ); + } + } + }; + + // Support: Promises/A+ section 2.3.3.3.1 + // https://promisesaplus.com/#point-57 + // Re-resolve promises immediately to dodge false rejection from + // subsequent errors + if ( depth ) { + process(); + } else { + + // Call an optional hook to record the stack, in case of exception + // since it's otherwise lost when execution goes async + if ( jQuery.Deferred.getStackHook ) { + process.stackTrace = jQuery.Deferred.getStackHook(); + } + window.setTimeout( process ); + } + }; + } + + return jQuery.Deferred( function( newDefer ) { + + // progress_handlers.add( ... ) + tuples[ 0 ][ 3 ].add( + resolve( + 0, + newDefer, + jQuery.isFunction( onProgress ) ? + onProgress : + Identity, + newDefer.notifyWith + ) + ); + + // fulfilled_handlers.add( ... ) + tuples[ 1 ][ 3 ].add( + resolve( + 0, + newDefer, + jQuery.isFunction( onFulfilled ) ? + onFulfilled : + Identity + ) + ); + + // rejected_handlers.add( ... ) + tuples[ 2 ][ 3 ].add( + resolve( + 0, + newDefer, + jQuery.isFunction( onRejected ) ? + onRejected : + Thrower + ) + ); + } ).promise(); + }, + + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 5 ]; + + // promise.progress = list.add + // promise.done = list.add + // promise.fail = list.add + promise[ tuple[ 1 ] ] = list.add; + + // Handle state + if ( stateString ) { + list.add( + function() { + + // state = "resolved" (i.e., fulfilled) + // state = "rejected" + state = stateString; + }, + + // rejected_callbacks.disable + // fulfilled_callbacks.disable + tuples[ 3 - i ][ 2 ].disable, + + // progress_callbacks.lock + tuples[ 0 ][ 2 ].lock + ); + } + + // progress_handlers.fire + // fulfilled_handlers.fire + // rejected_handlers.fire + list.add( tuple[ 3 ].fire ); + + // deferred.notify = function() { deferred.notifyWith(...) } + // deferred.resolve = function() { deferred.resolveWith(...) } + // deferred.reject = function() { deferred.rejectWith(...) } + deferred[ tuple[ 0 ] ] = function() { + deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); + return this; + }; + + // deferred.notifyWith = list.fireWith + // deferred.resolveWith = list.fireWith + // deferred.rejectWith = list.fireWith + deferred[ tuple[ 0 ] + "With" ] = list.fireWith; + } ); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( singleValue ) { + var + + // count of uncompleted subordinates + remaining = arguments.length, + + // count of unprocessed arguments + i = remaining, + + // subordinate fulfillment data + resolveContexts = Array( i ), + resolveValues = slice.call( arguments ), + + // the master Deferred + master = jQuery.Deferred(), + + // subordinate callback factory + updateFunc = function( i ) { + return function( value ) { + resolveContexts[ i ] = this; + resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if ( !( --remaining ) ) { + master.resolveWith( resolveContexts, resolveValues ); + } + }; + }; + + // Single- and empty arguments are adopted like Promise.resolve + if ( remaining <= 1 ) { + adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject, + !remaining ); + + // Use .then() to unwrap secondary thenables (cf. gh-3000) + if ( master.state() === "pending" || + jQuery.isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { + + return master.then(); + } + } + + // Multiple arguments are aggregated like Promise.all array elements + while ( i-- ) { + adoptValue( resolveValues[ i ], updateFunc( i ), master.reject ); + } + + return master.promise(); + } +} ); + + +// These usually indicate a programmer mistake during development, +// warn about them ASAP rather than swallowing them by default. +var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; + +jQuery.Deferred.exceptionHook = function( error, stack ) { + + // Support: IE 8 - 9 only + // Console exists when dev tools are open, which can happen at any time + if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { + window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); + } +}; + + + + +jQuery.readyException = function( error ) { + window.setTimeout( function() { + throw error; + } ); +}; + + + + +// The deferred used on DOM ready +var readyList = jQuery.Deferred(); + +jQuery.fn.ready = function( fn ) { + + readyList + .then( fn ) + + // Wrap jQuery.readyException in a function so that the lookup + // happens at the time of error handling instead of callback + // registration. + .catch( function( error ) { + jQuery.readyException( error ); + } ); + + return this; +}; + +jQuery.extend( { + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + } +} ); + +jQuery.ready.then = readyList.then; + +// The ready event handler and self cleanup method +function completed() { + document.removeEventListener( "DOMContentLoaded", completed ); + window.removeEventListener( "load", completed ); + jQuery.ready(); +} + +// Catch cases where $(document).ready() is called +// after the browser event has already occurred. +// Support: IE <=9 - 10 only +// Older IE sometimes signals "interactive" too soon +if ( document.readyState === "complete" || + ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { + + // Handle it asynchronously to allow scripts the opportunity to delay ready + window.setTimeout( jQuery.ready ); + +} else { + + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed ); +} + + + + +// Multifunctional method to get and set values of a collection +// The value/s can optionally be executed if it's a function +var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + len = elems.length, + bulk = key == null; + + // Sets many values + if ( jQuery.type( key ) === "object" ) { + chainable = true; + for ( i in key ) { + access( elems, fn, i, key[ i ], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !jQuery.isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < len; i++ ) { + fn( + elems[ i ], key, raw ? + value : + value.call( elems[ i ], i, fn( elems[ i ], key ) ) + ); + } + } + } + + if ( chainable ) { + return elems; + } + + // Gets + if ( bulk ) { + return fn.call( elems ); + } + + return len ? fn( elems[ 0 ], key ) : emptyGet; +}; +var acceptData = function( owner ) { + + // Accepts only: + // - Node + // - Node.ELEMENT_NODE + // - Node.DOCUMENT_NODE + // - Object + // - Any + return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); +}; + + + + +function Data() { + this.expando = jQuery.expando + Data.uid++; +} + +Data.uid = 1; + +Data.prototype = { + + cache: function( owner ) { + + // Check if the owner object already has a cache + var value = owner[ this.expando ]; + + // If not, create one + if ( !value ) { + value = {}; + + // We can accept data for non-element nodes in modern browsers, + // but we should not, see #8335. + // Always return an empty object. + if ( acceptData( owner ) ) { + + // If it is a node unlikely to be stringify-ed or looped over + // use plain assignment + if ( owner.nodeType ) { + owner[ this.expando ] = value; + + // Otherwise secure it in a non-enumerable property + // configurable must be true to allow the property to be + // deleted when data is removed + } else { + Object.defineProperty( owner, this.expando, { + value: value, + configurable: true + } ); + } + } + } + + return value; + }, + set: function( owner, data, value ) { + var prop, + cache = this.cache( owner ); + + // Handle: [ owner, key, value ] args + // Always use camelCase key (gh-2257) + if ( typeof data === "string" ) { + cache[ jQuery.camelCase( data ) ] = value; + + // Handle: [ owner, { properties } ] args + } else { + + // Copy the properties one-by-one to the cache object + for ( prop in data ) { + cache[ jQuery.camelCase( prop ) ] = data[ prop ]; + } + } + return cache; + }, + get: function( owner, key ) { + return key === undefined ? + this.cache( owner ) : + + // Always use camelCase key (gh-2257) + owner[ this.expando ] && owner[ this.expando ][ jQuery.camelCase( key ) ]; + }, + access: function( owner, key, value ) { + + // In cases where either: + // + // 1. No key was specified + // 2. A string key was specified, but no value provided + // + // Take the "read" path and allow the get method to determine + // which value to return, respectively either: + // + // 1. The entire cache object + // 2. The data stored at the key + // + if ( key === undefined || + ( ( key && typeof key === "string" ) && value === undefined ) ) { + + return this.get( owner, key ); + } + + // When the key is not a string, or both a key and value + // are specified, set or extend (existing objects) with either: + // + // 1. An object of properties + // 2. A key and value + // + this.set( owner, key, value ); + + // Since the "set" path can have two possible entry points + // return the expected data based on which path was taken[*] + return value !== undefined ? value : key; + }, + remove: function( owner, key ) { + var i, + cache = owner[ this.expando ]; + + if ( cache === undefined ) { + return; + } + + if ( key !== undefined ) { + + // Support array or space separated string of keys + if ( Array.isArray( key ) ) { + + // If key is an array of keys... + // We always set camelCase keys, so remove that. + key = key.map( jQuery.camelCase ); + } else { + key = jQuery.camelCase( key ); + + // If a key with the spaces exists, use it. + // Otherwise, create an array by matching non-whitespace + key = key in cache ? + [ key ] : + ( key.match( rnothtmlwhite ) || [] ); + } + + i = key.length; + + while ( i-- ) { + delete cache[ key[ i ] ]; + } + } + + // Remove the expando if there's no more data + if ( key === undefined || jQuery.isEmptyObject( cache ) ) { + + // Support: Chrome <=35 - 45 + // Webkit & Blink performance suffers when deleting properties + // from DOM nodes, so set to undefined instead + // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) + if ( owner.nodeType ) { + owner[ this.expando ] = undefined; + } else { + delete owner[ this.expando ]; + } + } + }, + hasData: function( owner ) { + var cache = owner[ this.expando ]; + return cache !== undefined && !jQuery.isEmptyObject( cache ); + } +}; +var dataPriv = new Data(); + +var dataUser = new Data(); + + + +// Implementation Summary +// +// 1. Enforce API surface and semantic compatibility with 1.9.x branch +// 2. Improve the module's maintainability by reducing the storage +// paths to a single mechanism. +// 3. Use the same single mechanism to support "private" and "user" data. +// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) +// 5. Avoid exposing implementation details on user objects (eg. expando properties) +// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 + +var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + rmultiDash = /[A-Z]/g; + +function getData( data ) { + if ( data === "true" ) { + return true; + } + + if ( data === "false" ) { + return false; + } + + if ( data === "null" ) { + return null; + } + + // Only convert to a number if it doesn't change the string + if ( data === +data + "" ) { + return +data; + } + + if ( rbrace.test( data ) ) { + return JSON.parse( data ); + } + + return data; +} + +function dataAttr( elem, key, data ) { + var name; + + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = getData( data ); + } catch ( e ) {} + + // Make sure we set the data so it isn't changed later + dataUser.set( elem, key, data ); + } else { + data = undefined; + } + } + return data; +} + +jQuery.extend( { + hasData: function( elem ) { + return dataUser.hasData( elem ) || dataPriv.hasData( elem ); + }, + + data: function( elem, name, data ) { + return dataUser.access( elem, name, data ); + }, + + removeData: function( elem, name ) { + dataUser.remove( elem, name ); + }, + + // TODO: Now that all calls to _data and _removeData have been replaced + // with direct calls to dataPriv methods, these can be deprecated. + _data: function( elem, name, data ) { + return dataPriv.access( elem, name, data ); + }, + + _removeData: function( elem, name ) { + dataPriv.remove( elem, name ); + } +} ); + +jQuery.fn.extend( { + data: function( key, value ) { + var i, name, data, + elem = this[ 0 ], + attrs = elem && elem.attributes; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = dataUser.get( elem ); + + if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { + i = attrs.length; + while ( i-- ) { + + // Support: IE 11 only + // The attrs elements can be null (#14894) + if ( attrs[ i ] ) { + name = attrs[ i ].name; + if ( name.indexOf( "data-" ) === 0 ) { + name = jQuery.camelCase( name.slice( 5 ) ); + dataAttr( elem, name, data[ name ] ); + } + } + } + dataPriv.set( elem, "hasDataAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each( function() { + dataUser.set( this, key ); + } ); + } + + return access( this, function( value ) { + var data; + + // The calling jQuery object (element matches) is not empty + // (and therefore has an element appears at this[ 0 ]) and the + // `value` parameter was not undefined. An empty jQuery object + // will result in `undefined` for elem = this[ 0 ] which will + // throw an exception if an attempt to read a data cache is made. + if ( elem && value === undefined ) { + + // Attempt to get data from the cache + // The key will always be camelCased in Data + data = dataUser.get( elem, key ); + if ( data !== undefined ) { + return data; + } + + // Attempt to "discover" the data in + // HTML5 custom data-* attrs + data = dataAttr( elem, key ); + if ( data !== undefined ) { + return data; + } + + // We tried really hard, but the data doesn't exist. + return; + } + + // Set the data... + this.each( function() { + + // We always store the camelCased key + dataUser.set( this, key, value ); + } ); + }, null, value, arguments.length > 1, null, true ); + }, + + removeData: function( key ) { + return this.each( function() { + dataUser.remove( this, key ); + } ); + } +} ); + + +jQuery.extend( { + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = dataPriv.get( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || Array.isArray( data ) ) { + queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // Clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // Not public - generate a queueHooks object, or return the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { + empty: jQuery.Callbacks( "once memory" ).add( function() { + dataPriv.remove( elem, [ type + "queue", key ] ); + } ) + } ); + } +} ); + +jQuery.fn.extend( { + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[ 0 ], type ); + } + + return data === undefined ? + this : + this.each( function() { + var queue = jQuery.queue( this, type, data ); + + // Ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + } ); + }, + dequeue: function( type ) { + return this.each( function() { + jQuery.dequeue( this, type ); + } ); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while ( i-- ) { + tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +} ); +var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; + +var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); + + +var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; + +var isHiddenWithinTree = function( elem, el ) { + + // isHiddenWithinTree might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + + // Inline style trumps all + return elem.style.display === "none" || + elem.style.display === "" && + + // Otherwise, check computed style + // Support: Firefox <=43 - 45 + // Disconnected elements can have computed display: none, so first confirm that elem is + // in the document. + jQuery.contains( elem.ownerDocument, elem ) && + + jQuery.css( elem, "display" ) === "none"; + }; + +var swap = function( elem, options, callback, args ) { + var ret, name, + old = {}; + + // Remember the old values, and insert the new ones + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.apply( elem, args || [] ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; +}; + + + + +function adjustCSS( elem, prop, valueParts, tween ) { + var adjusted, + scale = 1, + maxIterations = 20, + currentValue = tween ? + function() { + return tween.cur(); + } : + function() { + return jQuery.css( elem, prop, "" ); + }, + initial = currentValue(), + unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), + + // Starting value computation is required for potential unit mismatches + initialInUnit = ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && + rcssNum.exec( jQuery.css( elem, prop ) ); + + if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { + + // Trust units reported by jQuery.css + unit = unit || initialInUnit[ 3 ]; + + // Make sure we update the tween properties later on + valueParts = valueParts || []; + + // Iteratively approximate from a nonzero starting point + initialInUnit = +initial || 1; + + do { + + // If previous iteration zeroed out, double until we get *something*. + // Use string for doubling so we don't accidentally see scale as unchanged below + scale = scale || ".5"; + + // Adjust and apply + initialInUnit = initialInUnit / scale; + jQuery.style( elem, prop, initialInUnit + unit ); + + // Update scale, tolerating zero or NaN from tween.cur() + // Break the loop if scale is unchanged or perfect, or if we've just had enough. + } while ( + scale !== ( scale = currentValue() / initial ) && scale !== 1 && --maxIterations + ); + } + + if ( valueParts ) { + initialInUnit = +initialInUnit || +initial || 0; + + // Apply relative offset (+=/-=) if specified + adjusted = valueParts[ 1 ] ? + initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : + +valueParts[ 2 ]; + if ( tween ) { + tween.unit = unit; + tween.start = initialInUnit; + tween.end = adjusted; + } + } + return adjusted; +} + + +var defaultDisplayMap = {}; + +function getDefaultDisplay( elem ) { + var temp, + doc = elem.ownerDocument, + nodeName = elem.nodeName, + display = defaultDisplayMap[ nodeName ]; + + if ( display ) { + return display; + } + + temp = doc.body.appendChild( doc.createElement( nodeName ) ); + display = jQuery.css( temp, "display" ); + + temp.parentNode.removeChild( temp ); + + if ( display === "none" ) { + display = "block"; + } + defaultDisplayMap[ nodeName ] = display; + + return display; +} + +function showHide( elements, show ) { + var display, elem, + values = [], + index = 0, + length = elements.length; + + // Determine new display value for elements that need to change + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + + display = elem.style.display; + if ( show ) { + + // Since we force visibility upon cascade-hidden elements, an immediate (and slow) + // check is required in this first loop unless we have a nonempty display value (either + // inline or about-to-be-restored) + if ( display === "none" ) { + values[ index ] = dataPriv.get( elem, "display" ) || null; + if ( !values[ index ] ) { + elem.style.display = ""; + } + } + if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { + values[ index ] = getDefaultDisplay( elem ); + } + } else { + if ( display !== "none" ) { + values[ index ] = "none"; + + // Remember what we're overwriting + dataPriv.set( elem, "display", display ); + } + } + } + + // Set the display of the elements in a second loop to avoid constant reflow + for ( index = 0; index < length; index++ ) { + if ( values[ index ] != null ) { + elements[ index ].style.display = values[ index ]; + } + } + + return elements; +} + +jQuery.fn.extend( { + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state ) { + if ( typeof state === "boolean" ) { + return state ? this.show() : this.hide(); + } + + return this.each( function() { + if ( isHiddenWithinTree( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + } ); + } +} ); +var rcheckableType = ( /^(?:checkbox|radio)$/i ); + +var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]+)/i ); + +var rscriptType = ( /^$|\/(?:java|ecma)script/i ); + + + +// We have to close these tags to support XHTML (#13200) +var wrapMap = { + + // Support: IE <=9 only + option: [ 1, "<select multiple='multiple'>", "</select>" ], + + // XHTML parsers do not magically insert elements in the + // same way that tag soup parsers do. So we cannot shorten + // this by omitting <tbody> or other required elements. + thead: [ 1, "<table>", "</table>" ], + col: [ 2, "<table><colgroup>", "</colgroup></table>" ], + tr: [ 2, "<table><tbody>", "</tbody></table>" ], + td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ], + + _default: [ 0, "", "" ] +}; + +// Support: IE <=9 only +wrapMap.optgroup = wrapMap.option; + +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + + +function getAll( context, tag ) { + + // Support: IE <=9 - 11 only + // Use typeof to avoid zero-argument method invocation on host objects (#15151) + var ret; + + if ( typeof context.getElementsByTagName !== "undefined" ) { + ret = context.getElementsByTagName( tag || "*" ); + + } else if ( typeof context.querySelectorAll !== "undefined" ) { + ret = context.querySelectorAll( tag || "*" ); + + } else { + ret = []; + } + + if ( tag === undefined || tag && nodeName( context, tag ) ) { + return jQuery.merge( [ context ], ret ); + } + + return ret; +} + + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + dataPriv.set( + elems[ i ], + "globalEval", + !refElements || dataPriv.get( refElements[ i ], "globalEval" ) + ); + } +} + + +var rhtml = /<|&#?\w+;/; + +function buildFragment( elems, context, scripts, selection, ignored ) { + var elem, tmp, tag, wrap, contains, j, + fragment = context.createDocumentFragment(), + nodes = [], + i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( jQuery.type( elem ) === "object" ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; + + // Descend through wrappers to the right content + j = wrap[ 0 ]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, tmp.childNodes ); + + // Remember the top-level container + tmp = fragment.firstChild; + + // Ensure the created nodes are orphaned (#12392) + tmp.textContent = ""; + } + } + } + + // Remove wrapper from fragment + fragment.textContent = ""; + + i = 0; + while ( ( elem = nodes[ i++ ] ) ) { + + // Skip elements already in the context collection (trac-4087) + if ( selection && jQuery.inArray( elem, selection ) > -1 ) { + if ( ignored ) { + ignored.push( elem ); + } + continue; + } + + contains = jQuery.contains( elem.ownerDocument, elem ); + + // Append to fragment + tmp = getAll( fragment.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( contains ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( ( elem = tmp[ j++ ] ) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + return fragment; +} + + +( function() { + var fragment = document.createDocumentFragment(), + div = fragment.appendChild( document.createElement( "div" ) ), + input = document.createElement( "input" ); + + // Support: Android 4.0 - 4.3 only + // Check state lost if the name is set (#11217) + // Support: Windows Web Apps (WWA) + // `name` and `type` must use .setAttribute for WWA (#14901) + input.setAttribute( "type", "radio" ); + input.setAttribute( "checked", "checked" ); + input.setAttribute( "name", "t" ); + + div.appendChild( input ); + + // Support: Android <=4.1 only + // Older WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE <=11 only + // Make sure textarea (and checkbox) defaultValue is properly cloned + div.innerHTML = "<textarea>x</textarea>"; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; +} )(); +var documentElement = document.documentElement; + + + +var + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +// Support: IE <=9 only +// See #13393 for more info +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +function on( elem, types, selector, data, fn, one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + on( elem, type, selector, data, types[ type ], one ); + } + return elem; + } + + if ( data == null && fn == null ) { + + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return elem; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return elem.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + } ); +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + + var handleObjIn, eventHandle, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.get( elem ); + + // Don't attach events to noData or text/comment nodes (but allow plain objects) + if ( !elemData ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Ensure that invalid selectors throw exceptions at attach time + // Evaluate against documentElement in case elem is a non-element node (e.g., document) + if ( selector ) { + jQuery.find.matchesSelector( documentElement, selector ); + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !( events = elemData.events ) ) { + events = elemData.events = {}; + } + if ( !( eventHandle = elemData.handle ) ) { + eventHandle = elemData.handle = function( e ) { + + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? + jQuery.event.dispatch.apply( elem, arguments ) : undefined; + }; + } + + // Handle multiple events separated by a space + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend( { + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join( "." ) + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !( handlers = events[ type ] ) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener if the special events handler returns false + if ( !special.setup || + special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var j, origCount, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); + + if ( !elemData || !( events = elemData.events ) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[ 2 ] && + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || + selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || + special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove data and the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + dataPriv.remove( elem, "handle events" ); + } + }, + + dispatch: function( nativeEvent ) { + + // Make a writable jQuery.Event from the native event object + var event = jQuery.event.fix( nativeEvent ); + + var i, j, ret, matched, handleObj, handlerQueue, + args = new Array( arguments.length ), + handlers = ( dataPriv.get( this, "events" ) || {} )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[ 0 ] = event; + + for ( i = 1; i < arguments.length; i++ ) { + args[ i ] = arguments[ i ]; + } + + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( ( handleObj = matched.handlers[ j++ ] ) && + !event.isImmediatePropagationStopped() ) { + + // Triggered event must either 1) have no namespace, or 2) have namespace(s) + // a subset or equal to those in the bound event (both can have no namespace). + if ( !event.rnamespace || event.rnamespace.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || + handleObj.handler ).apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( ( event.result = ret ) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var i, handleObj, sel, matchedHandlers, matchedSelectors, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + if ( delegateCount && + + // Support: IE <=9 + // Black-hole SVG <use> instance trees (trac-13180) + cur.nodeType && + + // Support: Firefox <=42 + // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) + // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click + // Support: IE 11 only + // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) + !( event.type === "click" && event.button >= 1 ) ) { + + for ( ; cur !== this; cur = cur.parentNode || this ) { + + // Don't check non-elements (#13208) + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { + matchedHandlers = []; + matchedSelectors = {}; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matchedSelectors[ sel ] === undefined ) { + matchedSelectors[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) > -1 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matchedSelectors[ sel ] ) { + matchedHandlers.push( handleObj ); + } + } + if ( matchedHandlers.length ) { + handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); + } + } + } + } + + // Add the remaining (directly-bound) handlers + cur = this; + if ( delegateCount < handlers.length ) { + handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); + } + + return handlerQueue; + }, + + addProp: function( name, hook ) { + Object.defineProperty( jQuery.Event.prototype, name, { + enumerable: true, + configurable: true, + + get: jQuery.isFunction( hook ) ? + function() { + if ( this.originalEvent ) { + return hook( this.originalEvent ); + } + } : + function() { + if ( this.originalEvent ) { + return this.originalEvent[ name ]; + } + }, + + set: function( value ) { + Object.defineProperty( this, name, { + enumerable: true, + configurable: true, + writable: true, + value: value + } ); + } + } ); + }, + + fix: function( originalEvent ) { + return originalEvent[ jQuery.expando ] ? + originalEvent : + new jQuery.Event( originalEvent ); + }, + + special: { + load: { + + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + focus: { + + // Fire native event if possible so blur/focus sequence is correct + trigger: function() { + if ( this !== safeActiveElement() && this.focus ) { + this.focus(); + return false; + } + }, + delegateType: "focusin" + }, + blur: { + trigger: function() { + if ( this === safeActiveElement() && this.blur ) { + this.blur(); + return false; + } + }, + delegateType: "focusout" + }, + click: { + + // For checkbox, fire native event so checked state will be right + trigger: function() { + if ( this.type === "checkbox" && this.click && nodeName( this, "input" ) ) { + this.click(); + return false; + } + }, + + // For cross-browser consistency, don't fire native .click() on links + _default: function( event ) { + return nodeName( event.target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Support: Firefox 20+ + // Firefox doesn't alert if the returnValue field is not set. + if ( event.result !== undefined && event.originalEvent ) { + event.originalEvent.returnValue = event.result; + } + } + } + } +}; + +jQuery.removeEvent = function( elem, type, handle ) { + + // This "if" is needed for plain objects + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle ); + } +}; + +jQuery.Event = function( src, props ) { + + // Allow instantiation without the 'new' keyword + if ( !( this instanceof jQuery.Event ) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = src.defaultPrevented || + src.defaultPrevented === undefined && + + // Support: Android <=2.3 only + src.returnValue === false ? + returnTrue : + returnFalse; + + // Create target properties + // Support: Safari <=6 - 7 only + // Target should not be a text node (#504, #13143) + this.target = ( src.target && src.target.nodeType === 3 ) ? + src.target.parentNode : + src.target; + + this.currentTarget = src.currentTarget; + this.relatedTarget = src.relatedTarget; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + constructor: jQuery.Event, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + isSimulated: false, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + + if ( e && !this.isSimulated ) { + e.preventDefault(); + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopPropagation(); + } + }, + stopImmediatePropagation: function() { + var e = this.originalEvent; + + this.isImmediatePropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopImmediatePropagation(); + } + + this.stopPropagation(); + } +}; + +// Includes all common event props including KeyEvent and MouseEvent specific props +jQuery.each( { + altKey: true, + bubbles: true, + cancelable: true, + changedTouches: true, + ctrlKey: true, + detail: true, + eventPhase: true, + metaKey: true, + pageX: true, + pageY: true, + shiftKey: true, + view: true, + "char": true, + charCode: true, + key: true, + keyCode: true, + button: true, + buttons: true, + clientX: true, + clientY: true, + offsetX: true, + offsetY: true, + pointerId: true, + pointerType: true, + screenX: true, + screenY: true, + targetTouches: true, + toElement: true, + touches: true, + + which: function( event ) { + var button = event.button; + + // Add which for key events + if ( event.which == null && rkeyEvent.test( event.type ) ) { + return event.charCode != null ? event.charCode : event.keyCode; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) { + if ( button & 1 ) { + return 1; + } + + if ( button & 2 ) { + return 3; + } + + if ( button & 4 ) { + return 2; + } + + return 0; + } + + return event.which; + } +}, jQuery.event.addProp ); + +// Create mouseenter/leave events using mouseover/out and event-time checks +// so that event delegation works in jQuery. +// Do the same for pointerenter/pointerleave and pointerover/pointerout +// +// Support: Safari 7 only +// Safari sends mouseenter too often; see: +// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 +// for the description of the bug (it existed in older Chrome versions as well). +jQuery.each( { + mouseenter: "mouseover", + mouseleave: "mouseout", + pointerenter: "pointerover", + pointerleave: "pointerout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mouseenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +} ); + +jQuery.fn.extend( { + + on: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn ); + }, + one: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? + handleObj.origType + "." + handleObj.namespace : + handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each( function() { + jQuery.event.remove( this, types, fn, selector ); + } ); + } +} ); + + +var + + /* eslint-disable max-len */ + + // See https://github.com/eslint/eslint/issues/3229 + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi, + + /* eslint-enable */ + + // Support: IE <=10 - 11, Edge 12 - 13 + // In IE/Edge using regex groups here causes severe slowdowns. + // See https://connect.microsoft.com/IE/feedback/details/1736512/ + rnoInnerhtml = /<script|<style|<link/i, + + // checked="checked" or checked + rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, + rscriptTypeMasked = /^true\/(.*)/, + rcleanScript = /^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g; + +// Prefer a tbody over its parent table for containing new rows +function manipulationTarget( elem, content ) { + if ( nodeName( elem, "table" ) && + nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { + + return jQuery( ">tbody", elem )[ 0 ] || elem; + } + + return elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + var match = rscriptTypeMasked.exec( elem.type ); + + if ( match ) { + elem.type = match[ 1 ]; + } else { + elem.removeAttribute( "type" ); + } + + return elem; +} + +function cloneCopyEvent( src, dest ) { + var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events; + + if ( dest.nodeType !== 1 ) { + return; + } + + // 1. Copy private data: events, handlers, etc. + if ( dataPriv.hasData( src ) ) { + pdataOld = dataPriv.access( src ); + pdataCur = dataPriv.set( dest, pdataOld ); + events = pdataOld.events; + + if ( events ) { + delete pdataCur.handle; + pdataCur.events = {}; + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + } + + // 2. Copy user data + if ( dataUser.hasData( src ) ) { + udataOld = dataUser.access( src ); + udataCur = jQuery.extend( {}, udataOld ); + + dataUser.set( dest, udataCur ); + } +} + +// Fix IE bugs, see support tests +function fixInput( src, dest ) { + var nodeName = dest.nodeName.toLowerCase(); + + // Fails to persist the checked state of a cloned checkbox or radio button. + if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + dest.checked = src.checked; + + // Fails to return the selected option to the default selected state when cloning options + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +function domManip( collection, args, callback, ignored ) { + + // Flatten any nested arrays + args = concat.apply( [], args ); + + var fragment, first, scripts, hasScripts, node, doc, + i = 0, + l = collection.length, + iNoClone = l - 1, + value = args[ 0 ], + isFunction = jQuery.isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( isFunction || + ( l > 1 && typeof value === "string" && + !support.checkClone && rchecked.test( value ) ) ) { + return collection.each( function( index ) { + var self = collection.eq( index ); + if ( isFunction ) { + args[ 0 ] = value.call( this, index, self.html() ); + } + domManip( self, args, callback, ignored ); + } ); + } + + if ( l ) { + fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + // Require either new content or an interest in ignored elements to invoke the callback + if ( first || ignored ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item + // instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( collection[ i ], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !dataPriv.access( node, "globalEval" ) && + jQuery.contains( doc, node ) ) { + + if ( node.src ) { + + // Optional AJAX dependency, but won't run scripts if not present + if ( jQuery._evalUrl ) { + jQuery._evalUrl( node.src ); + } + } else { + DOMEval( node.textContent.replace( rcleanScript, "" ), doc ); + } + } + } + } + } + } + + return collection; +} + +function remove( elem, selector, keepData ) { + var node, + nodes = selector ? jQuery.filter( selector, elem ) : elem, + i = 0; + + for ( ; ( node = nodes[ i ] ) != null; i++ ) { + if ( !keepData && node.nodeType === 1 ) { + jQuery.cleanData( getAll( node ) ); + } + + if ( node.parentNode ) { + if ( keepData && jQuery.contains( node.ownerDocument, node ) ) { + setGlobalEval( getAll( node, "script" ) ); + } + node.parentNode.removeChild( node ); + } + } + + return elem; +} + +jQuery.extend( { + htmlPrefilter: function( html ) { + return html.replace( rxhtmlTag, "<$1></$2>" ); + }, + + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var i, l, srcElements, destElements, + clone = elem.cloneNode( true ), + inPage = jQuery.contains( elem.ownerDocument, elem ); + + // Fix IE cloning issues + if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && + !jQuery.isXMLDoc( elem ) ) { + + // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + fixInput( srcElements[ i ], destElements[ i ] ); + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + cloneCopyEvent( srcElements[ i ], destElements[ i ] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + // Return the cloned set + return clone; + }, + + cleanData: function( elems ) { + var data, elem, type, + special = jQuery.event.special, + i = 0; + + for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { + if ( acceptData( elem ) ) { + if ( ( data = elem[ dataPriv.expando ] ) ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataPriv.expando ] = undefined; + } + if ( elem[ dataUser.expando ] ) { + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataUser.expando ] = undefined; + } + } + } + } +} ); + +jQuery.fn.extend( { + detach: function( selector ) { + return remove( this, selector, true ); + }, + + remove: function( selector ) { + return remove( this, selector ); + }, + + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().each( function() { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.textContent = value; + } + } ); + }, null, value, arguments.length ); + }, + + append: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + } ); + }, + + prepend: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + } ); + }, + + before: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + } ); + }, + + after: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + } ); + }, + + empty: function() { + var elem, + i = 0; + + for ( ; ( elem = this[ i ] ) != null; i++ ) { + if ( elem.nodeType === 1 ) { + + // Prevent memory leaks + jQuery.cleanData( getAll( elem, false ) ); + + // Remove any remaining nodes + elem.textContent = ""; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function() { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + } ); + }, + + html: function( value ) { + return access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined && elem.nodeType === 1 ) { + return elem.innerHTML; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { + + value = jQuery.htmlPrefilter( value ); + + try { + for ( ; i < l; i++ ) { + elem = this[ i ] || {}; + + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch ( e ) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var ignored = []; + + // Make the changes, replacing each non-ignored context element with the new content + return domManip( this, arguments, function( elem ) { + var parent = this.parentNode; + + if ( jQuery.inArray( this, ignored ) < 0 ) { + jQuery.cleanData( getAll( this ) ); + if ( parent ) { + parent.replaceChild( elem, this ); + } + } + + // Force callback invocation + }, ignored ); + } +} ); + +jQuery.each( { + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1, + i = 0; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone( true ); + jQuery( insert[ i ] )[ original ]( elems ); + + // Support: Android <=4.0 only, PhantomJS 1 only + // .get() because push.apply(_, arraylike) throws on ancient WebKit + push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +} ); +var rmargin = ( /^margin/ ); + +var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); + +var getStyles = function( elem ) { + + // Support: IE <=11 only, Firefox <=30 (#15098, #14150) + // IE throws on elements created in popups + // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" + var view = elem.ownerDocument.defaultView; + + if ( !view || !view.opener ) { + view = window; + } + + return view.getComputedStyle( elem ); + }; + + + +( function() { + + // Executing both pixelPosition & boxSizingReliable tests require only one layout + // so they're executed at the same time to save the second computation. + function computeStyleTests() { + + // This is a singleton, we need to execute it only once + if ( !div ) { + return; + } + + div.style.cssText = + "box-sizing:border-box;" + + "position:relative;display:block;" + + "margin:auto;border:1px;padding:1px;" + + "top:1%;width:50%"; + div.innerHTML = ""; + documentElement.appendChild( container ); + + var divStyle = window.getComputedStyle( div ); + pixelPositionVal = divStyle.top !== "1%"; + + // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 + reliableMarginLeftVal = divStyle.marginLeft === "2px"; + boxSizingReliableVal = divStyle.width === "4px"; + + // Support: Android 4.0 - 4.3 only + // Some styles come back with percentage values, even though they shouldn't + div.style.marginRight = "50%"; + pixelMarginRightVal = divStyle.marginRight === "4px"; + + documentElement.removeChild( container ); + + // Nullify the div so it wouldn't be stored in the memory and + // it will also be a sign that checks already performed + div = null; + } + + var pixelPositionVal, boxSizingReliableVal, pixelMarginRightVal, reliableMarginLeftVal, + container = document.createElement( "div" ), + div = document.createElement( "div" ); + + // Finish early in limited (non-browser) environments + if ( !div.style ) { + return; + } + + // Support: IE <=9 - 11 only + // Style of cloned element affects source element cloned (#8908) + div.style.backgroundClip = "content-box"; + div.cloneNode( true ).style.backgroundClip = ""; + support.clearCloneStyle = div.style.backgroundClip === "content-box"; + + container.style.cssText = "border:0;width:8px;height:0;top:0;left:-9999px;" + + "padding:0;margin-top:1px;position:absolute"; + container.appendChild( div ); + + jQuery.extend( support, { + pixelPosition: function() { + computeStyleTests(); + return pixelPositionVal; + }, + boxSizingReliable: function() { + computeStyleTests(); + return boxSizingReliableVal; + }, + pixelMarginRight: function() { + computeStyleTests(); + return pixelMarginRightVal; + }, + reliableMarginLeft: function() { + computeStyleTests(); + return reliableMarginLeftVal; + } + } ); +} )(); + + +function curCSS( elem, name, computed ) { + var width, minWidth, maxWidth, ret, + + // Support: Firefox 51+ + // Retrieving style before computed somehow + // fixes an issue with getting wrong values + // on detached elements + style = elem.style; + + computed = computed || getStyles( elem ); + + // getPropertyValue is needed for: + // .css('filter') (IE 9 only, #12537) + // .css('--customProperty) (#3144) + if ( computed ) { + ret = computed.getPropertyValue( name ) || computed[ name ]; + + if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) { + ret = jQuery.style( elem, name ); + } + + // A tribute to the "awesome hack by Dean Edwards" + // Android Browser returns percentage for some values, + // but width seems to be reliably pixels. + // This is against the CSSOM draft spec: + // https://drafts.csswg.org/cssom/#resolved-values + if ( !support.pixelMarginRight() && rnumnonpx.test( ret ) && rmargin.test( name ) ) { + + // Remember the original values + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + // Put in the new values to get a computed value out + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + // Revert the changed values + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret !== undefined ? + + // Support: IE <=9 - 11 only + // IE returns zIndex value as an integer. + ret + "" : + ret; +} + + +function addGetHookIf( conditionFn, hookFn ) { + + // Define the hook, we'll check on the first run if it's really needed. + return { + get: function() { + if ( conditionFn() ) { + + // Hook not needed (or it's not possible to use it due + // to missing dependency), remove it. + delete this.get; + return; + } + + // Hook needed; redefine it so that the support test is not executed again. + return ( this.get = hookFn ).apply( this, arguments ); + } + }; +} + + +var + + // Swappable if display is none or starts with table + // except "table", "table-cell", or "table-caption" + // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + rcustomProp = /^--/, + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: "0", + fontWeight: "400" + }, + + cssPrefixes = [ "Webkit", "Moz", "ms" ], + emptyStyle = document.createElement( "div" ).style; + +// Return a css property mapped to a potentially vendor prefixed property +function vendorPropName( name ) { + + // Shortcut for names that are not vendor prefixed + if ( name in emptyStyle ) { + return name; + } + + // Check for vendor prefixed names + var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), + i = cssPrefixes.length; + + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in emptyStyle ) { + return name; + } + } +} + +// Return a property mapped along what jQuery.cssProps suggests or to +// a vendor prefixed property. +function finalPropName( name ) { + var ret = jQuery.cssProps[ name ]; + if ( !ret ) { + ret = jQuery.cssProps[ name ] = vendorPropName( name ) || name; + } + return ret; +} + +function setPositiveNumber( elem, value, subtract ) { + + // Any relative (+/-) values have already been + // normalized at this point + var matches = rcssNum.exec( value ); + return matches ? + + // Guard against undefined "subtract", e.g., when used as in cssHooks + Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : + value; +} + +function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) { + var i, + val = 0; + + // If we already have the right measurement, avoid augmentation + if ( extra === ( isBorderBox ? "border" : "content" ) ) { + i = 4; + + // Otherwise initialize for horizontal or vertical properties + } else { + i = name === "width" ? 1 : 0; + } + + for ( ; i < 4; i += 2 ) { + + // Both box models exclude margin, so add it if we want it + if ( extra === "margin" ) { + val += jQuery.css( elem, extra + cssExpand[ i ], true, styles ); + } + + if ( isBorderBox ) { + + // border-box includes padding, so remove it if we want content + if ( extra === "content" ) { + val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + } + + // At this point, extra isn't border nor margin, so remove border + if ( extra !== "margin" ) { + val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } else { + + // At this point, extra isn't content, so add padding + val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + + // At this point, extra isn't content nor padding, so add border + if ( extra !== "padding" ) { + val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } + } + + return val; +} + +function getWidthOrHeight( elem, name, extra ) { + + // Start with computed style + var valueIsBorderBox, + styles = getStyles( elem ), + val = curCSS( elem, name, styles ), + isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + + // Computed unit is not pixels. Stop here and return. + if ( rnumnonpx.test( val ) ) { + return val; + } + + // Check for style in case a browser which returns unreliable values + // for getComputedStyle silently falls back to the reliable elem.style + valueIsBorderBox = isBorderBox && + ( support.boxSizingReliable() || val === elem.style[ name ] ); + + // Fall back to offsetWidth/Height when value is "auto" + // This happens for inline elements with no explicit setting (gh-3571) + if ( val === "auto" ) { + val = elem[ "offset" + name[ 0 ].toUpperCase() + name.slice( 1 ) ]; + } + + // Normalize "", auto, and prepare for extra + val = parseFloat( val ) || 0; + + // Use the active box-sizing model to add/subtract irrelevant styles + return ( val + + augmentWidthOrHeight( + elem, + name, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox, + styles + ) + ) + "px"; +} + +jQuery.extend( { + + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity" ); + return ret === "" ? "1" : ret; + } + } + } + }, + + // Don't automatically add "px" to these possibly-unitless properties + cssNumber: { + "animationIterationCount": true, + "columnCount": true, + "fillOpacity": true, + "flexGrow": true, + "flexShrink": true, + "fontWeight": true, + "lineHeight": true, + "opacity": true, + "order": true, + "orphans": true, + "widows": true, + "zIndex": true, + "zoom": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: { + "float": "cssFloat" + }, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, type, hooks, + origName = jQuery.camelCase( name ), + isCustomProp = rcustomProp.test( name ), + style = elem.style; + + // Make sure that we're working with the right name. We don't + // want to query the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Gets hook for the prefixed version, then unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // Check if we're setting a value + if ( value !== undefined ) { + type = typeof value; + + // Convert "+=" or "-=" to relative numbers (#7345) + if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { + value = adjustCSS( elem, name, ret ); + + // Fixes bug #9237 + type = "number"; + } + + // Make sure that null and NaN values aren't set (#7116) + if ( value == null || value !== value ) { + return; + } + + // If a number was passed in, add the unit (except for certain CSS properties) + if ( type === "number" ) { + value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); + } + + // background-* props affect original clone's values + if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { + style[ name ] = "inherit"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !( "set" in hooks ) || + ( value = hooks.set( elem, value, extra ) ) !== undefined ) { + + if ( isCustomProp ) { + style.setProperty( name, value ); + } else { + style[ name ] = value; + } + } + + } else { + + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && + ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { + + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, extra, styles ) { + var val, num, hooks, + origName = jQuery.camelCase( name ), + isCustomProp = rcustomProp.test( name ); + + // Make sure that we're working with the right name. We don't + // want to modify the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Try prefixed name followed by the unprefixed name + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } + + // Otherwise, if a way to get the computed value exists, use that + if ( val === undefined ) { + val = curCSS( elem, name, styles ); + } + + // Convert "normal" to computed value + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; + } + + // Make numeric if forced or a qualifier was provided and val looks numeric + if ( extra === "" || extra ) { + num = parseFloat( val ); + return extra === true || isFinite( num ) ? num || 0 : val; + } + + return val; + } +} ); + +jQuery.each( [ "height", "width" ], function( i, name ) { + jQuery.cssHooks[ name ] = { + get: function( elem, computed, extra ) { + if ( computed ) { + + // Certain elements can have dimension info if we invisibly show them + // but it must have a current display style that would benefit + return rdisplayswap.test( jQuery.css( elem, "display" ) ) && + + // Support: Safari 8+ + // Table columns in Safari have non-zero offsetWidth & zero + // getBoundingClientRect().width unless display is changed. + // Support: IE <=11 only + // Running getBoundingClientRect on a disconnected node + // in IE throws an error. + ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? + swap( elem, cssShow, function() { + return getWidthOrHeight( elem, name, extra ); + } ) : + getWidthOrHeight( elem, name, extra ); + } + }, + + set: function( elem, value, extra ) { + var matches, + styles = extra && getStyles( elem ), + subtract = extra && augmentWidthOrHeight( + elem, + name, + extra, + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + styles + ); + + // Convert to pixels if value adjustment is needed + if ( subtract && ( matches = rcssNum.exec( value ) ) && + ( matches[ 3 ] || "px" ) !== "px" ) { + + elem.style[ name ] = value; + value = jQuery.css( elem, name ); + } + + return setPositiveNumber( elem, value, subtract ); + } + }; +} ); + +jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, + function( elem, computed ) { + if ( computed ) { + return ( parseFloat( curCSS( elem, "marginLeft" ) ) || + elem.getBoundingClientRect().left - + swap( elem, { marginLeft: 0 }, function() { + return elem.getBoundingClientRect().left; + } ) + ) + "px"; + } + } +); + +// These hooks are used by animate to expand properties +jQuery.each( { + margin: "", + padding: "", + border: "Width" +}, function( prefix, suffix ) { + jQuery.cssHooks[ prefix + suffix ] = { + expand: function( value ) { + var i = 0, + expanded = {}, + + // Assumes a single number if not a string + parts = typeof value === "string" ? value.split( " " ) : [ value ]; + + for ( ; i < 4; i++ ) { + expanded[ prefix + cssExpand[ i ] + suffix ] = + parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; + } + + return expanded; + } + }; + + if ( !rmargin.test( prefix ) ) { + jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; + } +} ); + +jQuery.fn.extend( { + css: function( name, value ) { + return access( this, function( elem, name, value ) { + var styles, len, + map = {}, + i = 0; + + if ( Array.isArray( name ) ) { + styles = getStyles( elem ); + len = name.length; + + for ( ; i < len; i++ ) { + map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); + } + + return map; + } + + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + } +} ); + + +function Tween( elem, options, prop, end, easing ) { + return new Tween.prototype.init( elem, options, prop, end, easing ); +} +jQuery.Tween = Tween; + +Tween.prototype = { + constructor: Tween, + init: function( elem, options, prop, end, easing, unit ) { + this.elem = elem; + this.prop = prop; + this.easing = easing || jQuery.easing._default; + this.options = options; + this.start = this.now = this.cur(); + this.end = end; + this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); + }, + cur: function() { + var hooks = Tween.propHooks[ this.prop ]; + + return hooks && hooks.get ? + hooks.get( this ) : + Tween.propHooks._default.get( this ); + }, + run: function( percent ) { + var eased, + hooks = Tween.propHooks[ this.prop ]; + + if ( this.options.duration ) { + this.pos = eased = jQuery.easing[ this.easing ]( + percent, this.options.duration * percent, 0, 1, this.options.duration + ); + } else { + this.pos = eased = percent; + } + this.now = ( this.end - this.start ) * eased + this.start; + + if ( this.options.step ) { + this.options.step.call( this.elem, this.now, this ); + } + + if ( hooks && hooks.set ) { + hooks.set( this ); + } else { + Tween.propHooks._default.set( this ); + } + return this; + } +}; + +Tween.prototype.init.prototype = Tween.prototype; + +Tween.propHooks = { + _default: { + get: function( tween ) { + var result; + + // Use a property on the element directly when it is not a DOM element, + // or when there is no matching style property that exists. + if ( tween.elem.nodeType !== 1 || + tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { + return tween.elem[ tween.prop ]; + } + + // Passing an empty string as a 3rd parameter to .css will automatically + // attempt a parseFloat and fallback to a string if the parse fails. + // Simple values such as "10px" are parsed to Float; + // complex values such as "rotate(1rad)" are returned as-is. + result = jQuery.css( tween.elem, tween.prop, "" ); + + // Empty strings, null, undefined and "auto" are converted to 0. + return !result || result === "auto" ? 0 : result; + }, + set: function( tween ) { + + // Use step hook for back compat. + // Use cssHook if its there. + // Use .style if available and use plain properties where available. + if ( jQuery.fx.step[ tween.prop ] ) { + jQuery.fx.step[ tween.prop ]( tween ); + } else if ( tween.elem.nodeType === 1 && + ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || + jQuery.cssHooks[ tween.prop ] ) ) { + jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); + } else { + tween.elem[ tween.prop ] = tween.now; + } + } + } +}; + +// Support: IE <=9 only +// Panic based approach to setting things on disconnected nodes +Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { + set: function( tween ) { + if ( tween.elem.nodeType && tween.elem.parentNode ) { + tween.elem[ tween.prop ] = tween.now; + } + } +}; + +jQuery.easing = { + linear: function( p ) { + return p; + }, + swing: function( p ) { + return 0.5 - Math.cos( p * Math.PI ) / 2; + }, + _default: "swing" +}; + +jQuery.fx = Tween.prototype.init; + +// Back compat <1.8 extension point +jQuery.fx.step = {}; + + + + +var + fxNow, inProgress, + rfxtypes = /^(?:toggle|show|hide)$/, + rrun = /queueHooks$/; + +function schedule() { + if ( inProgress ) { + if ( document.hidden === false && window.requestAnimationFrame ) { + window.requestAnimationFrame( schedule ); + } else { + window.setTimeout( schedule, jQuery.fx.interval ); + } + + jQuery.fx.tick(); + } +} + +// Animations created synchronously will run synchronously +function createFxNow() { + window.setTimeout( function() { + fxNow = undefined; + } ); + return ( fxNow = jQuery.now() ); +} + +// Generate parameters to create a standard animation +function genFx( type, includeWidth ) { + var which, + i = 0, + attrs = { height: type }; + + // If we include width, step value is 1 to do all cssExpand values, + // otherwise step value is 2 to skip over Left and Right + includeWidth = includeWidth ? 1 : 0; + for ( ; i < 4; i += 2 - includeWidth ) { + which = cssExpand[ i ]; + attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; + } + + if ( includeWidth ) { + attrs.opacity = attrs.width = type; + } + + return attrs; +} + +function createTween( value, prop, animation ) { + var tween, + collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), + index = 0, + length = collection.length; + for ( ; index < length; index++ ) { + if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { + + // We're done with this property + return tween; + } + } +} + +function defaultPrefilter( elem, props, opts ) { + var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, + isBox = "width" in props || "height" in props, + anim = this, + orig = {}, + style = elem.style, + hidden = elem.nodeType && isHiddenWithinTree( elem ), + dataShow = dataPriv.get( elem, "fxshow" ); + + // Queue-skipping animations hijack the fx hooks + if ( !opts.queue ) { + hooks = jQuery._queueHooks( elem, "fx" ); + if ( hooks.unqueued == null ) { + hooks.unqueued = 0; + oldfire = hooks.empty.fire; + hooks.empty.fire = function() { + if ( !hooks.unqueued ) { + oldfire(); + } + }; + } + hooks.unqueued++; + + anim.always( function() { + + // Ensure the complete handler is called before this completes + anim.always( function() { + hooks.unqueued--; + if ( !jQuery.queue( elem, "fx" ).length ) { + hooks.empty.fire(); + } + } ); + } ); + } + + // Detect show/hide animations + for ( prop in props ) { + value = props[ prop ]; + if ( rfxtypes.test( value ) ) { + delete props[ prop ]; + toggle = toggle || value === "toggle"; + if ( value === ( hidden ? "hide" : "show" ) ) { + + // Pretend to be hidden if this is a "show" and + // there is still data from a stopped show/hide + if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { + hidden = true; + + // Ignore all other no-op show/hide data + } else { + continue; + } + } + orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); + } + } + + // Bail out if this is a no-op like .hide().hide() + propTween = !jQuery.isEmptyObject( props ); + if ( !propTween && jQuery.isEmptyObject( orig ) ) { + return; + } + + // Restrict "overflow" and "display" styles during box animations + if ( isBox && elem.nodeType === 1 ) { + + // Support: IE <=9 - 11, Edge 12 - 13 + // Record all 3 overflow attributes because IE does not infer the shorthand + // from identically-valued overflowX and overflowY + opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; + + // Identify a display type, preferring old show/hide data over the CSS cascade + restoreDisplay = dataShow && dataShow.display; + if ( restoreDisplay == null ) { + restoreDisplay = dataPriv.get( elem, "display" ); + } + display = jQuery.css( elem, "display" ); + if ( display === "none" ) { + if ( restoreDisplay ) { + display = restoreDisplay; + } else { + + // Get nonempty value(s) by temporarily forcing visibility + showHide( [ elem ], true ); + restoreDisplay = elem.style.display || restoreDisplay; + display = jQuery.css( elem, "display" ); + showHide( [ elem ] ); + } + } + + // Animate inline elements as inline-block + if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { + if ( jQuery.css( elem, "float" ) === "none" ) { + + // Restore the original display value at the end of pure show/hide animations + if ( !propTween ) { + anim.done( function() { + style.display = restoreDisplay; + } ); + if ( restoreDisplay == null ) { + display = style.display; + restoreDisplay = display === "none" ? "" : display; + } + } + style.display = "inline-block"; + } + } + } + + if ( opts.overflow ) { + style.overflow = "hidden"; + anim.always( function() { + style.overflow = opts.overflow[ 0 ]; + style.overflowX = opts.overflow[ 1 ]; + style.overflowY = opts.overflow[ 2 ]; + } ); + } + + // Implement show/hide animations + propTween = false; + for ( prop in orig ) { + + // General show/hide setup for this element animation + if ( !propTween ) { + if ( dataShow ) { + if ( "hidden" in dataShow ) { + hidden = dataShow.hidden; + } + } else { + dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); + } + + // Store hidden/visible for toggle so `.stop().toggle()` "reverses" + if ( toggle ) { + dataShow.hidden = !hidden; + } + + // Show elements before animating them + if ( hidden ) { + showHide( [ elem ], true ); + } + + /* eslint-disable no-loop-func */ + + anim.done( function() { + + /* eslint-enable no-loop-func */ + + // The final step of a "hide" animation is actually hiding the element + if ( !hidden ) { + showHide( [ elem ] ); + } + dataPriv.remove( elem, "fxshow" ); + for ( prop in orig ) { + jQuery.style( elem, prop, orig[ prop ] ); + } + } ); + } + + // Per-property setup + propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); + if ( !( prop in dataShow ) ) { + dataShow[ prop ] = propTween.start; + if ( hidden ) { + propTween.end = propTween.start; + propTween.start = 0; + } + } + } +} + +function propFilter( props, specialEasing ) { + var index, name, easing, value, hooks; + + // camelCase, specialEasing and expand cssHook pass + for ( index in props ) { + name = jQuery.camelCase( index ); + easing = specialEasing[ name ]; + value = props[ index ]; + if ( Array.isArray( value ) ) { + easing = value[ 1 ]; + value = props[ index ] = value[ 0 ]; + } + + if ( index !== name ) { + props[ name ] = value; + delete props[ index ]; + } + + hooks = jQuery.cssHooks[ name ]; + if ( hooks && "expand" in hooks ) { + value = hooks.expand( value ); + delete props[ name ]; + + // Not quite $.extend, this won't overwrite existing keys. + // Reusing 'index' because we have the correct "name" + for ( index in value ) { + if ( !( index in props ) ) { + props[ index ] = value[ index ]; + specialEasing[ index ] = easing; + } + } + } else { + specialEasing[ name ] = easing; + } + } +} + +function Animation( elem, properties, options ) { + var result, + stopped, + index = 0, + length = Animation.prefilters.length, + deferred = jQuery.Deferred().always( function() { + + // Don't match elem in the :animated selector + delete tick.elem; + } ), + tick = function() { + if ( stopped ) { + return false; + } + var currentTime = fxNow || createFxNow(), + remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), + + // Support: Android 2.3 only + // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) + temp = remaining / animation.duration || 0, + percent = 1 - temp, + index = 0, + length = animation.tweens.length; + + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( percent ); + } + + deferred.notifyWith( elem, [ animation, percent, remaining ] ); + + // If there's more to do, yield + if ( percent < 1 && length ) { + return remaining; + } + + // If this was an empty animation, synthesize a final progress notification + if ( !length ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + } + + // Resolve the animation and report its conclusion + deferred.resolveWith( elem, [ animation ] ); + return false; + }, + animation = deferred.promise( { + elem: elem, + props: jQuery.extend( {}, properties ), + opts: jQuery.extend( true, { + specialEasing: {}, + easing: jQuery.easing._default + }, options ), + originalProperties: properties, + originalOptions: options, + startTime: fxNow || createFxNow(), + duration: options.duration, + tweens: [], + createTween: function( prop, end ) { + var tween = jQuery.Tween( elem, animation.opts, prop, end, + animation.opts.specialEasing[ prop ] || animation.opts.easing ); + animation.tweens.push( tween ); + return tween; + }, + stop: function( gotoEnd ) { + var index = 0, + + // If we are going to the end, we want to run all the tweens + // otherwise we skip this part + length = gotoEnd ? animation.tweens.length : 0; + if ( stopped ) { + return this; + } + stopped = true; + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( 1 ); + } + + // Resolve when we played the last frame; otherwise, reject + if ( gotoEnd ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + deferred.resolveWith( elem, [ animation, gotoEnd ] ); + } else { + deferred.rejectWith( elem, [ animation, gotoEnd ] ); + } + return this; + } + } ), + props = animation.props; + + propFilter( props, animation.opts.specialEasing ); + + for ( ; index < length; index++ ) { + result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); + if ( result ) { + if ( jQuery.isFunction( result.stop ) ) { + jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = + jQuery.proxy( result.stop, result ); + } + return result; + } + } + + jQuery.map( props, createTween, animation ); + + if ( jQuery.isFunction( animation.opts.start ) ) { + animation.opts.start.call( elem, animation ); + } + + // Attach callbacks from options + animation + .progress( animation.opts.progress ) + .done( animation.opts.done, animation.opts.complete ) + .fail( animation.opts.fail ) + .always( animation.opts.always ); + + jQuery.fx.timer( + jQuery.extend( tick, { + elem: elem, + anim: animation, + queue: animation.opts.queue + } ) + ); + + return animation; +} + +jQuery.Animation = jQuery.extend( Animation, { + + tweeners: { + "*": [ function( prop, value ) { + var tween = this.createTween( prop, value ); + adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); + return tween; + } ] + }, + + tweener: function( props, callback ) { + if ( jQuery.isFunction( props ) ) { + callback = props; + props = [ "*" ]; + } else { + props = props.match( rnothtmlwhite ); + } + + var prop, + index = 0, + length = props.length; + + for ( ; index < length; index++ ) { + prop = props[ index ]; + Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; + Animation.tweeners[ prop ].unshift( callback ); + } + }, + + prefilters: [ defaultPrefilter ], + + prefilter: function( callback, prepend ) { + if ( prepend ) { + Animation.prefilters.unshift( callback ); + } else { + Animation.prefilters.push( callback ); + } + } +} ); + +jQuery.speed = function( speed, easing, fn ) { + var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { + complete: fn || !fn && easing || + jQuery.isFunction( speed ) && speed, + duration: speed, + easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing + }; + + // Go to the end state if fx are off + if ( jQuery.fx.off ) { + opt.duration = 0; + + } else { + if ( typeof opt.duration !== "number" ) { + if ( opt.duration in jQuery.fx.speeds ) { + opt.duration = jQuery.fx.speeds[ opt.duration ]; + + } else { + opt.duration = jQuery.fx.speeds._default; + } + } + } + + // Normalize opt.queue - true/undefined/null -> "fx" + if ( opt.queue == null || opt.queue === true ) { + opt.queue = "fx"; + } + + // Queueing + opt.old = opt.complete; + + opt.complete = function() { + if ( jQuery.isFunction( opt.old ) ) { + opt.old.call( this ); + } + + if ( opt.queue ) { + jQuery.dequeue( this, opt.queue ); + } + }; + + return opt; +}; + +jQuery.fn.extend( { + fadeTo: function( speed, to, easing, callback ) { + + // Show any hidden elements after setting opacity to 0 + return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() + + // Animate to the value specified + .end().animate( { opacity: to }, speed, easing, callback ); + }, + animate: function( prop, speed, easing, callback ) { + var empty = jQuery.isEmptyObject( prop ), + optall = jQuery.speed( speed, easing, callback ), + doAnimation = function() { + + // Operate on a copy of prop so per-property easing won't be lost + var anim = Animation( this, jQuery.extend( {}, prop ), optall ); + + // Empty animations, or finishing resolves immediately + if ( empty || dataPriv.get( this, "finish" ) ) { + anim.stop( true ); + } + }; + doAnimation.finish = doAnimation; + + return empty || optall.queue === false ? + this.each( doAnimation ) : + this.queue( optall.queue, doAnimation ); + }, + stop: function( type, clearQueue, gotoEnd ) { + var stopQueue = function( hooks ) { + var stop = hooks.stop; + delete hooks.stop; + stop( gotoEnd ); + }; + + if ( typeof type !== "string" ) { + gotoEnd = clearQueue; + clearQueue = type; + type = undefined; + } + if ( clearQueue && type !== false ) { + this.queue( type || "fx", [] ); + } + + return this.each( function() { + var dequeue = true, + index = type != null && type + "queueHooks", + timers = jQuery.timers, + data = dataPriv.get( this ); + + if ( index ) { + if ( data[ index ] && data[ index ].stop ) { + stopQueue( data[ index ] ); + } + } else { + for ( index in data ) { + if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { + stopQueue( data[ index ] ); + } + } + } + + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && + ( type == null || timers[ index ].queue === type ) ) { + + timers[ index ].anim.stop( gotoEnd ); + dequeue = false; + timers.splice( index, 1 ); + } + } + + // Start the next in the queue if the last step wasn't forced. + // Timers currently will call their complete callbacks, which + // will dequeue but only if they were gotoEnd. + if ( dequeue || !gotoEnd ) { + jQuery.dequeue( this, type ); + } + } ); + }, + finish: function( type ) { + if ( type !== false ) { + type = type || "fx"; + } + return this.each( function() { + var index, + data = dataPriv.get( this ), + queue = data[ type + "queue" ], + hooks = data[ type + "queueHooks" ], + timers = jQuery.timers, + length = queue ? queue.length : 0; + + // Enable finishing flag on private data + data.finish = true; + + // Empty the queue first + jQuery.queue( this, type, [] ); + + if ( hooks && hooks.stop ) { + hooks.stop.call( this, true ); + } + + // Look for any active animations, and finish them + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && timers[ index ].queue === type ) { + timers[ index ].anim.stop( true ); + timers.splice( index, 1 ); + } + } + + // Look for any animations in the old queue and finish them + for ( index = 0; index < length; index++ ) { + if ( queue[ index ] && queue[ index ].finish ) { + queue[ index ].finish.call( this ); + } + } + + // Turn off finishing flag + delete data.finish; + } ); + } +} ); + +jQuery.each( [ "toggle", "show", "hide" ], function( i, name ) { + var cssFn = jQuery.fn[ name ]; + jQuery.fn[ name ] = function( speed, easing, callback ) { + return speed == null || typeof speed === "boolean" ? + cssFn.apply( this, arguments ) : + this.animate( genFx( name, true ), speed, easing, callback ); + }; +} ); + +// Generate shortcuts for custom animations +jQuery.each( { + slideDown: genFx( "show" ), + slideUp: genFx( "hide" ), + slideToggle: genFx( "toggle" ), + fadeIn: { opacity: "show" }, + fadeOut: { opacity: "hide" }, + fadeToggle: { opacity: "toggle" } +}, function( name, props ) { + jQuery.fn[ name ] = function( speed, easing, callback ) { + return this.animate( props, speed, easing, callback ); + }; +} ); + +jQuery.timers = []; +jQuery.fx.tick = function() { + var timer, + i = 0, + timers = jQuery.timers; + + fxNow = jQuery.now(); + + for ( ; i < timers.length; i++ ) { + timer = timers[ i ]; + + // Run the timer and safely remove it when done (allowing for external removal) + if ( !timer() && timers[ i ] === timer ) { + timers.splice( i--, 1 ); + } + } + + if ( !timers.length ) { + jQuery.fx.stop(); + } + fxNow = undefined; +}; + +jQuery.fx.timer = function( timer ) { + jQuery.timers.push( timer ); + jQuery.fx.start(); +}; + +jQuery.fx.interval = 13; +jQuery.fx.start = function() { + if ( inProgress ) { + return; + } + + inProgress = true; + schedule(); +}; + +jQuery.fx.stop = function() { + inProgress = null; +}; + +jQuery.fx.speeds = { + slow: 600, + fast: 200, + + // Default speed + _default: 400 +}; + + +// Based off of the plugin by Clint Helfers, with permission. +// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ +jQuery.fn.delay = function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = window.setTimeout( next, time ); + hooks.stop = function() { + window.clearTimeout( timeout ); + }; + } ); +}; + + +( function() { + var input = document.createElement( "input" ), + select = document.createElement( "select" ), + opt = select.appendChild( document.createElement( "option" ) ); + + input.type = "checkbox"; + + // Support: Android <=4.3 only + // Default value for a checkbox should be "on" + support.checkOn = input.value !== ""; + + // Support: IE <=11 only + // Must access selectedIndex to make default options select + support.optSelected = opt.selected; + + // Support: IE <=11 only + // An input loses its value after becoming a radio + input = document.createElement( "input" ); + input.value = "t"; + input.type = "radio"; + support.radioValue = input.value === "t"; +} )(); + + +var boolHook, + attrHandle = jQuery.expr.attrHandle; + +jQuery.fn.extend( { + attr: function( name, value ) { + return access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each( function() { + jQuery.removeAttr( this, name ); + } ); + } +} ); + +jQuery.extend( { + attr: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set attributes on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === "undefined" ) { + return jQuery.prop( elem, name, value ); + } + + // Attribute hooks are determined by the lowercase version + // Grab necessary hook if one is defined + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + hooks = jQuery.attrHooks[ name.toLowerCase() ] || + ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); + } + + if ( value !== undefined ) { + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return; + } + + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + elem.setAttribute( name, value + "" ); + return value; + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + ret = jQuery.find.attr( elem, name ); + + // Non-existent attributes return null, we normalize to undefined + return ret == null ? undefined : ret; + }, + + attrHooks: { + type: { + set: function( elem, value ) { + if ( !support.radioValue && value === "radio" && + nodeName( elem, "input" ) ) { + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + } + }, + + removeAttr: function( elem, value ) { + var name, + i = 0, + + // Attribute names can contain non-HTML whitespace characters + // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 + attrNames = value && value.match( rnothtmlwhite ); + + if ( attrNames && elem.nodeType === 1 ) { + while ( ( name = attrNames[ i++ ] ) ) { + elem.removeAttribute( name ); + } + } + } +} ); + +// Hooks for boolean attributes +boolHook = { + set: function( elem, value, name ) { + if ( value === false ) { + + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else { + elem.setAttribute( name, name ); + } + return name; + } +}; + +jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) { + var getter = attrHandle[ name ] || jQuery.find.attr; + + attrHandle[ name ] = function( elem, name, isXML ) { + var ret, handle, + lowercaseName = name.toLowerCase(); + + if ( !isXML ) { + + // Avoid an infinite loop by temporarily removing this function from the getter + handle = attrHandle[ lowercaseName ]; + attrHandle[ lowercaseName ] = ret; + ret = getter( elem, name, isXML ) != null ? + lowercaseName : + null; + attrHandle[ lowercaseName ] = handle; + } + return ret; + }; +} ); + + + + +var rfocusable = /^(?:input|select|textarea|button)$/i, + rclickable = /^(?:a|area)$/i; + +jQuery.fn.extend( { + prop: function( name, value ) { + return access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + return this.each( function() { + delete this[ jQuery.propFix[ name ] || name ]; + } ); + } +} ); + +jQuery.extend( { + prop: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set properties on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + return ( elem[ name ] = value ); + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + return elem[ name ]; + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + + // Support: IE <=9 - 11 only + // elem.tabIndex doesn't always return the + // correct value when it hasn't been explicitly set + // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + // Use proper attribute retrieval(#12072) + var tabindex = jQuery.find.attr( elem, "tabindex" ); + + if ( tabindex ) { + return parseInt( tabindex, 10 ); + } + + if ( + rfocusable.test( elem.nodeName ) || + rclickable.test( elem.nodeName ) && + elem.href + ) { + return 0; + } + + return -1; + } + } + }, + + propFix: { + "for": "htmlFor", + "class": "className" + } +} ); + +// Support: IE <=11 only +// Accessing the selectedIndex property +// forces the browser to respect setting selected +// on the option +// The getter ensures a default option is selected +// when in an optgroup +// eslint rule "no-unused-expressions" is disabled for this code +// since it considers such accessions noop +if ( !support.optSelected ) { + jQuery.propHooks.selected = { + get: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent && parent.parentNode ) { + parent.parentNode.selectedIndex; + } + return null; + }, + set: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; + + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + }; +} + +jQuery.each( [ + "tabIndex", + "readOnly", + "maxLength", + "cellSpacing", + "cellPadding", + "rowSpan", + "colSpan", + "useMap", + "frameBorder", + "contentEditable" +], function() { + jQuery.propFix[ this.toLowerCase() ] = this; +} ); + + + + + // Strip and collapse whitespace according to HTML spec + // https://html.spec.whatwg.org/multipage/infrastructure.html#strip-and-collapse-whitespace + function stripAndCollapse( value ) { + var tokens = value.match( rnothtmlwhite ) || []; + return tokens.join( " " ); + } + + +function getClass( elem ) { + return elem.getAttribute && elem.getAttribute( "class" ) || ""; +} + +jQuery.fn.extend( { + addClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( jQuery.isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + if ( typeof value === "string" && value ) { + classes = value.match( rnothtmlwhite ) || []; + + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + if ( cur.indexOf( " " + clazz + " " ) < 0 ) { + cur += clazz + " "; + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( jQuery.isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + if ( !arguments.length ) { + return this.attr( "class", "" ); + } + + if ( typeof value === "string" && value ) { + classes = value.match( rnothtmlwhite ) || []; + + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + + // This expression is here for better compressibility (see addClass) + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + + // Remove *all* instances + while ( cur.indexOf( " " + clazz + " " ) > -1 ) { + cur = cur.replace( " " + clazz + " ", " " ); + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value; + + if ( typeof stateVal === "boolean" && type === "string" ) { + return stateVal ? this.addClass( value ) : this.removeClass( value ); + } + + if ( jQuery.isFunction( value ) ) { + return this.each( function( i ) { + jQuery( this ).toggleClass( + value.call( this, i, getClass( this ), stateVal ), + stateVal + ); + } ); + } + + return this.each( function() { + var className, i, self, classNames; + + if ( type === "string" ) { + + // Toggle individual class names + i = 0; + self = jQuery( this ); + classNames = value.match( rnothtmlwhite ) || []; + + while ( ( className = classNames[ i++ ] ) ) { + + // Check each className given, space separated list + if ( self.hasClass( className ) ) { + self.removeClass( className ); + } else { + self.addClass( className ); + } + } + + // Toggle whole class name + } else if ( value === undefined || type === "boolean" ) { + className = getClass( this ); + if ( className ) { + + // Store className if set + dataPriv.set( this, "__className__", className ); + } + + // If the element has a class name or if we're passed `false`, + // then remove the whole classname (if there was one, the above saved it). + // Otherwise bring back whatever was previously saved (if anything), + // falling back to the empty string if nothing was stored. + if ( this.setAttribute ) { + this.setAttribute( "class", + className || value === false ? + "" : + dataPriv.get( this, "__className__" ) || "" + ); + } + } + } ); + }, + + hasClass: function( selector ) { + var className, elem, + i = 0; + + className = " " + selector + " "; + while ( ( elem = this[ i++ ] ) ) { + if ( elem.nodeType === 1 && + ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { + return true; + } + } + + return false; + } +} ); + + + + +var rreturn = /\r/g; + +jQuery.fn.extend( { + val: function( value ) { + var hooks, ret, isFunction, + elem = this[ 0 ]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || + jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && + "get" in hooks && + ( ret = hooks.get( elem, "value" ) ) !== undefined + ) { + return ret; + } + + ret = elem.value; + + // Handle most common string cases + if ( typeof ret === "string" ) { + return ret.replace( rreturn, "" ); + } + + // Handle cases where value is null/undef or number + return ret == null ? "" : ret; + } + + return; + } + + isFunction = jQuery.isFunction( value ); + + return this.each( function( i ) { + var val; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( isFunction ) { + val = value.call( this, i, jQuery( this ).val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + + } else if ( typeof val === "number" ) { + val += ""; + + } else if ( Array.isArray( val ) ) { + val = jQuery.map( val, function( value ) { + return value == null ? "" : value + ""; + } ); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + } ); + } +} ); + +jQuery.extend( { + valHooks: { + option: { + get: function( elem ) { + + var val = jQuery.find.attr( elem, "value" ); + return val != null ? + val : + + // Support: IE <=10 - 11 only + // option.text throws exceptions (#14686, #14858) + // Strip and collapse whitespace + // https://html.spec.whatwg.org/#strip-and-collapse-whitespace + stripAndCollapse( jQuery.text( elem ) ); + } + }, + select: { + get: function( elem ) { + var value, option, i, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === "select-one", + values = one ? null : [], + max = one ? index + 1 : options.length; + + if ( index < 0 ) { + i = max; + + } else { + i = one ? index : 0; + } + + // Loop through all the selected options + for ( ; i < max; i++ ) { + option = options[ i ]; + + // Support: IE <=9 only + // IE8-9 doesn't update selected after form reset (#2551) + if ( ( option.selected || i === index ) && + + // Don't return options that are disabled or in a disabled optgroup + !option.disabled && + ( !option.parentNode.disabled || + !nodeName( option.parentNode, "optgroup" ) ) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + }, + + set: function( elem, value ) { + var optionSet, option, + options = elem.options, + values = jQuery.makeArray( value ), + i = options.length; + + while ( i-- ) { + option = options[ i ]; + + /* eslint-disable no-cond-assign */ + + if ( option.selected = + jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 + ) { + optionSet = true; + } + + /* eslint-enable no-cond-assign */ + } + + // Force browsers to behave consistently when non-matching value is set + if ( !optionSet ) { + elem.selectedIndex = -1; + } + return values; + } + } + } +} ); + +// Radios and checkboxes getter/setter +jQuery.each( [ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + set: function( elem, value ) { + if ( Array.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); + } + } + }; + if ( !support.checkOn ) { + jQuery.valHooks[ this ].get = function( elem ) { + return elem.getAttribute( "value" ) === null ? "on" : elem.value; + }; + } +} ); + + + + +// Return jQuery for attributes-only inclusion + + +var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/; + +jQuery.extend( jQuery.event, { + + trigger: function( event, data, elem, onlyHandlers ) { + + var i, cur, tmp, bubbleType, ontype, handle, special, + eventPath = [ elem || document ], + type = hasOwn.call( event, "type" ) ? event.type : event, + namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; + + cur = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf( "." ) > -1 ) { + + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split( "." ); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf( ":" ) < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join( "." ); + event.rnamespace = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === ( elem.ownerDocument || document ) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { + + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( dataPriv.get( cur, "events" ) || {} )[ event.type ] && + dataPriv.get( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && handle.apply && acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === false ) { + event.preventDefault(); + } + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( ( !special._default || + special._default.apply( eventPath.pop(), data ) === false ) && + acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name as the event. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + elem[ type ](); + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + // Piggyback on a donor event to simulate a different one + // Used only for `focus(in | out)` events + simulate: function( type, elem, event ) { + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true + } + ); + + jQuery.event.trigger( e, null, elem ); + } + +} ); + +jQuery.fn.extend( { + + trigger: function( type, data ) { + return this.each( function() { + jQuery.event.trigger( type, data, this ); + } ); + }, + triggerHandler: function( type, data ) { + var elem = this[ 0 ]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +} ); + + +jQuery.each( ( "blur focus focusin focusout resize scroll click dblclick " + + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + + "change select submit keydown keypress keyup contextmenu" ).split( " " ), + function( i, name ) { + + // Handle event binding + jQuery.fn[ name ] = function( data, fn ) { + return arguments.length > 0 ? + this.on( name, null, data, fn ) : + this.trigger( name ); + }; +} ); + +jQuery.fn.extend( { + hover: function( fnOver, fnOut ) { + return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); + } +} ); + + + + +support.focusin = "onfocusin" in window; + + +// Support: Firefox <=44 +// Firefox doesn't have focus(in | out) events +// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 +// +// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 +// focus(in | out) events fire after focus & blur events, +// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order +// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 +if ( !support.focusin ) { + jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler on the document while someone wants focusin/focusout + var handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + var doc = this.ownerDocument || this, + attaches = dataPriv.access( doc, fix ); + + if ( !attaches ) { + doc.addEventListener( orig, handler, true ); + } + dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); + }, + teardown: function() { + var doc = this.ownerDocument || this, + attaches = dataPriv.access( doc, fix ) - 1; + + if ( !attaches ) { + doc.removeEventListener( orig, handler, true ); + dataPriv.remove( doc, fix ); + + } else { + dataPriv.access( doc, fix, attaches ); + } + } + }; + } ); +} +var location = window.location; + +var nonce = jQuery.now(); + +var rquery = ( /\?/ ); + + + +// Cross-browser xml parsing +jQuery.parseXML = function( data ) { + var xml; + if ( !data || typeof data !== "string" ) { + return null; + } + + // Support: IE 9 - 11 only + // IE throws on parseFromString with invalid input. + try { + xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); + } catch ( e ) { + xml = undefined; + } + + if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { + jQuery.error( "Invalid XML: " + data ); + } + return xml; +}; + + +var + rbracket = /\[\]$/, + rCRLF = /\r?\n/g, + rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, + rsubmittable = /^(?:input|select|textarea|keygen)/i; + +function buildParams( prefix, obj, traditional, add ) { + var name; + + if ( Array.isArray( obj ) ) { + + // Serialize array item. + jQuery.each( obj, function( i, v ) { + if ( traditional || rbracket.test( prefix ) ) { + + // Treat each array item as a scalar. + add( prefix, v ); + + } else { + + // Item is non-scalar (array or object), encode its numeric index. + buildParams( + prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", + v, + traditional, + add + ); + } + } ); + + } else if ( !traditional && jQuery.type( obj ) === "object" ) { + + // Serialize object item. + for ( name in obj ) { + buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); + } + + } else { + + // Serialize scalar item. + add( prefix, obj ); + } +} + +// Serialize an array of form elements or a set of +// key/values into a query string +jQuery.param = function( a, traditional ) { + var prefix, + s = [], + add = function( key, valueOrFunction ) { + + // If value is a function, invoke it and use its return value + var value = jQuery.isFunction( valueOrFunction ) ? + valueOrFunction() : + valueOrFunction; + + s[ s.length ] = encodeURIComponent( key ) + "=" + + encodeURIComponent( value == null ? "" : value ); + }; + + // If an array was passed in, assume that it is an array of form elements. + if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { + + // Serialize the form elements + jQuery.each( a, function() { + add( this.name, this.value ); + } ); + + } else { + + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for ( prefix in a ) { + buildParams( prefix, a[ prefix ], traditional, add ); + } + } + + // Return the resulting serialization + return s.join( "&" ); +}; + +jQuery.fn.extend( { + serialize: function() { + return jQuery.param( this.serializeArray() ); + }, + serializeArray: function() { + return this.map( function() { + + // Can add propHook for "elements" to filter or add form elements + var elements = jQuery.prop( this, "elements" ); + return elements ? jQuery.makeArray( elements ) : this; + } ) + .filter( function() { + var type = this.type; + + // Use .is( ":disabled" ) so that fieldset[disabled] works + return this.name && !jQuery( this ).is( ":disabled" ) && + rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && + ( this.checked || !rcheckableType.test( type ) ); + } ) + .map( function( i, elem ) { + var val = jQuery( this ).val(); + + if ( val == null ) { + return null; + } + + if ( Array.isArray( val ) ) { + return jQuery.map( val, function( val ) { + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ); + } + + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ).get(); + } +} ); + + +var + r20 = /%20/g, + rhash = /#.*$/, + rantiCache = /([?&])_=[^&]*/, + rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, + + // #7653, #8125, #8152: local protocol detection + rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, + rnoContent = /^(?:GET|HEAD)$/, + rprotocol = /^\/\//, + + /* Prefilters + * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) + * 2) These are called: + * - BEFORE asking for a transport + * - AFTER param serialization (s.data is a string if s.processData is true) + * 3) key is the dataType + * 4) the catchall symbol "*" can be used + * 5) execution will start with transport dataType and THEN continue down to "*" if needed + */ + prefilters = {}, + + /* Transports bindings + * 1) key is the dataType + * 2) the catchall symbol "*" can be used + * 3) selection will start with transport dataType and THEN go to "*" if needed + */ + transports = {}, + + // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression + allTypes = "*/".concat( "*" ), + + // Anchor tag for parsing the document origin + originAnchor = document.createElement( "a" ); + originAnchor.href = location.href; + +// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport +function addToPrefiltersOrTransports( structure ) { + + // dataTypeExpression is optional and defaults to "*" + return function( dataTypeExpression, func ) { + + if ( typeof dataTypeExpression !== "string" ) { + func = dataTypeExpression; + dataTypeExpression = "*"; + } + + var dataType, + i = 0, + dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; + + if ( jQuery.isFunction( func ) ) { + + // For each dataType in the dataTypeExpression + while ( ( dataType = dataTypes[ i++ ] ) ) { + + // Prepend if requested + if ( dataType[ 0 ] === "+" ) { + dataType = dataType.slice( 1 ) || "*"; + ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); + + // Otherwise append + } else { + ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); + } + } + } + }; +} + +// Base inspection function for prefilters and transports +function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { + + var inspected = {}, + seekingTransport = ( structure === transports ); + + function inspect( dataType ) { + var selected; + inspected[ dataType ] = true; + jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { + var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); + if ( typeof dataTypeOrTransport === "string" && + !seekingTransport && !inspected[ dataTypeOrTransport ] ) { + + options.dataTypes.unshift( dataTypeOrTransport ); + inspect( dataTypeOrTransport ); + return false; + } else if ( seekingTransport ) { + return !( selected = dataTypeOrTransport ); + } + } ); + return selected; + } + + return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); +} + +// A special extend for ajax options +// that takes "flat" options (not to be deep extended) +// Fixes #9887 +function ajaxExtend( target, src ) { + var key, deep, + flatOptions = jQuery.ajaxSettings.flatOptions || {}; + + for ( key in src ) { + if ( src[ key ] !== undefined ) { + ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; + } + } + if ( deep ) { + jQuery.extend( true, target, deep ); + } + + return target; +} + +/* Handles responses to an ajax request: + * - finds the right dataType (mediates between content-type and expected dataType) + * - returns the corresponding response + */ +function ajaxHandleResponses( s, jqXHR, responses ) { + + var ct, type, finalDataType, firstDataType, + contents = s.contents, + dataTypes = s.dataTypes; + + // Remove auto dataType and get content-type in the process + while ( dataTypes[ 0 ] === "*" ) { + dataTypes.shift(); + if ( ct === undefined ) { + ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); + } + } + + // Check if we're dealing with a known content-type + if ( ct ) { + for ( type in contents ) { + if ( contents[ type ] && contents[ type ].test( ct ) ) { + dataTypes.unshift( type ); + break; + } + } + } + + // Check to see if we have a response for the expected dataType + if ( dataTypes[ 0 ] in responses ) { + finalDataType = dataTypes[ 0 ]; + } else { + + // Try convertible dataTypes + for ( type in responses ) { + if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { + finalDataType = type; + break; + } + if ( !firstDataType ) { + firstDataType = type; + } + } + + // Or just use first one + finalDataType = finalDataType || firstDataType; + } + + // If we found a dataType + // We add the dataType to the list if needed + // and return the corresponding response + if ( finalDataType ) { + if ( finalDataType !== dataTypes[ 0 ] ) { + dataTypes.unshift( finalDataType ); + } + return responses[ finalDataType ]; + } +} + +/* Chain conversions given the request and the original response + * Also sets the responseXXX fields on the jqXHR instance + */ +function ajaxConvert( s, response, jqXHR, isSuccess ) { + var conv2, current, conv, tmp, prev, + converters = {}, + + // Work with a copy of dataTypes in case we need to modify it for conversion + dataTypes = s.dataTypes.slice(); + + // Create converters map with lowercased keys + if ( dataTypes[ 1 ] ) { + for ( conv in s.converters ) { + converters[ conv.toLowerCase() ] = s.converters[ conv ]; + } + } + + current = dataTypes.shift(); + + // Convert to each sequential dataType + while ( current ) { + + if ( s.responseFields[ current ] ) { + jqXHR[ s.responseFields[ current ] ] = response; + } + + // Apply the dataFilter if provided + if ( !prev && isSuccess && s.dataFilter ) { + response = s.dataFilter( response, s.dataType ); + } + + prev = current; + current = dataTypes.shift(); + + if ( current ) { + + // There's only work to do if current dataType is non-auto + if ( current === "*" ) { + + current = prev; + + // Convert response if prev dataType is non-auto and differs from current + } else if ( prev !== "*" && prev !== current ) { + + // Seek a direct converter + conv = converters[ prev + " " + current ] || converters[ "* " + current ]; + + // If none found, seek a pair + if ( !conv ) { + for ( conv2 in converters ) { + + // If conv2 outputs current + tmp = conv2.split( " " ); + if ( tmp[ 1 ] === current ) { + + // If prev can be converted to accepted input + conv = converters[ prev + " " + tmp[ 0 ] ] || + converters[ "* " + tmp[ 0 ] ]; + if ( conv ) { + + // Condense equivalence converters + if ( conv === true ) { + conv = converters[ conv2 ]; + + // Otherwise, insert the intermediate dataType + } else if ( converters[ conv2 ] !== true ) { + current = tmp[ 0 ]; + dataTypes.unshift( tmp[ 1 ] ); + } + break; + } + } + } + } + + // Apply converter (if not an equivalence) + if ( conv !== true ) { + + // Unless errors are allowed to bubble, catch and return them + if ( conv && s.throws ) { + response = conv( response ); + } else { + try { + response = conv( response ); + } catch ( e ) { + return { + state: "parsererror", + error: conv ? e : "No conversion from " + prev + " to " + current + }; + } + } + } + } + } + } + + return { state: "success", data: response }; +} + +jQuery.extend( { + + // Counter for holding the number of active queries + active: 0, + + // Last-Modified header cache for next request + lastModified: {}, + etag: {}, + + ajaxSettings: { + url: location.href, + type: "GET", + isLocal: rlocalProtocol.test( location.protocol ), + global: true, + processData: true, + async: true, + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + + /* + timeout: 0, + data: null, + dataType: null, + username: null, + password: null, + cache: null, + throws: false, + traditional: false, + headers: {}, + */ + + accepts: { + "*": allTypes, + text: "text/plain", + html: "text/html", + xml: "application/xml, text/xml", + json: "application/json, text/javascript" + }, + + contents: { + xml: /\bxml\b/, + html: /\bhtml/, + json: /\bjson\b/ + }, + + responseFields: { + xml: "responseXML", + text: "responseText", + json: "responseJSON" + }, + + // Data converters + // Keys separate source (or catchall "*") and destination types with a single space + converters: { + + // Convert anything to text + "* text": String, + + // Text to html (true = no transformation) + "text html": true, + + // Evaluate text as a json expression + "text json": JSON.parse, + + // Parse text as xml + "text xml": jQuery.parseXML + }, + + // For options that shouldn't be deep extended: + // you can add your own custom options here if + // and when you create one that shouldn't be + // deep extended (see ajaxExtend) + flatOptions: { + url: true, + context: true + } + }, + + // Creates a full fledged settings object into target + // with both ajaxSettings and settings fields. + // If target is omitted, writes into ajaxSettings. + ajaxSetup: function( target, settings ) { + return settings ? + + // Building a settings object + ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : + + // Extending ajaxSettings + ajaxExtend( jQuery.ajaxSettings, target ); + }, + + ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), + ajaxTransport: addToPrefiltersOrTransports( transports ), + + // Main method + ajax: function( url, options ) { + + // If url is an object, simulate pre-1.5 signature + if ( typeof url === "object" ) { + options = url; + url = undefined; + } + + // Force options to be an object + options = options || {}; + + var transport, + + // URL without anti-cache param + cacheURL, + + // Response headers + responseHeadersString, + responseHeaders, + + // timeout handle + timeoutTimer, + + // Url cleanup var + urlAnchor, + + // Request state (becomes false upon send and true upon completion) + completed, + + // To know if global events are to be dispatched + fireGlobals, + + // Loop variable + i, + + // uncached part of the url + uncached, + + // Create the final options object + s = jQuery.ajaxSetup( {}, options ), + + // Callbacks context + callbackContext = s.context || s, + + // Context for global events is callbackContext if it is a DOM node or jQuery collection + globalEventContext = s.context && + ( callbackContext.nodeType || callbackContext.jquery ) ? + jQuery( callbackContext ) : + jQuery.event, + + // Deferreds + deferred = jQuery.Deferred(), + completeDeferred = jQuery.Callbacks( "once memory" ), + + // Status-dependent callbacks + statusCode = s.statusCode || {}, + + // Headers (they are sent all at once) + requestHeaders = {}, + requestHeadersNames = {}, + + // Default abort message + strAbort = "canceled", + + // Fake xhr + jqXHR = { + readyState: 0, + + // Builds headers hashtable if needed + getResponseHeader: function( key ) { + var match; + if ( completed ) { + if ( !responseHeaders ) { + responseHeaders = {}; + while ( ( match = rheaders.exec( responseHeadersString ) ) ) { + responseHeaders[ match[ 1 ].toLowerCase() ] = match[ 2 ]; + } + } + match = responseHeaders[ key.toLowerCase() ]; + } + return match == null ? null : match; + }, + + // Raw string + getAllResponseHeaders: function() { + return completed ? responseHeadersString : null; + }, + + // Caches the header + setRequestHeader: function( name, value ) { + if ( completed == null ) { + name = requestHeadersNames[ name.toLowerCase() ] = + requestHeadersNames[ name.toLowerCase() ] || name; + requestHeaders[ name ] = value; + } + return this; + }, + + // Overrides response content-type header + overrideMimeType: function( type ) { + if ( completed == null ) { + s.mimeType = type; + } + return this; + }, + + // Status-dependent callbacks + statusCode: function( map ) { + var code; + if ( map ) { + if ( completed ) { + + // Execute the appropriate callbacks + jqXHR.always( map[ jqXHR.status ] ); + } else { + + // Lazy-add the new callbacks in a way that preserves old ones + for ( code in map ) { + statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; + } + } + } + return this; + }, + + // Cancel the request + abort: function( statusText ) { + var finalText = statusText || strAbort; + if ( transport ) { + transport.abort( finalText ); + } + done( 0, finalText ); + return this; + } + }; + + // Attach deferreds + deferred.promise( jqXHR ); + + // Add protocol if not provided (prefilters might expect it) + // Handle falsy url in the settings object (#10093: consistency with old signature) + // We also use the url parameter if available + s.url = ( ( url || s.url || location.href ) + "" ) + .replace( rprotocol, location.protocol + "//" ); + + // Alias method option to type as per ticket #12004 + s.type = options.method || options.type || s.method || s.type; + + // Extract dataTypes list + s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; + + // A cross-domain request is in order when the origin doesn't match the current origin. + if ( s.crossDomain == null ) { + urlAnchor = document.createElement( "a" ); + + // Support: IE <=8 - 11, Edge 12 - 13 + // IE throws exception on accessing the href property if url is malformed, + // e.g. http://example.com:80x/ + try { + urlAnchor.href = s.url; + + // Support: IE <=8 - 11 only + // Anchor's host property isn't correctly set when s.url is relative + urlAnchor.href = urlAnchor.href; + s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== + urlAnchor.protocol + "//" + urlAnchor.host; + } catch ( e ) { + + // If there is an error parsing the URL, assume it is crossDomain, + // it can be rejected by the transport if it is invalid + s.crossDomain = true; + } + } + + // Convert data if not already a string + if ( s.data && s.processData && typeof s.data !== "string" ) { + s.data = jQuery.param( s.data, s.traditional ); + } + + // Apply prefilters + inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); + + // If request was aborted inside a prefilter, stop there + if ( completed ) { + return jqXHR; + } + + // We can fire global events as of now if asked to + // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) + fireGlobals = jQuery.event && s.global; + + // Watch for a new set of requests + if ( fireGlobals && jQuery.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); + } + + // Uppercase the type + s.type = s.type.toUpperCase(); + + // Determine if request has content + s.hasContent = !rnoContent.test( s.type ); + + // Save the URL in case we're toying with the If-Modified-Since + // and/or If-None-Match header later on + // Remove hash to simplify url manipulation + cacheURL = s.url.replace( rhash, "" ); + + // More options handling for requests with no content + if ( !s.hasContent ) { + + // Remember the hash so we can put it back + uncached = s.url.slice( cacheURL.length ); + + // If data is available, append data to url + if ( s.data ) { + cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; + + // #9682: remove data so that it's not used in an eventual retry + delete s.data; + } + + // Add or update anti-cache param if needed + if ( s.cache === false ) { + cacheURL = cacheURL.replace( rantiCache, "$1" ); + uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce++ ) + uncached; + } + + // Put hash and anti-cache on the URL that will be requested (gh-1732) + s.url = cacheURL + uncached; + + // Change '%20' to '+' if this is encoded form body content (gh-2658) + } else if ( s.data && s.processData && + ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { + s.data = s.data.replace( r20, "+" ); + } + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + if ( jQuery.lastModified[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); + } + if ( jQuery.etag[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); + } + } + + // Set the correct header, if data is being sent + if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { + jqXHR.setRequestHeader( "Content-Type", s.contentType ); + } + + // Set the Accepts header for the server, depending on the dataType + jqXHR.setRequestHeader( + "Accept", + s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? + s.accepts[ s.dataTypes[ 0 ] ] + + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : + s.accepts[ "*" ] + ); + + // Check for headers option + for ( i in s.headers ) { + jqXHR.setRequestHeader( i, s.headers[ i ] ); + } + + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && + ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { + + // Abort if not done already and return + return jqXHR.abort(); + } + + // Aborting is no longer a cancellation + strAbort = "abort"; + + // Install callbacks on deferreds + completeDeferred.add( s.complete ); + jqXHR.done( s.success ); + jqXHR.fail( s.error ); + + // Get transport + transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); + + // If no transport, we auto-abort + if ( !transport ) { + done( -1, "No Transport" ); + } else { + jqXHR.readyState = 1; + + // Send global event + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); + } + + // If request was aborted inside ajaxSend, stop there + if ( completed ) { + return jqXHR; + } + + // Timeout + if ( s.async && s.timeout > 0 ) { + timeoutTimer = window.setTimeout( function() { + jqXHR.abort( "timeout" ); + }, s.timeout ); + } + + try { + completed = false; + transport.send( requestHeaders, done ); + } catch ( e ) { + + // Rethrow post-completion exceptions + if ( completed ) { + throw e; + } + + // Propagate others as results + done( -1, e ); + } + } + + // Callback for when everything is done + function done( status, nativeStatusText, responses, headers ) { + var isSuccess, success, error, response, modified, + statusText = nativeStatusText; + + // Ignore repeat invocations + if ( completed ) { + return; + } + + completed = true; + + // Clear timeout if it exists + if ( timeoutTimer ) { + window.clearTimeout( timeoutTimer ); + } + + // Dereference transport for early garbage collection + // (no matter how long the jqXHR object will be used) + transport = undefined; + + // Cache response headers + responseHeadersString = headers || ""; + + // Set readyState + jqXHR.readyState = status > 0 ? 4 : 0; + + // Determine if successful + isSuccess = status >= 200 && status < 300 || status === 304; + + // Get response data + if ( responses ) { + response = ajaxHandleResponses( s, jqXHR, responses ); + } + + // Convert no matter what (that way responseXXX fields are always set) + response = ajaxConvert( s, response, jqXHR, isSuccess ); + + // If successful, handle type chaining + if ( isSuccess ) { + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + modified = jqXHR.getResponseHeader( "Last-Modified" ); + if ( modified ) { + jQuery.lastModified[ cacheURL ] = modified; + } + modified = jqXHR.getResponseHeader( "etag" ); + if ( modified ) { + jQuery.etag[ cacheURL ] = modified; + } + } + + // if no content + if ( status === 204 || s.type === "HEAD" ) { + statusText = "nocontent"; + + // if not modified + } else if ( status === 304 ) { + statusText = "notmodified"; + + // If we have data, let's convert it + } else { + statusText = response.state; + success = response.data; + error = response.error; + isSuccess = !error; + } + } else { + + // Extract error from statusText and normalize for non-aborts + error = statusText; + if ( status || !statusText ) { + statusText = "error"; + if ( status < 0 ) { + status = 0; + } + } + } + + // Set data for the fake xhr object + jqXHR.status = status; + jqXHR.statusText = ( nativeStatusText || statusText ) + ""; + + // Success/Error + if ( isSuccess ) { + deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); + } else { + deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); + } + + // Status-dependent callbacks + jqXHR.statusCode( statusCode ); + statusCode = undefined; + + if ( fireGlobals ) { + globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", + [ jqXHR, s, isSuccess ? success : error ] ); + } + + // Complete + completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); + + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); + + // Handle the global AJAX counter + if ( !( --jQuery.active ) ) { + jQuery.event.trigger( "ajaxStop" ); + } + } + } + + return jqXHR; + }, + + getJSON: function( url, data, callback ) { + return jQuery.get( url, data, callback, "json" ); + }, + + getScript: function( url, callback ) { + return jQuery.get( url, undefined, callback, "script" ); + } +} ); + +jQuery.each( [ "get", "post" ], function( i, method ) { + jQuery[ method ] = function( url, data, callback, type ) { + + // Shift arguments if data argument was omitted + if ( jQuery.isFunction( data ) ) { + type = type || callback; + callback = data; + data = undefined; + } + + // The url can be an options object (which then must have .url) + return jQuery.ajax( jQuery.extend( { + url: url, + type: method, + dataType: type, + data: data, + success: callback + }, jQuery.isPlainObject( url ) && url ) ); + }; +} ); + + +jQuery._evalUrl = function( url ) { + return jQuery.ajax( { + url: url, + + // Make this explicit, since user can override this through ajaxSetup (#11264) + type: "GET", + dataType: "script", + cache: true, + async: false, + global: false, + "throws": true + } ); +}; + + +jQuery.fn.extend( { + wrapAll: function( html ) { + var wrap; + + if ( this[ 0 ] ) { + if ( jQuery.isFunction( html ) ) { + html = html.call( this[ 0 ] ); + } + + // The elements to wrap the target around + wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); + + if ( this[ 0 ].parentNode ) { + wrap.insertBefore( this[ 0 ] ); + } + + wrap.map( function() { + var elem = this; + + while ( elem.firstElementChild ) { + elem = elem.firstElementChild; + } + + return elem; + } ).append( this ); + } + + return this; + }, + + wrapInner: function( html ) { + if ( jQuery.isFunction( html ) ) { + return this.each( function( i ) { + jQuery( this ).wrapInner( html.call( this, i ) ); + } ); + } + + return this.each( function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + } ); + }, + + wrap: function( html ) { + var isFunction = jQuery.isFunction( html ); + + return this.each( function( i ) { + jQuery( this ).wrapAll( isFunction ? html.call( this, i ) : html ); + } ); + }, + + unwrap: function( selector ) { + this.parent( selector ).not( "body" ).each( function() { + jQuery( this ).replaceWith( this.childNodes ); + } ); + return this; + } +} ); + + +jQuery.expr.pseudos.hidden = function( elem ) { + return !jQuery.expr.pseudos.visible( elem ); +}; +jQuery.expr.pseudos.visible = function( elem ) { + return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); +}; + + + + +jQuery.ajaxSettings.xhr = function() { + try { + return new window.XMLHttpRequest(); + } catch ( e ) {} +}; + +var xhrSuccessStatus = { + + // File protocol always yields status code 0, assume 200 + 0: 200, + + // Support: IE <=9 only + // #1450: sometimes IE returns 1223 when it should be 204 + 1223: 204 + }, + xhrSupported = jQuery.ajaxSettings.xhr(); + +support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); +support.ajax = xhrSupported = !!xhrSupported; + +jQuery.ajaxTransport( function( options ) { + var callback, errorCallback; + + // Cross domain only allowed if supported through XMLHttpRequest + if ( support.cors || xhrSupported && !options.crossDomain ) { + return { + send: function( headers, complete ) { + var i, + xhr = options.xhr(); + + xhr.open( + options.type, + options.url, + options.async, + options.username, + options.password + ); + + // Apply custom fields if provided + if ( options.xhrFields ) { + for ( i in options.xhrFields ) { + xhr[ i ] = options.xhrFields[ i ]; + } + } + + // Override mime type if needed + if ( options.mimeType && xhr.overrideMimeType ) { + xhr.overrideMimeType( options.mimeType ); + } + + // X-Requested-With header + // For cross-domain requests, seeing as conditions for a preflight are + // akin to a jigsaw puzzle, we simply never set it to be sure. + // (it can always be set on a per-request basis or even using ajaxSetup) + // For same-domain requests, won't change header if already provided. + if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { + headers[ "X-Requested-With" ] = "XMLHttpRequest"; + } + + // Set headers + for ( i in headers ) { + xhr.setRequestHeader( i, headers[ i ] ); + } + + // Callback + callback = function( type ) { + return function() { + if ( callback ) { + callback = errorCallback = xhr.onload = + xhr.onerror = xhr.onabort = xhr.onreadystatechange = null; + + if ( type === "abort" ) { + xhr.abort(); + } else if ( type === "error" ) { + + // Support: IE <=9 only + // On a manual native abort, IE9 throws + // errors on any property access that is not readyState + if ( typeof xhr.status !== "number" ) { + complete( 0, "error" ); + } else { + complete( + + // File: protocol always yields status 0; see #8605, #14207 + xhr.status, + xhr.statusText + ); + } + } else { + complete( + xhrSuccessStatus[ xhr.status ] || xhr.status, + xhr.statusText, + + // Support: IE <=9 only + // IE9 has no XHR2 but throws on binary (trac-11426) + // For XHR2 non-text, let the caller handle it (gh-2498) + ( xhr.responseType || "text" ) !== "text" || + typeof xhr.responseText !== "string" ? + { binary: xhr.response } : + { text: xhr.responseText }, + xhr.getAllResponseHeaders() + ); + } + } + }; + }; + + // Listen to events + xhr.onload = callback(); + errorCallback = xhr.onerror = callback( "error" ); + + // Support: IE 9 only + // Use onreadystatechange to replace onabort + // to handle uncaught aborts + if ( xhr.onabort !== undefined ) { + xhr.onabort = errorCallback; + } else { + xhr.onreadystatechange = function() { + + // Check readyState before timeout as it changes + if ( xhr.readyState === 4 ) { + + // Allow onerror to be called first, + // but that will not handle a native abort + // Also, save errorCallback to a variable + // as xhr.onerror cannot be accessed + window.setTimeout( function() { + if ( callback ) { + errorCallback(); + } + } ); + } + }; + } + + // Create the abort callback + callback = callback( "abort" ); + + try { + + // Do send the request (this may raise an exception) + xhr.send( options.hasContent && options.data || null ); + } catch ( e ) { + + // #14683: Only rethrow if this hasn't been notified as an error yet + if ( callback ) { + throw e; + } + } + }, + + abort: function() { + if ( callback ) { + callback(); + } + } + }; + } +} ); + + + + +// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) +jQuery.ajaxPrefilter( function( s ) { + if ( s.crossDomain ) { + s.contents.script = false; + } +} ); + +// Install script dataType +jQuery.ajaxSetup( { + accepts: { + script: "text/javascript, application/javascript, " + + "application/ecmascript, application/x-ecmascript" + }, + contents: { + script: /\b(?:java|ecma)script\b/ + }, + converters: { + "text script": function( text ) { + jQuery.globalEval( text ); + return text; + } + } +} ); + +// Handle cache's special case and crossDomain +jQuery.ajaxPrefilter( "script", function( s ) { + if ( s.cache === undefined ) { + s.cache = false; + } + if ( s.crossDomain ) { + s.type = "GET"; + } +} ); + +// Bind script tag hack transport +jQuery.ajaxTransport( "script", function( s ) { + + // This transport only deals with cross domain requests + if ( s.crossDomain ) { + var script, callback; + return { + send: function( _, complete ) { + script = jQuery( "<script>" ).prop( { + charset: s.scriptCharset, + src: s.url + } ).on( + "load error", + callback = function( evt ) { + script.remove(); + callback = null; + if ( evt ) { + complete( evt.type === "error" ? 404 : 200, evt.type ); + } + } + ); + + // Use native DOM manipulation to avoid our domManip AJAX trickery + document.head.appendChild( script[ 0 ] ); + }, + abort: function() { + if ( callback ) { + callback(); + } + } + }; + } +} ); + + + + +var oldCallbacks = [], + rjsonp = /(=)\?(?=&|$)|\?\?/; + +// Default jsonp settings +jQuery.ajaxSetup( { + jsonp: "callback", + jsonpCallback: function() { + var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce++ ) ); + this[ callback ] = true; + return callback; + } +} ); + +// Detect, normalize options and install callbacks for jsonp requests +jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) { + + var callbackName, overwritten, responseContainer, + jsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ? + "url" : + typeof s.data === "string" && + ( s.contentType || "" ) + .indexOf( "application/x-www-form-urlencoded" ) === 0 && + rjsonp.test( s.data ) && "data" + ); + + // Handle iff the expected data type is "jsonp" or we have a parameter to set + if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) { + + // Get callback name, remembering preexisting value associated with it + callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ? + s.jsonpCallback() : + s.jsonpCallback; + + // Insert callback into url or form data + if ( jsonProp ) { + s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName ); + } else if ( s.jsonp !== false ) { + s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName; + } + + // Use data converter to retrieve json after script execution + s.converters[ "script json" ] = function() { + if ( !responseContainer ) { + jQuery.error( callbackName + " was not called" ); + } + return responseContainer[ 0 ]; + }; + + // Force json dataType + s.dataTypes[ 0 ] = "json"; + + // Install callback + overwritten = window[ callbackName ]; + window[ callbackName ] = function() { + responseContainer = arguments; + }; + + // Clean-up function (fires after converters) + jqXHR.always( function() { + + // If previous value didn't exist - remove it + if ( overwritten === undefined ) { + jQuery( window ).removeProp( callbackName ); + + // Otherwise restore preexisting value + } else { + window[ callbackName ] = overwritten; + } + + // Save back as free + if ( s[ callbackName ] ) { + + // Make sure that re-using the options doesn't screw things around + s.jsonpCallback = originalSettings.jsonpCallback; + + // Save the callback name for future use + oldCallbacks.push( callbackName ); + } + + // Call if it was a function and we have a response + if ( responseContainer && jQuery.isFunction( overwritten ) ) { + overwritten( responseContainer[ 0 ] ); + } + + responseContainer = overwritten = undefined; + } ); + + // Delegate to script + return "script"; + } +} ); + + + + +// Support: Safari 8 only +// In Safari 8 documents created via document.implementation.createHTMLDocument +// collapse sibling forms: the second one becomes a child of the first one. +// Because of that, this security measure has to be disabled in Safari 8. +// https://bugs.webkit.org/show_bug.cgi?id=137337 +support.createHTMLDocument = ( function() { + var body = document.implementation.createHTMLDocument( "" ).body; + body.innerHTML = "<form></form><form></form>"; + return body.childNodes.length === 2; +} )(); + + +// Argument "data" should be string of html +// context (optional): If specified, the fragment will be created in this context, +// defaults to document +// keepScripts (optional): If true, will include scripts passed in the html string +jQuery.parseHTML = function( data, context, keepScripts ) { + if ( typeof data !== "string" ) { + return []; + } + if ( typeof context === "boolean" ) { + keepScripts = context; + context = false; + } + + var base, parsed, scripts; + + if ( !context ) { + + // Stop scripts or inline event handlers from being executed immediately + // by using document.implementation + if ( support.createHTMLDocument ) { + context = document.implementation.createHTMLDocument( "" ); + + // Set the base href for the created document + // so any parsed elements with URLs + // are based on the document's URL (gh-2965) + base = context.createElement( "base" ); + base.href = document.location.href; + context.head.appendChild( base ); + } else { + context = document; + } + } + + parsed = rsingleTag.exec( data ); + scripts = !keepScripts && []; + + // Single tag + if ( parsed ) { + return [ context.createElement( parsed[ 1 ] ) ]; + } + + parsed = buildFragment( [ data ], context, scripts ); + + if ( scripts && scripts.length ) { + jQuery( scripts ).remove(); + } + + return jQuery.merge( [], parsed.childNodes ); +}; + + +/** + * Load a url into a page + */ +jQuery.fn.load = function( url, params, callback ) { + var selector, type, response, + self = this, + off = url.indexOf( " " ); + + if ( off > -1 ) { + selector = stripAndCollapse( url.slice( off ) ); + url = url.slice( 0, off ); + } + + // If it's a function + if ( jQuery.isFunction( params ) ) { + + // We assume that it's the callback + callback = params; + params = undefined; + + // Otherwise, build a param string + } else if ( params && typeof params === "object" ) { + type = "POST"; + } + + // If we have elements to modify, make the request + if ( self.length > 0 ) { + jQuery.ajax( { + url: url, + + // If "type" variable is undefined, then "GET" method will be used. + // Make value of this field explicit since + // user can override it through ajaxSetup method + type: type || "GET", + dataType: "html", + data: params + } ).done( function( responseText ) { + + // Save response for use in complete callback + response = arguments; + + self.html( selector ? + + // If a selector was specified, locate the right elements in a dummy div + // Exclude scripts to avoid IE 'Permission Denied' errors + jQuery( "<div>" ).append( jQuery.parseHTML( responseText ) ).find( selector ) : + + // Otherwise use the full result + responseText ); + + // If the request succeeds, this function gets "data", "status", "jqXHR" + // but they are ignored because response was set above. + // If it fails, this function gets "jqXHR", "status", "error" + } ).always( callback && function( jqXHR, status ) { + self.each( function() { + callback.apply( this, response || [ jqXHR.responseText, status, jqXHR ] ); + } ); + } ); + } + + return this; +}; + + + + +// Attach a bunch of functions for handling common AJAX events +jQuery.each( [ + "ajaxStart", + "ajaxStop", + "ajaxComplete", + "ajaxError", + "ajaxSuccess", + "ajaxSend" +], function( i, type ) { + jQuery.fn[ type ] = function( fn ) { + return this.on( type, fn ); + }; +} ); + + + + +jQuery.expr.pseudos.animated = function( elem ) { + return jQuery.grep( jQuery.timers, function( fn ) { + return elem === fn.elem; + } ).length; +}; + + + + +jQuery.offset = { + setOffset: function( elem, options, i ) { + var curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition, + position = jQuery.css( elem, "position" ), + curElem = jQuery( elem ), + props = {}; + + // Set position first, in-case top/left are set even on static elem + if ( position === "static" ) { + elem.style.position = "relative"; + } + + curOffset = curElem.offset(); + curCSSTop = jQuery.css( elem, "top" ); + curCSSLeft = jQuery.css( elem, "left" ); + calculatePosition = ( position === "absolute" || position === "fixed" ) && + ( curCSSTop + curCSSLeft ).indexOf( "auto" ) > -1; + + // Need to be able to calculate position if either + // top or left is auto and position is either absolute or fixed + if ( calculatePosition ) { + curPosition = curElem.position(); + curTop = curPosition.top; + curLeft = curPosition.left; + + } else { + curTop = parseFloat( curCSSTop ) || 0; + curLeft = parseFloat( curCSSLeft ) || 0; + } + + if ( jQuery.isFunction( options ) ) { + + // Use jQuery.extend here to allow modification of coordinates argument (gh-1848) + options = options.call( elem, i, jQuery.extend( {}, curOffset ) ); + } + + if ( options.top != null ) { + props.top = ( options.top - curOffset.top ) + curTop; + } + if ( options.left != null ) { + props.left = ( options.left - curOffset.left ) + curLeft; + } + + if ( "using" in options ) { + options.using.call( elem, props ); + + } else { + curElem.css( props ); + } + } +}; + +jQuery.fn.extend( { + offset: function( options ) { + + // Preserve chaining for setter + if ( arguments.length ) { + return options === undefined ? + this : + this.each( function( i ) { + jQuery.offset.setOffset( this, options, i ); + } ); + } + + var doc, docElem, rect, win, + elem = this[ 0 ]; + + if ( !elem ) { + return; + } + + // Return zeros for disconnected and hidden (display: none) elements (gh-2310) + // Support: IE <=11 only + // Running getBoundingClientRect on a + // disconnected node in IE throws an error + if ( !elem.getClientRects().length ) { + return { top: 0, left: 0 }; + } + + rect = elem.getBoundingClientRect(); + + doc = elem.ownerDocument; + docElem = doc.documentElement; + win = doc.defaultView; + + return { + top: rect.top + win.pageYOffset - docElem.clientTop, + left: rect.left + win.pageXOffset - docElem.clientLeft + }; + }, + + position: function() { + if ( !this[ 0 ] ) { + return; + } + + var offsetParent, offset, + elem = this[ 0 ], + parentOffset = { top: 0, left: 0 }; + + // Fixed elements are offset from window (parentOffset = {top:0, left: 0}, + // because it is its only offset parent + if ( jQuery.css( elem, "position" ) === "fixed" ) { + + // Assume getBoundingClientRect is there when computed position is fixed + offset = elem.getBoundingClientRect(); + + } else { + + // Get *real* offsetParent + offsetParent = this.offsetParent(); + + // Get correct offsets + offset = this.offset(); + if ( !nodeName( offsetParent[ 0 ], "html" ) ) { + parentOffset = offsetParent.offset(); + } + + // Add offsetParent borders + parentOffset = { + top: parentOffset.top + jQuery.css( offsetParent[ 0 ], "borderTopWidth", true ), + left: parentOffset.left + jQuery.css( offsetParent[ 0 ], "borderLeftWidth", true ) + }; + } + + // Subtract parent offsets and element margins + return { + top: offset.top - parentOffset.top - jQuery.css( elem, "marginTop", true ), + left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true ) + }; + }, + + // This method will return documentElement in the following cases: + // 1) For the element inside the iframe without offsetParent, this method will return + // documentElement of the parent window + // 2) For the hidden or detached element + // 3) For body or html element, i.e. in case of the html node - it will return itself + // + // but those exceptions were never presented as a real life use-cases + // and might be considered as more preferable results. + // + // This logic, however, is not guaranteed and can change at any point in the future + offsetParent: function() { + return this.map( function() { + var offsetParent = this.offsetParent; + + while ( offsetParent && jQuery.css( offsetParent, "position" ) === "static" ) { + offsetParent = offsetParent.offsetParent; + } + + return offsetParent || documentElement; + } ); + } +} ); + +// Create scrollLeft and scrollTop methods +jQuery.each( { scrollLeft: "pageXOffset", scrollTop: "pageYOffset" }, function( method, prop ) { + var top = "pageYOffset" === prop; + + jQuery.fn[ method ] = function( val ) { + return access( this, function( elem, method, val ) { + + // Coalesce documents and windows + var win; + if ( jQuery.isWindow( elem ) ) { + win = elem; + } else if ( elem.nodeType === 9 ) { + win = elem.defaultView; + } + + if ( val === undefined ) { + return win ? win[ prop ] : elem[ method ]; + } + + if ( win ) { + win.scrollTo( + !top ? val : win.pageXOffset, + top ? val : win.pageYOffset + ); + + } else { + elem[ method ] = val; + } + }, method, val, arguments.length ); + }; +} ); + +// Support: Safari <=7 - 9.1, Chrome <=37 - 49 +// Add the top/left cssHooks using jQuery.fn.position +// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084 +// Blink bug: https://bugs.chromium.org/p/chromium/issues/detail?id=589347 +// getComputedStyle returns percent when specified for top/left/bottom/right; +// rather than make the css module depend on the offset module, just check for it here +jQuery.each( [ "top", "left" ], function( i, prop ) { + jQuery.cssHooks[ prop ] = addGetHookIf( support.pixelPosition, + function( elem, computed ) { + if ( computed ) { + computed = curCSS( elem, prop ); + + // If curCSS returns percentage, fallback to offset + return rnumnonpx.test( computed ) ? + jQuery( elem ).position()[ prop ] + "px" : + computed; + } + } + ); +} ); + + +// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods +jQuery.each( { Height: "height", Width: "width" }, function( name, type ) { + jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name }, + function( defaultExtra, funcName ) { + + // Margin is only for outerHeight, outerWidth + jQuery.fn[ funcName ] = function( margin, value ) { + var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ), + extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" ); + + return access( this, function( elem, type, value ) { + var doc; + + if ( jQuery.isWindow( elem ) ) { + + // $( window ).outerWidth/Height return w/h including scrollbars (gh-1729) + return funcName.indexOf( "outer" ) === 0 ? + elem[ "inner" + name ] : + elem.document.documentElement[ "client" + name ]; + } + + // Get document width or height + if ( elem.nodeType === 9 ) { + doc = elem.documentElement; + + // Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height], + // whichever is greatest + return Math.max( + elem.body[ "scroll" + name ], doc[ "scroll" + name ], + elem.body[ "offset" + name ], doc[ "offset" + name ], + doc[ "client" + name ] + ); + } + + return value === undefined ? + + // Get width or height on the element, requesting but not forcing parseFloat + jQuery.css( elem, type, extra ) : + + // Set width or height on the element + jQuery.style( elem, type, value, extra ); + }, type, chainable ? margin : undefined, chainable ); + }; + } ); +} ); + + +jQuery.fn.extend( { + + bind: function( types, data, fn ) { + return this.on( types, null, data, fn ); + }, + unbind: function( types, fn ) { + return this.off( types, null, fn ); + }, + + delegate: function( selector, types, data, fn ) { + return this.on( types, selector, data, fn ); + }, + undelegate: function( selector, types, fn ) { + + // ( namespace ) or ( selector, types [, fn] ) + return arguments.length === 1 ? + this.off( selector, "**" ) : + this.off( types, selector || "**", fn ); + } +} ); + +jQuery.holdReady = function( hold ) { + if ( hold ) { + jQuery.readyWait++; + } else { + jQuery.ready( true ); + } +}; +jQuery.isArray = Array.isArray; +jQuery.parseJSON = JSON.parse; +jQuery.nodeName = nodeName; + + + + +// Register as a named AMD module, since jQuery can be concatenated with other +// files that may use define, but not via a proper concatenation script that +// understands anonymous AMD modules. A named AMD is safest and most robust +// way to register. Lowercase jquery is used because AMD module names are +// derived from file names, and jQuery is normally delivered in a lowercase +// file name. Do this after creating the global so that if an AMD module wants +// to call noConflict to hide this version of jQuery, it will work. + +// Note that for maximum portability, libraries that are not jQuery should +// declare themselves as anonymous modules, and avoid setting a global if an +// AMD loader is present. jQuery is a special case. For more information, see +// https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon + +if ( typeof define === "function" && define.amd ) { + define( "jquery", [], function() { + return jQuery; + } ); +} + + + + +var + + // Map over jQuery in case of overwrite + _jQuery = window.jQuery, + + // Map over the $ in case of overwrite + _$ = window.$; + +jQuery.noConflict = function( deep ) { + if ( window.$ === jQuery ) { + window.$ = _$; + } + + if ( deep && window.jQuery === jQuery ) { + window.jQuery = _jQuery; + } + + return jQuery; +}; + +// Expose jQuery and $ identifiers, even in AMD +// (#7102#comment:10, https://github.com/jquery/jquery/pull/557) +// and CommonJS for browser emulators (#13566) +if ( !noGlobal ) { + window.jQuery = window.$ = jQuery; +} + + + + +return jQuery; +} ); diff --git a/docs/readthedocsFiles/_build/html/_static/jquery.js b/docs/readthedocsFiles/_build/html/_static/jquery.js new file mode 100644 index 00000000..644d35e2 --- /dev/null +++ b/docs/readthedocsFiles/_build/html/_static/jquery.js @@ -0,0 +1,4 @@ +/*! jQuery v3.2.1 | (c) JS Foundation and other contributors | jquery.org/license */ +!function(a,b){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){"use strict";var c=[],d=a.document,e=Object.getPrototypeOf,f=c.slice,g=c.concat,h=c.push,i=c.indexOf,j={},k=j.toString,l=j.hasOwnProperty,m=l.toString,n=m.call(Object),o={};function p(a,b){b=b||d;var c=b.createElement("script");c.text=a,b.head.appendChild(c).parentNode.removeChild(c)}var q="3.2.1",r=function(a,b){return new r.fn.init(a,b)},s=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,t=/^-ms-/,u=/-([a-z])/g,v=function(a,b){return b.toUpperCase()};r.fn=r.prototype={jquery:q,constructor:r,length:0,toArray:function(){return f.call(this)},get:function(a){return null==a?f.call(this):a<0?this[a+this.length]:this[a]},pushStack:function(a){var b=r.merge(this.constructor(),a);return b.prevObject=this,b},each:function(a){return r.each(this,a)},map:function(a){return this.pushStack(r.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(f.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(a<0?b:0);return this.pushStack(c>=0&&c<b?[this[c]]:[])},end:function(){return this.prevObject||this.constructor()},push:h,sort:c.sort,splice:c.splice},r.extend=r.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||r.isFunction(g)||(g={}),h===i&&(g=this,h--);h<i;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(r.isPlainObject(d)||(e=Array.isArray(d)))?(e?(e=!1,f=c&&Array.isArray(c)?c:[]):f=c&&r.isPlainObject(c)?c:{},g[b]=r.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},r.extend({expando:"jQuery"+(q+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===r.type(a)},isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){var b=r.type(a);return("number"===b||"string"===b)&&!isNaN(a-parseFloat(a))},isPlainObject:function(a){var b,c;return!(!a||"[object Object]"!==k.call(a))&&(!(b=e(a))||(c=l.call(b,"constructor")&&b.constructor,"function"==typeof c&&m.call(c)===n))},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?j[k.call(a)]||"object":typeof a},globalEval:function(a){p(a)},camelCase:function(a){return a.replace(t,"ms-").replace(u,v)},each:function(a,b){var c,d=0;if(w(a)){for(c=a.length;d<c;d++)if(b.call(a[d],d,a[d])===!1)break}else for(d in a)if(b.call(a[d],d,a[d])===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(s,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(w(Object(a))?r.merge(c,"string"==typeof a?[a]:a):h.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:i.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;d<c;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;f<g;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,e,f=0,h=[];if(w(a))for(d=a.length;f<d;f++)e=b(a[f],f,c),null!=e&&h.push(e);else for(f in a)e=b(a[f],f,c),null!=e&&h.push(e);return g.apply([],h)},guid:1,proxy:function(a,b){var c,d,e;if("string"==typeof b&&(c=a[b],b=a,a=c),r.isFunction(a))return d=f.call(arguments,2),e=function(){return a.apply(b||this,d.concat(f.call(arguments)))},e.guid=a.guid=a.guid||r.guid++,e},now:Date.now,support:o}),"function"==typeof Symbol&&(r.fn[Symbol.iterator]=c[Symbol.iterator]),r.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(a,b){j["[object "+b+"]"]=b.toLowerCase()});function w(a){var b=!!a&&"length"in a&&a.length,c=r.type(a);return"function"!==c&&!r.isWindow(a)&&("array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a)}var x=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=function(a,b){for(var c=0,d=a.length;c<d;c++)if(a[c]===b)return c;return-1},J="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",K="[\\x20\\t\\r\\n\\f]",L="(?:\\\\.|[\\w-]|[^\0-\\xa0])+",M="\\["+K+"*("+L+")(?:"+K+"*([*^$|!~]?=)"+K+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+L+"))|)"+K+"*\\]",N=":("+L+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+M+")*)|.*)\\)|)",O=new RegExp(K+"+","g"),P=new RegExp("^"+K+"+|((?:^|[^\\\\])(?:\\\\.)*)"+K+"+$","g"),Q=new RegExp("^"+K+"*,"+K+"*"),R=new RegExp("^"+K+"*([>+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(N),U=new RegExp("^"+L+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+N),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),aa=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:d<0?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ba=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ca=function(a,b){return b?"\0"===a?"\ufffd":a.slice(0,-1)+"\\"+a.charCodeAt(a.length-1).toString(16)+" ":"\\"+a},da=function(){m()},ea=ta(function(a){return a.disabled===!0&&("form"in a||"label"in a)},{dir:"parentNode",next:"legend"});try{G.apply(D=H.call(v.childNodes),v.childNodes),D[v.childNodes.length].nodeType}catch(fa){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s=b&&b.ownerDocument,w=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==w&&9!==w&&11!==w)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==w&&(l=Z.exec(a)))if(f=l[1]){if(9===w){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(s&&(j=s.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(l[2])return G.apply(d,b.getElementsByTagName(a)),d;if((f=l[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==w)s=b,r=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(ba,ca):b.setAttribute("id",k=u),o=g(a),h=o.length;while(h--)o[h]="#"+k+" "+sa(o[h]);r=o.join(","),s=$.test(a)&&qa(b.parentNode)||b}if(r)try{return G.apply(d,s.querySelectorAll(r)),d}catch(x){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(P,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("fieldset");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&a.sourceIndex-b.sourceIndex;if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return function(b){return"form"in b?b.parentNode&&b.disabled===!1?"label"in b?"label"in b.parentNode?b.parentNode.disabled===a:b.disabled===a:b.isDisabled===a||b.isDisabled!==!a&&ea(b)===a:b.disabled===a:"label"in b&&b.disabled===a}}function pa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function qa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return!!b&&"HTML"!==b.nodeName},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),v!==n&&(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(n.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){return a.getAttribute("id")===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}}):(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c,d,e,f=b.getElementById(a);if(f){if(c=f.getAttributeNode("id"),c&&c.value===a)return[f];e=b.getElementsByName(a),d=0;while(f=e[d++])if(c=f.getAttributeNode("id"),c&&c.value===a)return[f]}return[]}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){if("undefined"!=typeof b.getElementsByClassName&&p)return b.getElementsByClassName(a)},r=[],q=[],(c.qsa=Y.test(n.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="<a id='"+u+"'></a><select id='"+u+"-\r\\' msallowcapture=''><option selected=''></option></select>",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){a.innerHTML="<a href='' disabled='disabled'></a><select disabled='disabled'><option/></select>";var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+K+"*[*^$|!~]?="),2!==a.querySelectorAll(":enabled").length&&q.push(":enabled",":disabled"),o.appendChild(a).disabled=!0,2!==a.querySelectorAll(":disabled").length&&q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Y.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"*"),s.call(a,"[s!='']:x"),r.push("!=",N)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Y.test(o.compareDocumentPosition),t=b||Y.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?I(k,a)-I(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?I(k,a)-I(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?la(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(S,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.escape=function(a){return(a+"").replace(ba,ca)},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(_,aa),a[3]=(a[3]||a[4]||a[5]||"").replace(_,aa),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return V.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&T.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(_,aa).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:!b||(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(O," ")+" ").indexOf(c)>-1:"|="===b&&(e===c||e.slice(0,c.length+1)===c+"-"))}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(P,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(_,aa),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return U.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(_,aa).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:oa(!1),disabled:oa(!0),checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:pa(function(){return[0]}),last:pa(function(a,b){return[b-1]}),eq:pa(function(a,b,c){return[c<0?c+b:c]}),even:pa(function(a,b){for(var c=0;c<b;c+=2)a.push(c);return a}),odd:pa(function(a,b){for(var c=1;c<b;c+=2)a.push(c);return a}),lt:pa(function(a,b,c){for(var d=c<0?c+b:c;--d>=0;)a.push(d);return a}),gt:pa(function(a,b,c){for(var d=c<0?c+b:c;++d<b;)a.push(d);return a})}},d.pseudos.nth=d.pseudos.eq;for(b in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})d.pseudos[b]=ma(b);for(b in{submit:!0,reset:!0})d.pseudos[b]=na(b);function ra(){}ra.prototype=d.filters=d.pseudos,d.setFilters=new ra,g=ga.tokenize=function(a,b){var c,e,f,g,h,i,j,k=z[a+" "];if(k)return b?0:k.slice(0);h=a,i=[],j=d.preFilter;while(h){c&&!(e=Q.exec(h))||(e&&(h=h.slice(e[0].length)||h),i.push(f=[])),c=!1,(e=R.exec(h))&&(c=e.shift(),f.push({value:c,type:e[0].replace(P," ")}),h=h.slice(c.length));for(g in d.filter)!(e=V[g].exec(h))||j[g]&&!(e=j[g](e))||(c=e.shift(),f.push({value:c,type:g,matches:e}),h=h.slice(c.length));if(!c)break}return b?h.length:h?ga.error(a):z(a,i).slice(0)};function sa(a){for(var b=0,c=a.length,d="";b<c;b++)d+=a[b].value;return d}function ta(a,b,c){var d=b.dir,e=b.next,f=e||d,g=c&&"parentNode"===f,h=x++;return b.first?function(b,c,e){while(b=b[d])if(1===b.nodeType||g)return a(b,c,e);return!1}:function(b,c,i){var j,k,l,m=[w,h];if(i){while(b=b[d])if((1===b.nodeType||g)&&a(b,c,i))return!0}else while(b=b[d])if(1===b.nodeType||g)if(l=b[u]||(b[u]={}),k=l[b.uniqueID]||(l[b.uniqueID]={}),e&&e===b.nodeName.toLowerCase())b=b[d]||b;else{if((j=k[f])&&j[0]===w&&j[1]===h)return m[2]=j[2];if(k[f]=m,m[2]=a(b,c,i))return!0}return!1}}function ua(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function va(a,b,c){for(var d=0,e=b.length;d<e;d++)ga(a,b[d],c);return c}function wa(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;h<i;h++)(f=a[h])&&(c&&!c(f,d,e)||(g.push(f),j&&b.push(h)));return g}function xa(a,b,c,d,e,f){return d&&!d[u]&&(d=xa(d)),e&&!e[u]&&(e=xa(e,f)),ia(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||va(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:wa(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=wa(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?I(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=wa(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ya(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ta(function(a){return a===b},h,!0),l=ta(function(a){return I(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];i<f;i++)if(c=d.relative[a[i].type])m=[ta(ua(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;e<f;e++)if(d.relative[a[e].type])break;return xa(i>1&&ua(m),i>1&&sa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(P,"$1"),c,i<e&&ya(a.slice(i,e)),e<f&&ya(a=a.slice(e)),e<f&&sa(a))}m.push(c)}return ua(m)}function za(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=E.call(i));u=wa(u)}G.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&ga.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=ya(b[c]),f[u]?d.push(f):e.push(f);f=A(a,za(e,d)),f.selector=a}return f},i=ga.select=function(a,b,c,e){var f,i,j,k,l,m="function"==typeof a&&a,n=!e&&g(a=m.selector||a);if(c=c||[],1===n.length){if(i=n[0]=n[0].slice(0),i.length>2&&"ID"===(j=i[0]).type&&9===b.nodeType&&p&&d.relative[i[1].type]){if(b=(d.find.ID(j.matches[0].replace(_,aa),b)||[])[0],!b)return c;m&&(b=b.parentNode),a=a.slice(i.shift().value.length)}f=V.needsContext.test(a)?0:i.length;while(f--){if(j=i[f],d.relative[k=j.type])break;if((l=d.find[k])&&(e=l(j.matches[0].replace(_,aa),$.test(i[0].type)&&qa(b.parentNode)||b))){if(i.splice(f,1),a=e.length&&sa(i),!a)return G.apply(c,e),c;break}}}return(m||h(a,n))(e,b,!p,c,!b||$.test(a)&&qa(b.parentNode)||b),c},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("fieldset"))}),ja(function(a){return a.innerHTML="<a href='#'></a>","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){if(!c)return a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="<input/>",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){if(!c&&"input"===a.nodeName.toLowerCase())return a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(J,function(a,b,c){var d;if(!c)return a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);r.find=x,r.expr=x.selectors,r.expr[":"]=r.expr.pseudos,r.uniqueSort=r.unique=x.uniqueSort,r.text=x.getText,r.isXMLDoc=x.isXML,r.contains=x.contains,r.escapeSelector=x.escape;var y=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&r(a).is(c))break;d.push(a)}return d},z=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},A=r.expr.match.needsContext;function B(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()}var C=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i,D=/^.[^:#\[\.,]*$/;function E(a,b,c){return r.isFunction(b)?r.grep(a,function(a,d){return!!b.call(a,d,a)!==c}):b.nodeType?r.grep(a,function(a){return a===b!==c}):"string"!=typeof b?r.grep(a,function(a){return i.call(b,a)>-1!==c}):D.test(b)?r.filter(b,a,c):(b=r.filter(b,a),r.grep(a,function(a){return i.call(b,a)>-1!==c&&1===a.nodeType}))}r.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?r.find.matchesSelector(d,a)?[d]:[]:r.find.matches(a,r.grep(b,function(a){return 1===a.nodeType}))},r.fn.extend({find:function(a){var b,c,d=this.length,e=this;if("string"!=typeof a)return this.pushStack(r(a).filter(function(){for(b=0;b<d;b++)if(r.contains(e[b],this))return!0}));for(c=this.pushStack([]),b=0;b<d;b++)r.find(a,e[b],c);return d>1?r.uniqueSort(c):c},filter:function(a){return this.pushStack(E(this,a||[],!1))},not:function(a){return this.pushStack(E(this,a||[],!0))},is:function(a){return!!E(this,"string"==typeof a&&A.test(a)?r(a):a||[],!1).length}});var F,G=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,H=r.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||F,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:G.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof r?b[0]:b,r.merge(this,r.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),C.test(e[1])&&r.isPlainObject(b))for(e in b)r.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&(this[0]=f,this.length=1),this}return a.nodeType?(this[0]=a,this.length=1,this):r.isFunction(a)?void 0!==c.ready?c.ready(a):a(r):r.makeArray(a,this)};H.prototype=r.fn,F=r(d);var I=/^(?:parents|prev(?:Until|All))/,J={children:!0,contents:!0,next:!0,prev:!0};r.fn.extend({has:function(a){var b=r(a,this),c=b.length;return this.filter(function(){for(var a=0;a<c;a++)if(r.contains(this,b[a]))return!0})},closest:function(a,b){var c,d=0,e=this.length,f=[],g="string"!=typeof a&&r(a);if(!A.test(a))for(;d<e;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&r.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?r.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?i.call(r(a),this[0]):i.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(r.uniqueSort(r.merge(this.get(),r(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function K(a,b){while((a=a[b])&&1!==a.nodeType);return a}r.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return y(a,"parentNode")},parentsUntil:function(a,b,c){return y(a,"parentNode",c)},next:function(a){return K(a,"nextSibling")},prev:function(a){return K(a,"previousSibling")},nextAll:function(a){return y(a,"nextSibling")},prevAll:function(a){return y(a,"previousSibling")},nextUntil:function(a,b,c){return y(a,"nextSibling",c)},prevUntil:function(a,b,c){return y(a,"previousSibling",c)},siblings:function(a){return z((a.parentNode||{}).firstChild,a)},children:function(a){return z(a.firstChild)},contents:function(a){return B(a,"iframe")?a.contentDocument:(B(a,"template")&&(a=a.content||a),r.merge([],a.childNodes))}},function(a,b){r.fn[a]=function(c,d){var e=r.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=r.filter(d,e)),this.length>1&&(J[a]||r.uniqueSort(e),I.test(a)&&e.reverse()),this.pushStack(e)}});var L=/[^\x20\t\r\n\f]+/g;function M(a){var b={};return r.each(a.match(L)||[],function(a,c){b[c]=!0}),b}r.Callbacks=function(a){a="string"==typeof a?M(a):r.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=e||a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h<f.length)f[h].apply(c[0],c[1])===!1&&a.stopOnFalse&&(h=f.length,c=!1)}a.memory||(c=!1),b=!1,e&&(f=c?[]:"")},j={add:function(){return f&&(c&&!b&&(h=f.length-1,g.push(c)),function d(b){r.each(b,function(b,c){r.isFunction(c)?a.unique&&j.has(c)||f.push(c):c&&c.length&&"string"!==r.type(c)&&d(c)})}(arguments),c&&!b&&i()),this},remove:function(){return r.each(arguments,function(a,b){var c;while((c=r.inArray(b,f,c))>-1)f.splice(c,1),c<=h&&h--}),this},has:function(a){return a?r.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||b||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j};function N(a){return a}function O(a){throw a}function P(a,b,c,d){var e;try{a&&r.isFunction(e=a.promise)?e.call(a).done(b).fail(c):a&&r.isFunction(e=a.then)?e.call(a,b,c):b.apply(void 0,[a].slice(d))}catch(a){c.apply(void 0,[a])}}r.extend({Deferred:function(b){var c=[["notify","progress",r.Callbacks("memory"),r.Callbacks("memory"),2],["resolve","done",r.Callbacks("once memory"),r.Callbacks("once memory"),0,"resolved"],["reject","fail",r.Callbacks("once memory"),r.Callbacks("once memory"),1,"rejected"]],d="pending",e={state:function(){return d},always:function(){return f.done(arguments).fail(arguments),this},"catch":function(a){return e.then(null,a)},pipe:function(){var a=arguments;return r.Deferred(function(b){r.each(c,function(c,d){var e=r.isFunction(a[d[4]])&&a[d[4]];f[d[1]](function(){var a=e&&e.apply(this,arguments);a&&r.isFunction(a.promise)?a.promise().progress(b.notify).done(b.resolve).fail(b.reject):b[d[0]+"With"](this,e?[a]:arguments)})}),a=null}).promise()},then:function(b,d,e){var f=0;function g(b,c,d,e){return function(){var h=this,i=arguments,j=function(){var a,j;if(!(b<f)){if(a=d.apply(h,i),a===c.promise())throw new TypeError("Thenable self-resolution");j=a&&("object"==typeof a||"function"==typeof a)&&a.then,r.isFunction(j)?e?j.call(a,g(f,c,N,e),g(f,c,O,e)):(f++,j.call(a,g(f,c,N,e),g(f,c,O,e),g(f,c,N,c.notifyWith))):(d!==N&&(h=void 0,i=[a]),(e||c.resolveWith)(h,i))}},k=e?j:function(){try{j()}catch(a){r.Deferred.exceptionHook&&r.Deferred.exceptionHook(a,k.stackTrace),b+1>=f&&(d!==O&&(h=void 0,i=[a]),c.rejectWith(h,i))}};b?k():(r.Deferred.getStackHook&&(k.stackTrace=r.Deferred.getStackHook()),a.setTimeout(k))}}return r.Deferred(function(a){c[0][3].add(g(0,a,r.isFunction(e)?e:N,a.notifyWith)),c[1][3].add(g(0,a,r.isFunction(b)?b:N)),c[2][3].add(g(0,a,r.isFunction(d)?d:O))}).promise()},promise:function(a){return null!=a?r.extend(a,e):e}},f={};return r.each(c,function(a,b){var g=b[2],h=b[5];e[b[1]]=g.add,h&&g.add(function(){d=h},c[3-a][2].disable,c[0][2].lock),g.add(b[3].fire),f[b[0]]=function(){return f[b[0]+"With"](this===f?void 0:this,arguments),this},f[b[0]+"With"]=g.fireWith}),e.promise(f),b&&b.call(f,f),f},when:function(a){var b=arguments.length,c=b,d=Array(c),e=f.call(arguments),g=r.Deferred(),h=function(a){return function(c){d[a]=this,e[a]=arguments.length>1?f.call(arguments):c,--b||g.resolveWith(d,e)}};if(b<=1&&(P(a,g.done(h(c)).resolve,g.reject,!b),"pending"===g.state()||r.isFunction(e[c]&&e[c].then)))return g.then();while(c--)P(e[c],h(c),g.reject);return g.promise()}});var Q=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;r.Deferred.exceptionHook=function(b,c){a.console&&a.console.warn&&b&&Q.test(b.name)&&a.console.warn("jQuery.Deferred exception: "+b.message,b.stack,c)},r.readyException=function(b){a.setTimeout(function(){throw b})};var R=r.Deferred();r.fn.ready=function(a){return R.then(a)["catch"](function(a){r.readyException(a)}),this},r.extend({isReady:!1,readyWait:1,ready:function(a){(a===!0?--r.readyWait:r.isReady)||(r.isReady=!0,a!==!0&&--r.readyWait>0||R.resolveWith(d,[r]))}}),r.ready.then=R.then;function S(){d.removeEventListener("DOMContentLoaded",S), +a.removeEventListener("load",S),r.ready()}"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(r.ready):(d.addEventListener("DOMContentLoaded",S),a.addEventListener("load",S));var T=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===r.type(c)){e=!0;for(h in c)T(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,r.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(r(a),c)})),b))for(;h<i;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},U=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function V(){this.expando=r.expando+V.uid++}V.uid=1,V.prototype={cache:function(a){var b=a[this.expando];return b||(b={},U(a)&&(a.nodeType?a[this.expando]=b:Object.defineProperty(a,this.expando,{value:b,configurable:!0}))),b},set:function(a,b,c){var d,e=this.cache(a);if("string"==typeof b)e[r.camelCase(b)]=c;else for(d in b)e[r.camelCase(d)]=b[d];return e},get:function(a,b){return void 0===b?this.cache(a):a[this.expando]&&a[this.expando][r.camelCase(b)]},access:function(a,b,c){return void 0===b||b&&"string"==typeof b&&void 0===c?this.get(a,b):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d=a[this.expando];if(void 0!==d){if(void 0!==b){Array.isArray(b)?b=b.map(r.camelCase):(b=r.camelCase(b),b=b in d?[b]:b.match(L)||[]),c=b.length;while(c--)delete d[b[c]]}(void 0===b||r.isEmptyObject(d))&&(a.nodeType?a[this.expando]=void 0:delete a[this.expando])}},hasData:function(a){var b=a[this.expando];return void 0!==b&&!r.isEmptyObject(b)}};var W=new V,X=new V,Y=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,Z=/[A-Z]/g;function $(a){return"true"===a||"false"!==a&&("null"===a?null:a===+a+""?+a:Y.test(a)?JSON.parse(a):a)}function _(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(Z,"-$&").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c=$(c)}catch(e){}X.set(a,b,c)}else c=void 0;return c}r.extend({hasData:function(a){return X.hasData(a)||W.hasData(a)},data:function(a,b,c){return X.access(a,b,c)},removeData:function(a,b){X.remove(a,b)},_data:function(a,b,c){return W.access(a,b,c)},_removeData:function(a,b){W.remove(a,b)}}),r.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=X.get(f),1===f.nodeType&&!W.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=r.camelCase(d.slice(5)),_(f,d,e[d])));W.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){X.set(this,a)}):T(this,function(b){var c;if(f&&void 0===b){if(c=X.get(f,a),void 0!==c)return c;if(c=_(f,a),void 0!==c)return c}else this.each(function(){X.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){X.remove(this,a)})}}),r.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=W.get(a,b),c&&(!d||Array.isArray(c)?d=W.access(a,b,r.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=r.queue(a,b),d=c.length,e=c.shift(),f=r._queueHooks(a,b),g=function(){r.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return W.get(a,c)||W.access(a,c,{empty:r.Callbacks("once memory").add(function(){W.remove(a,[b+"queue",c])})})}}),r.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length<c?r.queue(this[0],a):void 0===b?this:this.each(function(){var c=r.queue(this,a,b);r._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&r.dequeue(this,a)})},dequeue:function(a){return this.each(function(){r.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=r.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=W.get(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}});var aa=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,ba=new RegExp("^(?:([+-])=|)("+aa+")([a-z%]*)$","i"),ca=["Top","Right","Bottom","Left"],da=function(a,b){return a=b||a,"none"===a.style.display||""===a.style.display&&r.contains(a.ownerDocument,a)&&"none"===r.css(a,"display")},ea=function(a,b,c,d){var e,f,g={};for(f in b)g[f]=a.style[f],a.style[f]=b[f];e=c.apply(a,d||[]);for(f in b)a.style[f]=g[f];return e};function fa(a,b,c,d){var e,f=1,g=20,h=d?function(){return d.cur()}:function(){return r.css(a,b,"")},i=h(),j=c&&c[3]||(r.cssNumber[b]?"":"px"),k=(r.cssNumber[b]||"px"!==j&&+i)&&ba.exec(r.css(a,b));if(k&&k[3]!==j){j=j||k[3],c=c||[],k=+i||1;do f=f||".5",k/=f,r.style(a,b,k+j);while(f!==(f=h()/i)&&1!==f&&--g)}return c&&(k=+k||+i||0,e=c[1]?k+(c[1]+1)*c[2]:+c[2],d&&(d.unit=j,d.start=k,d.end=e)),e}var ga={};function ha(a){var b,c=a.ownerDocument,d=a.nodeName,e=ga[d];return e?e:(b=c.body.appendChild(c.createElement(d)),e=r.css(b,"display"),b.parentNode.removeChild(b),"none"===e&&(e="block"),ga[d]=e,e)}function ia(a,b){for(var c,d,e=[],f=0,g=a.length;f<g;f++)d=a[f],d.style&&(c=d.style.display,b?("none"===c&&(e[f]=W.get(d,"display")||null,e[f]||(d.style.display="")),""===d.style.display&&da(d)&&(e[f]=ha(d))):"none"!==c&&(e[f]="none",W.set(d,"display",c)));for(f=0;f<g;f++)null!=e[f]&&(a[f].style.display=e[f]);return a}r.fn.extend({show:function(){return ia(this,!0)},hide:function(){return ia(this)},toggle:function(a){return"boolean"==typeof a?a?this.show():this.hide():this.each(function(){da(this)?r(this).show():r(this).hide()})}});var ja=/^(?:checkbox|radio)$/i,ka=/<([a-z][^\/\0>\x20\t\r\n\f]+)/i,la=/^$|\/(?:java|ecma)script/i,ma={option:[1,"<select multiple='multiple'>","</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};ma.optgroup=ma.option,ma.tbody=ma.tfoot=ma.colgroup=ma.caption=ma.thead,ma.th=ma.td;function na(a,b){var c;return c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[],void 0===b||b&&B(a,b)?r.merge([a],c):c}function oa(a,b){for(var c=0,d=a.length;c<d;c++)W.set(a[c],"globalEval",!b||W.get(b[c],"globalEval"))}var pa=/<|&#?\w+;/;function qa(a,b,c,d,e){for(var f,g,h,i,j,k,l=b.createDocumentFragment(),m=[],n=0,o=a.length;n<o;n++)if(f=a[n],f||0===f)if("object"===r.type(f))r.merge(m,f.nodeType?[f]:f);else if(pa.test(f)){g=g||l.appendChild(b.createElement("div")),h=(ka.exec(f)||["",""])[1].toLowerCase(),i=ma[h]||ma._default,g.innerHTML=i[1]+r.htmlPrefilter(f)+i[2],k=i[0];while(k--)g=g.lastChild;r.merge(m,g.childNodes),g=l.firstChild,g.textContent=""}else m.push(b.createTextNode(f));l.textContent="",n=0;while(f=m[n++])if(d&&r.inArray(f,d)>-1)e&&e.push(f);else if(j=r.contains(f.ownerDocument,f),g=na(l.appendChild(f),"script"),j&&oa(g),c){k=0;while(f=g[k++])la.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),o.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="<textarea>x</textarea>",o.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var ra=d.documentElement,sa=/^key/,ta=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,ua=/^([^.]*)(?:\.(.+)|)/;function va(){return!0}function wa(){return!1}function xa(){try{return d.activeElement}catch(a){}}function ya(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)ya(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=wa;else if(!e)return a;return 1===f&&(g=e,e=function(a){return r().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=r.guid++)),a.each(function(){r.event.add(this,b,e,d,c)})}r.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=W.get(a);if(q){c.handler&&(f=c,c=f.handler,e=f.selector),e&&r.find.matchesSelector(ra,e),c.guid||(c.guid=r.guid++),(i=q.events)||(i=q.events={}),(g=q.handle)||(g=q.handle=function(b){return"undefined"!=typeof r&&r.event.triggered!==b.type?r.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(L)||[""],j=b.length;while(j--)h=ua.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n&&(l=r.event.special[n]||{},n=(e?l.delegateType:l.bindType)||n,l=r.event.special[n]||{},k=r.extend({type:n,origType:p,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&r.expr.match.needsContext.test(e),namespace:o.join(".")},f),(m=i[n])||(m=i[n]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,o,g)!==!1||a.addEventListener&&a.addEventListener(n,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),r.event.global[n]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=W.hasData(a)&&W.get(a);if(q&&(i=q.events)){b=(b||"").match(L)||[""],j=b.length;while(j--)if(h=ua.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n){l=r.event.special[n]||{},n=(d?l.delegateType:l.bindType)||n,m=i[n]||[],h=h[2]&&new RegExp("(^|\\.)"+o.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&p!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,o,q.handle)!==!1||r.removeEvent(a,n,q.handle),delete i[n])}else for(n in i)r.event.remove(a,n+b[j],c,d,!0);r.isEmptyObject(i)&&W.remove(a,"handle events")}},dispatch:function(a){var b=r.event.fix(a),c,d,e,f,g,h,i=new Array(arguments.length),j=(W.get(this,"events")||{})[b.type]||[],k=r.event.special[b.type]||{};for(i[0]=b,c=1;c<arguments.length;c++)i[c]=arguments[c];if(b.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,b)!==!1){h=r.event.handlers.call(this,b,j),c=0;while((f=h[c++])&&!b.isPropagationStopped()){b.currentTarget=f.elem,d=0;while((g=f.handlers[d++])&&!b.isImmediatePropagationStopped())b.rnamespace&&!b.rnamespace.test(g.namespace)||(b.handleObj=g,b.data=g.data,e=((r.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(b.result=e)===!1&&(b.preventDefault(),b.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,b),b.result}},handlers:function(a,b){var c,d,e,f,g,h=[],i=b.delegateCount,j=a.target;if(i&&j.nodeType&&!("click"===a.type&&a.button>=1))for(;j!==this;j=j.parentNode||this)if(1===j.nodeType&&("click"!==a.type||j.disabled!==!0)){for(f=[],g={},c=0;c<i;c++)d=b[c],e=d.selector+" ",void 0===g[e]&&(g[e]=d.needsContext?r(e,this).index(j)>-1:r.find(e,this,null,[j]).length),g[e]&&f.push(d);f.length&&h.push({elem:j,handlers:f})}return j=this,i<b.length&&h.push({elem:j,handlers:b.slice(i)}),h},addProp:function(a,b){Object.defineProperty(r.Event.prototype,a,{enumerable:!0,configurable:!0,get:r.isFunction(b)?function(){if(this.originalEvent)return b(this.originalEvent)}:function(){if(this.originalEvent)return this.originalEvent[a]},set:function(b){Object.defineProperty(this,a,{enumerable:!0,configurable:!0,writable:!0,value:b})}})},fix:function(a){return a[r.expando]?a:new r.Event(a)},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==xa()&&this.focus)return this.focus(),!1},delegateType:"focusin"},blur:{trigger:function(){if(this===xa()&&this.blur)return this.blur(),!1},delegateType:"focusout"},click:{trigger:function(){if("checkbox"===this.type&&this.click&&B(this,"input"))return this.click(),!1},_default:function(a){return B(a.target,"a")}},beforeunload:{postDispatch:function(a){void 0!==a.result&&a.originalEvent&&(a.originalEvent.returnValue=a.result)}}}},r.removeEvent=function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c)},r.Event=function(a,b){return this instanceof r.Event?(a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||void 0===a.defaultPrevented&&a.returnValue===!1?va:wa,this.target=a.target&&3===a.target.nodeType?a.target.parentNode:a.target,this.currentTarget=a.currentTarget,this.relatedTarget=a.relatedTarget):this.type=a,b&&r.extend(this,b),this.timeStamp=a&&a.timeStamp||r.now(),void(this[r.expando]=!0)):new r.Event(a,b)},r.Event.prototype={constructor:r.Event,isDefaultPrevented:wa,isPropagationStopped:wa,isImmediatePropagationStopped:wa,isSimulated:!1,preventDefault:function(){var a=this.originalEvent;this.isDefaultPrevented=va,a&&!this.isSimulated&&a.preventDefault()},stopPropagation:function(){var a=this.originalEvent;this.isPropagationStopped=va,a&&!this.isSimulated&&a.stopPropagation()},stopImmediatePropagation:function(){var a=this.originalEvent;this.isImmediatePropagationStopped=va,a&&!this.isSimulated&&a.stopImmediatePropagation(),this.stopPropagation()}},r.each({altKey:!0,bubbles:!0,cancelable:!0,changedTouches:!0,ctrlKey:!0,detail:!0,eventPhase:!0,metaKey:!0,pageX:!0,pageY:!0,shiftKey:!0,view:!0,"char":!0,charCode:!0,key:!0,keyCode:!0,button:!0,buttons:!0,clientX:!0,clientY:!0,offsetX:!0,offsetY:!0,pointerId:!0,pointerType:!0,screenX:!0,screenY:!0,targetTouches:!0,toElement:!0,touches:!0,which:function(a){var b=a.button;return null==a.which&&sa.test(a.type)?null!=a.charCode?a.charCode:a.keyCode:!a.which&&void 0!==b&&ta.test(a.type)?1&b?1:2&b?3:4&b?2:0:a.which}},r.event.addProp),r.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(a,b){r.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj;return e&&(e===d||r.contains(d,e))||(a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b),c}}}),r.fn.extend({on:function(a,b,c,d){return ya(this,a,b,c,d)},one:function(a,b,c,d){return ya(this,a,b,c,d,1)},off:function(a,b,c){var d,e;if(a&&a.preventDefault&&a.handleObj)return d=a.handleObj,r(a.delegateTarget).off(d.namespace?d.origType+"."+d.namespace:d.origType,d.selector,d.handler),this;if("object"==typeof a){for(e in a)this.off(e,b,a[e]);return this}return b!==!1&&"function"!=typeof b||(c=b,b=void 0),c===!1&&(c=wa),this.each(function(){r.event.remove(this,a,c,b)})}});var za=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi,Aa=/<script|<style|<link/i,Ba=/checked\s*(?:[^=]|=\s*.checked.)/i,Ca=/^true\/(.*)/,Da=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g;function Ea(a,b){return B(a,"table")&&B(11!==b.nodeType?b:b.firstChild,"tr")?r(">tbody",a)[0]||a:a}function Fa(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function Ga(a){var b=Ca.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Ha(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(W.hasData(a)&&(f=W.access(a),g=W.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;c<d;c++)r.event.add(b,e,j[e][c])}X.hasData(a)&&(h=X.access(a),i=r.extend({},h),X.set(b,i))}}function Ia(a,b){var c=b.nodeName.toLowerCase();"input"===c&&ja.test(a.type)?b.checked=a.checked:"input"!==c&&"textarea"!==c||(b.defaultValue=a.defaultValue)}function Ja(a,b,c,d){b=g.apply([],b);var e,f,h,i,j,k,l=0,m=a.length,n=m-1,q=b[0],s=r.isFunction(q);if(s||m>1&&"string"==typeof q&&!o.checkClone&&Ba.test(q))return a.each(function(e){var f=a.eq(e);s&&(b[0]=q.call(this,e,f.html())),Ja(f,b,c,d)});if(m&&(e=qa(b,a[0].ownerDocument,!1,a,d),f=e.firstChild,1===e.childNodes.length&&(e=f),f||d)){for(h=r.map(na(e,"script"),Fa),i=h.length;l<m;l++)j=e,l!==n&&(j=r.clone(j,!0,!0),i&&r.merge(h,na(j,"script"))),c.call(a[l],j,l);if(i)for(k=h[h.length-1].ownerDocument,r.map(h,Ga),l=0;l<i;l++)j=h[l],la.test(j.type||"")&&!W.access(j,"globalEval")&&r.contains(k,j)&&(j.src?r._evalUrl&&r._evalUrl(j.src):p(j.textContent.replace(Da,""),k))}return a}function Ka(a,b,c){for(var d,e=b?r.filter(b,a):a,f=0;null!=(d=e[f]);f++)c||1!==d.nodeType||r.cleanData(na(d)),d.parentNode&&(c&&r.contains(d.ownerDocument,d)&&oa(na(d,"script")),d.parentNode.removeChild(d));return a}r.extend({htmlPrefilter:function(a){return a.replace(za,"<$1></$2>")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=r.contains(a.ownerDocument,a);if(!(o.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||r.isXMLDoc(a)))for(g=na(h),f=na(a),d=0,e=f.length;d<e;d++)Ia(f[d],g[d]);if(b)if(c)for(f=f||na(a),g=g||na(h),d=0,e=f.length;d<e;d++)Ha(f[d],g[d]);else Ha(a,h);return g=na(h,"script"),g.length>0&&oa(g,!i&&na(a,"script")),h},cleanData:function(a){for(var b,c,d,e=r.event.special,f=0;void 0!==(c=a[f]);f++)if(U(c)){if(b=c[W.expando]){if(b.events)for(d in b.events)e[d]?r.event.remove(c,d):r.removeEvent(c,d,b.handle);c[W.expando]=void 0}c[X.expando]&&(c[X.expando]=void 0)}}}),r.fn.extend({detach:function(a){return Ka(this,a,!0)},remove:function(a){return Ka(this,a)},text:function(a){return T(this,function(a){return void 0===a?r.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=a)})},null,a,arguments.length)},append:function(){return Ja(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ea(this,a);b.appendChild(a)}})},prepend:function(){return Ja(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ea(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return Ja(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return Ja(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(r.cleanData(na(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null!=a&&a,b=null==b?a:b,this.map(function(){return r.clone(this,a,b)})},html:function(a){return T(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!Aa.test(a)&&!ma[(ka.exec(a)||["",""])[1].toLowerCase()]){a=r.htmlPrefilter(a);try{for(;c<d;c++)b=this[c]||{},1===b.nodeType&&(r.cleanData(na(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=[];return Ja(this,arguments,function(b){var c=this.parentNode;r.inArray(this,a)<0&&(r.cleanData(na(this)),c&&c.replaceChild(b,this))},a)}}),r.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){r.fn[a]=function(a){for(var c,d=[],e=r(a),f=e.length-1,g=0;g<=f;g++)c=g===f?this:this.clone(!0),r(e[g])[b](c),h.apply(d,c.get());return this.pushStack(d)}});var La=/^margin/,Ma=new RegExp("^("+aa+")(?!px)[a-z%]+$","i"),Na=function(b){var c=b.ownerDocument.defaultView;return c&&c.opener||(c=a),c.getComputedStyle(b)};!function(){function b(){if(i){i.style.cssText="box-sizing:border-box;position:relative;display:block;margin:auto;border:1px;padding:1px;top:1%;width:50%",i.innerHTML="",ra.appendChild(h);var b=a.getComputedStyle(i);c="1%"!==b.top,g="2px"===b.marginLeft,e="4px"===b.width,i.style.marginRight="50%",f="4px"===b.marginRight,ra.removeChild(h),i=null}}var c,e,f,g,h=d.createElement("div"),i=d.createElement("div");i.style&&(i.style.backgroundClip="content-box",i.cloneNode(!0).style.backgroundClip="",o.clearCloneStyle="content-box"===i.style.backgroundClip,h.style.cssText="border:0;width:8px;height:0;top:0;left:-9999px;padding:0;margin-top:1px;position:absolute",h.appendChild(i),r.extend(o,{pixelPosition:function(){return b(),c},boxSizingReliable:function(){return b(),e},pixelMarginRight:function(){return b(),f},reliableMarginLeft:function(){return b(),g}}))}();function Oa(a,b,c){var d,e,f,g,h=a.style;return c=c||Na(a),c&&(g=c.getPropertyValue(b)||c[b],""!==g||r.contains(a.ownerDocument,a)||(g=r.style(a,b)),!o.pixelMarginRight()&&Ma.test(g)&&La.test(b)&&(d=h.width,e=h.minWidth,f=h.maxWidth,h.minWidth=h.maxWidth=h.width=g,g=c.width,h.width=d,h.minWidth=e,h.maxWidth=f)),void 0!==g?g+"":g}function Pa(a,b){return{get:function(){return a()?void delete this.get:(this.get=b).apply(this,arguments)}}}var Qa=/^(none|table(?!-c[ea]).+)/,Ra=/^--/,Sa={position:"absolute",visibility:"hidden",display:"block"},Ta={letterSpacing:"0",fontWeight:"400"},Ua=["Webkit","Moz","ms"],Va=d.createElement("div").style;function Wa(a){if(a in Va)return a;var b=a[0].toUpperCase()+a.slice(1),c=Ua.length;while(c--)if(a=Ua[c]+b,a in Va)return a}function Xa(a){var b=r.cssProps[a];return b||(b=r.cssProps[a]=Wa(a)||a),b}function Ya(a,b,c){var d=ba.exec(b);return d?Math.max(0,d[2]-(c||0))+(d[3]||"px"):b}function Za(a,b,c,d,e){var f,g=0;for(f=c===(d?"border":"content")?4:"width"===b?1:0;f<4;f+=2)"margin"===c&&(g+=r.css(a,c+ca[f],!0,e)),d?("content"===c&&(g-=r.css(a,"padding"+ca[f],!0,e)),"margin"!==c&&(g-=r.css(a,"border"+ca[f]+"Width",!0,e))):(g+=r.css(a,"padding"+ca[f],!0,e),"padding"!==c&&(g+=r.css(a,"border"+ca[f]+"Width",!0,e)));return g}function $a(a,b,c){var d,e=Na(a),f=Oa(a,b,e),g="border-box"===r.css(a,"boxSizing",!1,e);return Ma.test(f)?f:(d=g&&(o.boxSizingReliable()||f===a.style[b]),"auto"===f&&(f=a["offset"+b[0].toUpperCase()+b.slice(1)]),f=parseFloat(f)||0,f+Za(a,b,c||(g?"border":"content"),d,e)+"px")}r.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=Oa(a,"opacity");return""===c?"1":c}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":"cssFloat"},style:function(a,b,c,d){if(a&&3!==a.nodeType&&8!==a.nodeType&&a.style){var e,f,g,h=r.camelCase(b),i=Ra.test(b),j=a.style;return i||(b=Xa(h)),g=r.cssHooks[b]||r.cssHooks[h],void 0===c?g&&"get"in g&&void 0!==(e=g.get(a,!1,d))?e:j[b]:(f=typeof c,"string"===f&&(e=ba.exec(c))&&e[1]&&(c=fa(a,b,e),f="number"),null!=c&&c===c&&("number"===f&&(c+=e&&e[3]||(r.cssNumber[h]?"":"px")),o.clearCloneStyle||""!==c||0!==b.indexOf("background")||(j[b]="inherit"),g&&"set"in g&&void 0===(c=g.set(a,c,d))||(i?j.setProperty(b,c):j[b]=c)),void 0)}},css:function(a,b,c,d){var e,f,g,h=r.camelCase(b),i=Ra.test(b);return i||(b=Xa(h)),g=r.cssHooks[b]||r.cssHooks[h],g&&"get"in g&&(e=g.get(a,!0,c)),void 0===e&&(e=Oa(a,b,d)),"normal"===e&&b in Ta&&(e=Ta[b]),""===c||c?(f=parseFloat(e),c===!0||isFinite(f)?f||0:e):e}}),r.each(["height","width"],function(a,b){r.cssHooks[b]={get:function(a,c,d){if(c)return!Qa.test(r.css(a,"display"))||a.getClientRects().length&&a.getBoundingClientRect().width?$a(a,b,d):ea(a,Sa,function(){return $a(a,b,d)})},set:function(a,c,d){var e,f=d&&Na(a),g=d&&Za(a,b,d,"border-box"===r.css(a,"boxSizing",!1,f),f);return g&&(e=ba.exec(c))&&"px"!==(e[3]||"px")&&(a.style[b]=c,c=r.css(a,b)),Ya(a,c,g)}}}),r.cssHooks.marginLeft=Pa(o.reliableMarginLeft,function(a,b){if(b)return(parseFloat(Oa(a,"marginLeft"))||a.getBoundingClientRect().left-ea(a,{marginLeft:0},function(){return a.getBoundingClientRect().left}))+"px"}),r.each({margin:"",padding:"",border:"Width"},function(a,b){r.cssHooks[a+b]={expand:function(c){for(var d=0,e={},f="string"==typeof c?c.split(" "):[c];d<4;d++)e[a+ca[d]+b]=f[d]||f[d-2]||f[0];return e}},La.test(a)||(r.cssHooks[a+b].set=Ya)}),r.fn.extend({css:function(a,b){return T(this,function(a,b,c){var d,e,f={},g=0;if(Array.isArray(b)){for(d=Na(a),e=b.length;g<e;g++)f[b[g]]=r.css(a,b[g],!1,d);return f}return void 0!==c?r.style(a,b,c):r.css(a,b)},a,b,arguments.length>1)}});function _a(a,b,c,d,e){return new _a.prototype.init(a,b,c,d,e)}r.Tween=_a,_a.prototype={constructor:_a,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||r.easing._default,this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(r.cssNumber[c]?"":"px")},cur:function(){var a=_a.propHooks[this.prop];return a&&a.get?a.get(this):_a.propHooks._default.get(this)},run:function(a){var b,c=_a.propHooks[this.prop];return this.options.duration?this.pos=b=r.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):_a.propHooks._default.set(this),this}},_a.prototype.init.prototype=_a.prototype,_a.propHooks={_default:{get:function(a){var b;return 1!==a.elem.nodeType||null!=a.elem[a.prop]&&null==a.elem.style[a.prop]?a.elem[a.prop]:(b=r.css(a.elem,a.prop,""),b&&"auto"!==b?b:0)},set:function(a){r.fx.step[a.prop]?r.fx.step[a.prop](a):1!==a.elem.nodeType||null==a.elem.style[r.cssProps[a.prop]]&&!r.cssHooks[a.prop]?a.elem[a.prop]=a.now:r.style(a.elem,a.prop,a.now+a.unit)}}},_a.propHooks.scrollTop=_a.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},r.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2},_default:"swing"},r.fx=_a.prototype.init,r.fx.step={};var ab,bb,cb=/^(?:toggle|show|hide)$/,db=/queueHooks$/;function eb(){bb&&(d.hidden===!1&&a.requestAnimationFrame?a.requestAnimationFrame(eb):a.setTimeout(eb,r.fx.interval),r.fx.tick())}function fb(){return a.setTimeout(function(){ab=void 0}),ab=r.now()}function gb(a,b){var c,d=0,e={height:a};for(b=b?1:0;d<4;d+=2-b)c=ca[d],e["margin"+c]=e["padding"+c]=a;return b&&(e.opacity=e.width=a),e}function hb(a,b,c){for(var d,e=(kb.tweeners[b]||[]).concat(kb.tweeners["*"]),f=0,g=e.length;f<g;f++)if(d=e[f].call(c,b,a))return d}function ib(a,b,c){var d,e,f,g,h,i,j,k,l="width"in b||"height"in b,m=this,n={},o=a.style,p=a.nodeType&&da(a),q=W.get(a,"fxshow");c.queue||(g=r._queueHooks(a,"fx"),null==g.unqueued&&(g.unqueued=0,h=g.empty.fire,g.empty.fire=function(){g.unqueued||h()}),g.unqueued++,m.always(function(){m.always(function(){g.unqueued--,r.queue(a,"fx").length||g.empty.fire()})}));for(d in b)if(e=b[d],cb.test(e)){if(delete b[d],f=f||"toggle"===e,e===(p?"hide":"show")){if("show"!==e||!q||void 0===q[d])continue;p=!0}n[d]=q&&q[d]||r.style(a,d)}if(i=!r.isEmptyObject(b),i||!r.isEmptyObject(n)){l&&1===a.nodeType&&(c.overflow=[o.overflow,o.overflowX,o.overflowY],j=q&&q.display,null==j&&(j=W.get(a,"display")),k=r.css(a,"display"),"none"===k&&(j?k=j:(ia([a],!0),j=a.style.display||j,k=r.css(a,"display"),ia([a]))),("inline"===k||"inline-block"===k&&null!=j)&&"none"===r.css(a,"float")&&(i||(m.done(function(){o.display=j}),null==j&&(k=o.display,j="none"===k?"":k)),o.display="inline-block")),c.overflow&&(o.overflow="hidden",m.always(function(){o.overflow=c.overflow[0],o.overflowX=c.overflow[1],o.overflowY=c.overflow[2]})),i=!1;for(d in n)i||(q?"hidden"in q&&(p=q.hidden):q=W.access(a,"fxshow",{display:j}),f&&(q.hidden=!p),p&&ia([a],!0),m.done(function(){p||ia([a]),W.remove(a,"fxshow");for(d in n)r.style(a,d,n[d])})),i=hb(p?q[d]:0,d,m),d in q||(q[d]=i.start,p&&(i.end=i.start,i.start=0))}}function jb(a,b){var c,d,e,f,g;for(c in a)if(d=r.camelCase(c),e=b[d],f=a[c],Array.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=r.cssHooks[d],g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}function kb(a,b,c){var d,e,f=0,g=kb.prefilters.length,h=r.Deferred().always(function(){delete i.elem}),i=function(){if(e)return!1;for(var b=ab||fb(),c=Math.max(0,j.startTime+j.duration-b),d=c/j.duration||0,f=1-d,g=0,i=j.tweens.length;g<i;g++)j.tweens[g].run(f);return h.notifyWith(a,[j,f,c]),f<1&&i?c:(i||h.notifyWith(a,[j,1,0]),h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:r.extend({},b),opts:r.extend(!0,{specialEasing:{},easing:r.easing._default},c),originalProperties:b,originalOptions:c,startTime:ab||fb(),duration:c.duration,tweens:[],createTween:function(b,c){var d=r.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(d),d},stop:function(b){var c=0,d=b?j.tweens.length:0;if(e)return this;for(e=!0;c<d;c++)j.tweens[c].run(1);return b?(h.notifyWith(a,[j,1,0]),h.resolveWith(a,[j,b])):h.rejectWith(a,[j,b]),this}}),k=j.props;for(jb(k,j.opts.specialEasing);f<g;f++)if(d=kb.prefilters[f].call(j,a,k,j.opts))return r.isFunction(d.stop)&&(r._queueHooks(j.elem,j.opts.queue).stop=r.proxy(d.stop,d)),d;return r.map(k,hb,j),r.isFunction(j.opts.start)&&j.opts.start.call(a,j),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always),r.fx.timer(r.extend(i,{elem:a,anim:j,queue:j.opts.queue})),j}r.Animation=r.extend(kb,{tweeners:{"*":[function(a,b){var c=this.createTween(a,b);return fa(c.elem,a,ba.exec(b),c),c}]},tweener:function(a,b){r.isFunction(a)?(b=a,a=["*"]):a=a.match(L);for(var c,d=0,e=a.length;d<e;d++)c=a[d],kb.tweeners[c]=kb.tweeners[c]||[],kb.tweeners[c].unshift(b)},prefilters:[ib],prefilter:function(a,b){b?kb.prefilters.unshift(a):kb.prefilters.push(a)}}),r.speed=function(a,b,c){var d=a&&"object"==typeof a?r.extend({},a):{complete:c||!c&&b||r.isFunction(a)&&a,duration:a,easing:c&&b||b&&!r.isFunction(b)&&b};return r.fx.off?d.duration=0:"number"!=typeof d.duration&&(d.duration in r.fx.speeds?d.duration=r.fx.speeds[d.duration]:d.duration=r.fx.speeds._default),null!=d.queue&&d.queue!==!0||(d.queue="fx"),d.old=d.complete,d.complete=function(){r.isFunction(d.old)&&d.old.call(this),d.queue&&r.dequeue(this,d.queue)},d},r.fn.extend({fadeTo:function(a,b,c,d){return this.filter(da).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=r.isEmptyObject(a),f=r.speed(b,c,d),g=function(){var b=kb(this,r.extend({},a),f);(e||W.get(this,"finish"))&&b.stop(!0)};return g.finish=g,e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,b,c){var d=function(a){var b=a.stop;delete a.stop,b(c)};return"string"!=typeof a&&(c=b,b=a,a=void 0),b&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,e=null!=a&&a+"queueHooks",f=r.timers,g=W.get(this);if(e)g[e]&&g[e].stop&&d(g[e]);else for(e in g)g[e]&&g[e].stop&&db.test(e)&&d(g[e]);for(e=f.length;e--;)f[e].elem!==this||null!=a&&f[e].queue!==a||(f[e].anim.stop(c),b=!1,f.splice(e,1));!b&&c||r.dequeue(this,a)})},finish:function(a){return a!==!1&&(a=a||"fx"),this.each(function(){var b,c=W.get(this),d=c[a+"queue"],e=c[a+"queueHooks"],f=r.timers,g=d?d.length:0;for(c.finish=!0,r.queue(this,a,[]),e&&e.stop&&e.stop.call(this,!0),b=f.length;b--;)f[b].elem===this&&f[b].queue===a&&(f[b].anim.stop(!0),f.splice(b,1));for(b=0;b<g;b++)d[b]&&d[b].finish&&d[b].finish.call(this);delete c.finish})}}),r.each(["toggle","show","hide"],function(a,b){var c=r.fn[b];r.fn[b]=function(a,d,e){return null==a||"boolean"==typeof a?c.apply(this,arguments):this.animate(gb(b,!0),a,d,e)}}),r.each({slideDown:gb("show"),slideUp:gb("hide"),slideToggle:gb("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){r.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),r.timers=[],r.fx.tick=function(){var a,b=0,c=r.timers;for(ab=r.now();b<c.length;b++)a=c[b],a()||c[b]!==a||c.splice(b--,1);c.length||r.fx.stop(),ab=void 0},r.fx.timer=function(a){r.timers.push(a),r.fx.start()},r.fx.interval=13,r.fx.start=function(){bb||(bb=!0,eb())},r.fx.stop=function(){bb=null},r.fx.speeds={slow:600,fast:200,_default:400},r.fn.delay=function(b,c){return b=r.fx?r.fx.speeds[b]||b:b,c=c||"fx",this.queue(c,function(c,d){var e=a.setTimeout(c,b);d.stop=function(){a.clearTimeout(e)}})},function(){var a=d.createElement("input"),b=d.createElement("select"),c=b.appendChild(d.createElement("option"));a.type="checkbox",o.checkOn=""!==a.value,o.optSelected=c.selected,a=d.createElement("input"),a.value="t",a.type="radio",o.radioValue="t"===a.value}();var lb,mb=r.expr.attrHandle;r.fn.extend({attr:function(a,b){return T(this,r.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){r.removeAttr(this,a)})}}),r.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return"undefined"==typeof a.getAttribute?r.prop(a,b,c):(1===f&&r.isXMLDoc(a)||(e=r.attrHooks[b.toLowerCase()]||(r.expr.match.bool.test(b)?lb:void 0)),void 0!==c?null===c?void r.removeAttr(a,b):e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:(a.setAttribute(b,c+""),c):e&&"get"in e&&null!==(d=e.get(a,b))?d:(d=r.find.attr(a,b), +null==d?void 0:d))},attrHooks:{type:{set:function(a,b){if(!o.radioValue&&"radio"===b&&B(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}},removeAttr:function(a,b){var c,d=0,e=b&&b.match(L);if(e&&1===a.nodeType)while(c=e[d++])a.removeAttribute(c)}}),lb={set:function(a,b,c){return b===!1?r.removeAttr(a,c):a.setAttribute(c,c),c}},r.each(r.expr.match.bool.source.match(/\w+/g),function(a,b){var c=mb[b]||r.find.attr;mb[b]=function(a,b,d){var e,f,g=b.toLowerCase();return d||(f=mb[g],mb[g]=e,e=null!=c(a,b,d)?g:null,mb[g]=f),e}});var nb=/^(?:input|select|textarea|button)$/i,ob=/^(?:a|area)$/i;r.fn.extend({prop:function(a,b){return T(this,r.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[r.propFix[a]||a]})}}),r.extend({prop:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return 1===f&&r.isXMLDoc(a)||(b=r.propFix[b]||b,e=r.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=r.find.attr(a,"tabindex");return b?parseInt(b,10):nb.test(a.nodeName)||ob.test(a.nodeName)&&a.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),o.optSelected||(r.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null},set:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}}),r.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){r.propFix[this.toLowerCase()]=this});function pb(a){var b=a.match(L)||[];return b.join(" ")}function qb(a){return a.getAttribute&&a.getAttribute("class")||""}r.fn.extend({addClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).addClass(a.call(this,b,qb(this)))});if("string"==typeof a&&a){b=a.match(L)||[];while(c=this[i++])if(e=qb(c),d=1===c.nodeType&&" "+pb(e)+" "){g=0;while(f=b[g++])d.indexOf(" "+f+" ")<0&&(d+=f+" ");h=pb(d),e!==h&&c.setAttribute("class",h)}}return this},removeClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).removeClass(a.call(this,b,qb(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof a&&a){b=a.match(L)||[];while(c=this[i++])if(e=qb(c),d=1===c.nodeType&&" "+pb(e)+" "){g=0;while(f=b[g++])while(d.indexOf(" "+f+" ")>-1)d=d.replace(" "+f+" "," ");h=pb(d),e!==h&&c.setAttribute("class",h)}}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):r.isFunction(a)?this.each(function(c){r(this).toggleClass(a.call(this,c,qb(this),b),b)}):this.each(function(){var b,d,e,f;if("string"===c){d=0,e=r(this),f=a.match(L)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else void 0!==a&&"boolean"!==c||(b=qb(this),b&&W.set(this,"__className__",b),this.setAttribute&&this.setAttribute("class",b||a===!1?"":W.get(this,"__className__")||""))})},hasClass:function(a){var b,c,d=0;b=" "+a+" ";while(c=this[d++])if(1===c.nodeType&&(" "+pb(qb(c))+" ").indexOf(b)>-1)return!0;return!1}});var rb=/\r/g;r.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=r.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,r(this).val()):a,null==e?e="":"number"==typeof e?e+="":Array.isArray(e)&&(e=r.map(e,function(a){return null==a?"":a+""})),b=r.valHooks[this.type]||r.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=r.valHooks[e.type]||r.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(rb,""):null==c?"":c)}}}),r.extend({valHooks:{option:{get:function(a){var b=r.find.attr(a,"value");return null!=b?b:pb(r.text(a))}},select:{get:function(a){var b,c,d,e=a.options,f=a.selectedIndex,g="select-one"===a.type,h=g?null:[],i=g?f+1:e.length;for(d=f<0?i:g?f:0;d<i;d++)if(c=e[d],(c.selected||d===f)&&!c.disabled&&(!c.parentNode.disabled||!B(c.parentNode,"optgroup"))){if(b=r(c).val(),g)return b;h.push(b)}return h},set:function(a,b){var c,d,e=a.options,f=r.makeArray(b),g=e.length;while(g--)d=e[g],(d.selected=r.inArray(r.valHooks.option.get(d),f)>-1)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),r.each(["radio","checkbox"],function(){r.valHooks[this]={set:function(a,b){if(Array.isArray(b))return a.checked=r.inArray(r(a).val(),b)>-1}},o.checkOn||(r.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var sb=/^(?:focusinfocus|focusoutblur)$/;r.extend(r.event,{trigger:function(b,c,e,f){var g,h,i,j,k,m,n,o=[e||d],p=l.call(b,"type")?b.type:b,q=l.call(b,"namespace")?b.namespace.split("."):[];if(h=i=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!sb.test(p+r.event.triggered)&&(p.indexOf(".")>-1&&(q=p.split("."),p=q.shift(),q.sort()),k=p.indexOf(":")<0&&"on"+p,b=b[r.expando]?b:new r.Event(p,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=q.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:r.makeArray(c,[b]),n=r.event.special[p]||{},f||!n.trigger||n.trigger.apply(e,c)!==!1)){if(!f&&!n.noBubble&&!r.isWindow(e)){for(j=n.delegateType||p,sb.test(j+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),i=h;i===(e.ownerDocument||d)&&o.push(i.defaultView||i.parentWindow||a)}g=0;while((h=o[g++])&&!b.isPropagationStopped())b.type=g>1?j:n.bindType||p,m=(W.get(h,"events")||{})[b.type]&&W.get(h,"handle"),m&&m.apply(h,c),m=k&&h[k],m&&m.apply&&U(h)&&(b.result=m.apply(h,c),b.result===!1&&b.preventDefault());return b.type=p,f||b.isDefaultPrevented()||n._default&&n._default.apply(o.pop(),c)!==!1||!U(e)||k&&r.isFunction(e[p])&&!r.isWindow(e)&&(i=e[k],i&&(e[k]=null),r.event.triggered=p,e[p](),r.event.triggered=void 0,i&&(e[k]=i)),b.result}},simulate:function(a,b,c){var d=r.extend(new r.Event,c,{type:a,isSimulated:!0});r.event.trigger(d,null,b)}}),r.fn.extend({trigger:function(a,b){return this.each(function(){r.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];if(c)return r.event.trigger(a,b,c,!0)}}),r.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(a,b){r.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),r.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),o.focusin="onfocusin"in a,o.focusin||r.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){r.event.simulate(b,a.target,r.event.fix(a))};r.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=W.access(d,b);e||d.addEventListener(a,c,!0),W.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=W.access(d,b)-1;e?W.access(d,b,e):(d.removeEventListener(a,c,!0),W.remove(d,b))}}});var tb=a.location,ub=r.now(),vb=/\?/;r.parseXML=function(b){var c;if(!b||"string"!=typeof b)return null;try{c=(new a.DOMParser).parseFromString(b,"text/xml")}catch(d){c=void 0}return c&&!c.getElementsByTagName("parsererror").length||r.error("Invalid XML: "+b),c};var wb=/\[\]$/,xb=/\r?\n/g,yb=/^(?:submit|button|image|reset|file)$/i,zb=/^(?:input|select|textarea|keygen)/i;function Ab(a,b,c,d){var e;if(Array.isArray(b))r.each(b,function(b,e){c||wb.test(a)?d(a,e):Ab(a+"["+("object"==typeof e&&null!=e?b:"")+"]",e,c,d)});else if(c||"object"!==r.type(b))d(a,b);else for(e in b)Ab(a+"["+e+"]",b[e],c,d)}r.param=function(a,b){var c,d=[],e=function(a,b){var c=r.isFunction(b)?b():b;d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(null==c?"":c)};if(Array.isArray(a)||a.jquery&&!r.isPlainObject(a))r.each(a,function(){e(this.name,this.value)});else for(c in a)Ab(c,a[c],b,e);return d.join("&")},r.fn.extend({serialize:function(){return r.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=r.prop(this,"elements");return a?r.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!r(this).is(":disabled")&&zb.test(this.nodeName)&&!yb.test(a)&&(this.checked||!ja.test(a))}).map(function(a,b){var c=r(this).val();return null==c?null:Array.isArray(c)?r.map(c,function(a){return{name:b.name,value:a.replace(xb,"\r\n")}}):{name:b.name,value:c.replace(xb,"\r\n")}}).get()}});var Bb=/%20/g,Cb=/#.*$/,Db=/([?&])_=[^&]*/,Eb=/^(.*?):[ \t]*([^\r\n]*)$/gm,Fb=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Gb=/^(?:GET|HEAD)$/,Hb=/^\/\//,Ib={},Jb={},Kb="*/".concat("*"),Lb=d.createElement("a");Lb.href=tb.href;function Mb(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(L)||[];if(r.isFunction(c))while(d=f[e++])"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function Nb(a,b,c,d){var e={},f=a===Jb;function g(h){var i;return e[h]=!0,r.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function Ob(a,b){var c,d,e=r.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&r.extend(!0,a,d),a}function Pb(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}if(f)return f!==i[0]&&i.unshift(f),c[f]}function Qb(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}r.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:tb.href,type:"GET",isLocal:Fb.test(tb.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Kb,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":r.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?Ob(Ob(a,r.ajaxSettings),b):Ob(r.ajaxSettings,a)},ajaxPrefilter:Mb(Ib),ajaxTransport:Mb(Jb),ajax:function(b,c){"object"==typeof b&&(c=b,b=void 0),c=c||{};var e,f,g,h,i,j,k,l,m,n,o=r.ajaxSetup({},c),p=o.context||o,q=o.context&&(p.nodeType||p.jquery)?r(p):r.event,s=r.Deferred(),t=r.Callbacks("once memory"),u=o.statusCode||{},v={},w={},x="canceled",y={readyState:0,getResponseHeader:function(a){var b;if(k){if(!h){h={};while(b=Eb.exec(g))h[b[1].toLowerCase()]=b[2]}b=h[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return k?g:null},setRequestHeader:function(a,b){return null==k&&(a=w[a.toLowerCase()]=w[a.toLowerCase()]||a,v[a]=b),this},overrideMimeType:function(a){return null==k&&(o.mimeType=a),this},statusCode:function(a){var b;if(a)if(k)y.always(a[y.status]);else for(b in a)u[b]=[u[b],a[b]];return this},abort:function(a){var b=a||x;return e&&e.abort(b),A(0,b),this}};if(s.promise(y),o.url=((b||o.url||tb.href)+"").replace(Hb,tb.protocol+"//"),o.type=c.method||c.type||o.method||o.type,o.dataTypes=(o.dataType||"*").toLowerCase().match(L)||[""],null==o.crossDomain){j=d.createElement("a");try{j.href=o.url,j.href=j.href,o.crossDomain=Lb.protocol+"//"+Lb.host!=j.protocol+"//"+j.host}catch(z){o.crossDomain=!0}}if(o.data&&o.processData&&"string"!=typeof o.data&&(o.data=r.param(o.data,o.traditional)),Nb(Ib,o,c,y),k)return y;l=r.event&&o.global,l&&0===r.active++&&r.event.trigger("ajaxStart"),o.type=o.type.toUpperCase(),o.hasContent=!Gb.test(o.type),f=o.url.replace(Cb,""),o.hasContent?o.data&&o.processData&&0===(o.contentType||"").indexOf("application/x-www-form-urlencoded")&&(o.data=o.data.replace(Bb,"+")):(n=o.url.slice(f.length),o.data&&(f+=(vb.test(f)?"&":"?")+o.data,delete o.data),o.cache===!1&&(f=f.replace(Db,"$1"),n=(vb.test(f)?"&":"?")+"_="+ub++ +n),o.url=f+n),o.ifModified&&(r.lastModified[f]&&y.setRequestHeader("If-Modified-Since",r.lastModified[f]),r.etag[f]&&y.setRequestHeader("If-None-Match",r.etag[f])),(o.data&&o.hasContent&&o.contentType!==!1||c.contentType)&&y.setRequestHeader("Content-Type",o.contentType),y.setRequestHeader("Accept",o.dataTypes[0]&&o.accepts[o.dataTypes[0]]?o.accepts[o.dataTypes[0]]+("*"!==o.dataTypes[0]?", "+Kb+"; q=0.01":""):o.accepts["*"]);for(m in o.headers)y.setRequestHeader(m,o.headers[m]);if(o.beforeSend&&(o.beforeSend.call(p,y,o)===!1||k))return y.abort();if(x="abort",t.add(o.complete),y.done(o.success),y.fail(o.error),e=Nb(Jb,o,c,y)){if(y.readyState=1,l&&q.trigger("ajaxSend",[y,o]),k)return y;o.async&&o.timeout>0&&(i=a.setTimeout(function(){y.abort("timeout")},o.timeout));try{k=!1,e.send(v,A)}catch(z){if(k)throw z;A(-1,z)}}else A(-1,"No Transport");function A(b,c,d,h){var j,m,n,v,w,x=c;k||(k=!0,i&&a.clearTimeout(i),e=void 0,g=h||"",y.readyState=b>0?4:0,j=b>=200&&b<300||304===b,d&&(v=Pb(o,y,d)),v=Qb(o,v,y,j),j?(o.ifModified&&(w=y.getResponseHeader("Last-Modified"),w&&(r.lastModified[f]=w),w=y.getResponseHeader("etag"),w&&(r.etag[f]=w)),204===b||"HEAD"===o.type?x="nocontent":304===b?x="notmodified":(x=v.state,m=v.data,n=v.error,j=!n)):(n=x,!b&&x||(x="error",b<0&&(b=0))),y.status=b,y.statusText=(c||x)+"",j?s.resolveWith(p,[m,x,y]):s.rejectWith(p,[y,x,n]),y.statusCode(u),u=void 0,l&&q.trigger(j?"ajaxSuccess":"ajaxError",[y,o,j?m:n]),t.fireWith(p,[y,x]),l&&(q.trigger("ajaxComplete",[y,o]),--r.active||r.event.trigger("ajaxStop")))}return y},getJSON:function(a,b,c){return r.get(a,b,c,"json")},getScript:function(a,b){return r.get(a,void 0,b,"script")}}),r.each(["get","post"],function(a,b){r[b]=function(a,c,d,e){return r.isFunction(c)&&(e=e||d,d=c,c=void 0),r.ajax(r.extend({url:a,type:b,dataType:e,data:c,success:d},r.isPlainObject(a)&&a))}}),r._evalUrl=function(a){return r.ajax({url:a,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},r.fn.extend({wrapAll:function(a){var b;return this[0]&&(r.isFunction(a)&&(a=a.call(this[0])),b=r(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this},wrapInner:function(a){return r.isFunction(a)?this.each(function(b){r(this).wrapInner(a.call(this,b))}):this.each(function(){var b=r(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=r.isFunction(a);return this.each(function(c){r(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(a){return this.parent(a).not("body").each(function(){r(this).replaceWith(this.childNodes)}),this}}),r.expr.pseudos.hidden=function(a){return!r.expr.pseudos.visible(a)},r.expr.pseudos.visible=function(a){return!!(a.offsetWidth||a.offsetHeight||a.getClientRects().length)},r.ajaxSettings.xhr=function(){try{return new a.XMLHttpRequest}catch(b){}};var Rb={0:200,1223:204},Sb=r.ajaxSettings.xhr();o.cors=!!Sb&&"withCredentials"in Sb,o.ajax=Sb=!!Sb,r.ajaxTransport(function(b){var c,d;if(o.cors||Sb&&!b.crossDomain)return{send:function(e,f){var g,h=b.xhr();if(h.open(b.type,b.url,b.async,b.username,b.password),b.xhrFields)for(g in b.xhrFields)h[g]=b.xhrFields[g];b.mimeType&&h.overrideMimeType&&h.overrideMimeType(b.mimeType),b.crossDomain||e["X-Requested-With"]||(e["X-Requested-With"]="XMLHttpRequest");for(g in e)h.setRequestHeader(g,e[g]);c=function(a){return function(){c&&(c=d=h.onload=h.onerror=h.onabort=h.onreadystatechange=null,"abort"===a?h.abort():"error"===a?"number"!=typeof h.status?f(0,"error"):f(h.status,h.statusText):f(Rb[h.status]||h.status,h.statusText,"text"!==(h.responseType||"text")||"string"!=typeof h.responseText?{binary:h.response}:{text:h.responseText},h.getAllResponseHeaders()))}},h.onload=c(),d=h.onerror=c("error"),void 0!==h.onabort?h.onabort=d:h.onreadystatechange=function(){4===h.readyState&&a.setTimeout(function(){c&&d()})},c=c("abort");try{h.send(b.hasContent&&b.data||null)}catch(i){if(c)throw i}},abort:function(){c&&c()}}}),r.ajaxPrefilter(function(a){a.crossDomain&&(a.contents.script=!1)}),r.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(a){return r.globalEval(a),a}}}),r.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),r.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(e,f){b=r("<script>").prop({charset:a.scriptCharset,src:a.url}).on("load error",c=function(a){b.remove(),c=null,a&&f("error"===a.type?404:200,a.type)}),d.head.appendChild(b[0])},abort:function(){c&&c()}}}});var Tb=[],Ub=/(=)\?(?=&|$)|\?\?/;r.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=Tb.pop()||r.expando+"_"+ub++;return this[a]=!0,a}}),r.ajaxPrefilter("json jsonp",function(b,c,d){var e,f,g,h=b.jsonp!==!1&&(Ub.test(b.url)?"url":"string"==typeof b.data&&0===(b.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ub.test(b.data)&&"data");if(h||"jsonp"===b.dataTypes[0])return e=b.jsonpCallback=r.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,h?b[h]=b[h].replace(Ub,"$1"+e):b.jsonp!==!1&&(b.url+=(vb.test(b.url)?"&":"?")+b.jsonp+"="+e),b.converters["script json"]=function(){return g||r.error(e+" was not called"),g[0]},b.dataTypes[0]="json",f=a[e],a[e]=function(){g=arguments},d.always(function(){void 0===f?r(a).removeProp(e):a[e]=f,b[e]&&(b.jsonpCallback=c.jsonpCallback,Tb.push(e)),g&&r.isFunction(f)&&f(g[0]),g=f=void 0}),"script"}),o.createHTMLDocument=function(){var a=d.implementation.createHTMLDocument("").body;return a.innerHTML="<form></form><form></form>",2===a.childNodes.length}(),r.parseHTML=function(a,b,c){if("string"!=typeof a)return[];"boolean"==typeof b&&(c=b,b=!1);var e,f,g;return b||(o.createHTMLDocument?(b=d.implementation.createHTMLDocument(""),e=b.createElement("base"),e.href=d.location.href,b.head.appendChild(e)):b=d),f=C.exec(a),g=!c&&[],f?[b.createElement(f[1])]:(f=qa([a],b,g),g&&g.length&&r(g).remove(),r.merge([],f.childNodes))},r.fn.load=function(a,b,c){var d,e,f,g=this,h=a.indexOf(" ");return h>-1&&(d=pb(a.slice(h)),a=a.slice(0,h)),r.isFunction(b)?(c=b,b=void 0):b&&"object"==typeof b&&(e="POST"),g.length>0&&r.ajax({url:a,type:e||"GET",dataType:"html",data:b}).done(function(a){f=arguments,g.html(d?r("<div>").append(r.parseHTML(a)).find(d):a)}).always(c&&function(a,b){g.each(function(){c.apply(this,f||[a.responseText,b,a])})}),this},r.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(a,b){r.fn[b]=function(a){return this.on(b,a)}}),r.expr.pseudos.animated=function(a){return r.grep(r.timers,function(b){return a===b.elem}).length},r.offset={setOffset:function(a,b,c){var d,e,f,g,h,i,j,k=r.css(a,"position"),l=r(a),m={};"static"===k&&(a.style.position="relative"),h=l.offset(),f=r.css(a,"top"),i=r.css(a,"left"),j=("absolute"===k||"fixed"===k)&&(f+i).indexOf("auto")>-1,j?(d=l.position(),g=d.top,e=d.left):(g=parseFloat(f)||0,e=parseFloat(i)||0),r.isFunction(b)&&(b=b.call(a,c,r.extend({},h))),null!=b.top&&(m.top=b.top-h.top+g),null!=b.left&&(m.left=b.left-h.left+e),"using"in b?b.using.call(a,m):l.css(m)}},r.fn.extend({offset:function(a){if(arguments.length)return void 0===a?this:this.each(function(b){r.offset.setOffset(this,a,b)});var b,c,d,e,f=this[0];if(f)return f.getClientRects().length?(d=f.getBoundingClientRect(),b=f.ownerDocument,c=b.documentElement,e=b.defaultView,{top:d.top+e.pageYOffset-c.clientTop,left:d.left+e.pageXOffset-c.clientLeft}):{top:0,left:0}},position:function(){if(this[0]){var a,b,c=this[0],d={top:0,left:0};return"fixed"===r.css(c,"position")?b=c.getBoundingClientRect():(a=this.offsetParent(),b=this.offset(),B(a[0],"html")||(d=a.offset()),d={top:d.top+r.css(a[0],"borderTopWidth",!0),left:d.left+r.css(a[0],"borderLeftWidth",!0)}),{top:b.top-d.top-r.css(c,"marginTop",!0),left:b.left-d.left-r.css(c,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var a=this.offsetParent;while(a&&"static"===r.css(a,"position"))a=a.offsetParent;return a||ra})}}),r.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,b){var c="pageYOffset"===b;r.fn[a]=function(d){return T(this,function(a,d,e){var f;return r.isWindow(a)?f=a:9===a.nodeType&&(f=a.defaultView),void 0===e?f?f[b]:a[d]:void(f?f.scrollTo(c?f.pageXOffset:e,c?e:f.pageYOffset):a[d]=e)},a,d,arguments.length)}}),r.each(["top","left"],function(a,b){r.cssHooks[b]=Pa(o.pixelPosition,function(a,c){if(c)return c=Oa(a,b),Ma.test(c)?r(a).position()[b]+"px":c})}),r.each({Height:"height",Width:"width"},function(a,b){r.each({padding:"inner"+a,content:b,"":"outer"+a},function(c,d){r.fn[d]=function(e,f){var g=arguments.length&&(c||"boolean"!=typeof e),h=c||(e===!0||f===!0?"margin":"border");return T(this,function(b,c,e){var f;return r.isWindow(b)?0===d.indexOf("outer")?b["inner"+a]:b.document.documentElement["client"+a]:9===b.nodeType?(f=b.documentElement,Math.max(b.body["scroll"+a],f["scroll"+a],b.body["offset"+a],f["offset"+a],f["client"+a])):void 0===e?r.css(b,c,h):r.style(b,c,e,h)},b,g?e:void 0,g)}})}),r.fn.extend({bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return 1===arguments.length?this.off(a,"**"):this.off(b,a||"**",c)}}),r.holdReady=function(a){a?r.readyWait++:r.ready(!0)},r.isArray=Array.isArray,r.parseJSON=JSON.parse,r.nodeName=B,"function"==typeof define&&define.amd&&define("jquery",[],function(){return r});var Vb=a.jQuery,Wb=a.$;return r.noConflict=function(b){return a.$===r&&(a.$=Wb),b&&a.jQuery===r&&(a.jQuery=Vb),r},b||(a.jQuery=a.$=r),r}); diff --git a/docs/readthedocsFiles/_build/html/_static/minus.png b/docs/readthedocsFiles/_build/html/_static/minus.png new file mode 100644 index 0000000000000000000000000000000000000000..d96755fdaf8bb2214971e0db9c1fd3077d7c419d GIT binary patch literal 90 zcmeAS@N?(olHy`uVBq!ia0vp^+#t*WBp7;*Yy1LIik>cxAr*|t7R?Mi>2?kWtu=nj kDsEF_5m^0CR;1wuP-*O&G^0G}KYk!hp00i_>zopr08q^qX#fBK literal 0 HcmV?d00001 diff --git a/docs/readthedocsFiles/_build/html/_static/plus.png b/docs/readthedocsFiles/_build/html/_static/plus.png new file mode 100644 index 0000000000000000000000000000000000000000..7107cec93a979b9a5f64843235a16651d563ce2d GIT binary patch literal 90 zcmeAS@N?(olHy`uVBq!ia0vp^+#t*WBp7;*Yy1LIik>cxAr*|t7R?Mi>2?kWtu>-2 m3q%Vub%g%s<8sJhVPMczOq}xhg9DJoz~JfX=d#Wzp$Pyb1r*Kz literal 0 HcmV?d00001 diff --git a/docs/readthedocsFiles/_build/html/_static/pygments.css b/docs/readthedocsFiles/_build/html/_static/pygments.css new file mode 100644 index 00000000..20c4814d --- /dev/null +++ b/docs/readthedocsFiles/_build/html/_static/pygments.css @@ -0,0 +1,69 @@ +.highlight .hll { background-color: #ffffcc } +.highlight { background: #eeffcc; } +.highlight .c { color: #408090; font-style: italic } /* Comment */ +.highlight .err { border: 1px solid #FF0000 } /* Error */ +.highlight .k { color: #007020; font-weight: bold } /* Keyword */ +.highlight .o { color: #666666 } /* Operator */ +.highlight .ch { color: #408090; font-style: italic } /* Comment.Hashbang */ +.highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #007020 } /* Comment.Preproc */ +.highlight .cpf { color: #408090; font-style: italic } /* Comment.PreprocFile */ +.highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */ +.highlight .gd { color: #A00000 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #FF0000 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #00A000 } /* Generic.Inserted */ +.highlight .go { color: #333333 } /* Generic.Output */ +.highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #0044DD } /* Generic.Traceback */ +.highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #007020 } /* Keyword.Pseudo */ +.highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #902000 } /* Keyword.Type */ +.highlight .m { color: #208050 } /* Literal.Number */ +.highlight .s { color: #4070a0 } /* Literal.String */ +.highlight .na { color: #4070a0 } /* Name.Attribute */ +.highlight .nb { color: #007020 } /* Name.Builtin */ +.highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ +.highlight .no { color: #60add5 } /* Name.Constant */ +.highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ +.highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ +.highlight .ne { color: #007020 } /* Name.Exception */ +.highlight .nf { color: #06287e } /* Name.Function */ +.highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ +.highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ +.highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #bb60d5 } /* Name.Variable */ +.highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mb { color: #208050 } /* Literal.Number.Bin */ +.highlight .mf { color: #208050 } /* Literal.Number.Float */ +.highlight .mh { color: #208050 } /* Literal.Number.Hex */ +.highlight .mi { color: #208050 } /* Literal.Number.Integer */ +.highlight .mo { color: #208050 } /* Literal.Number.Oct */ +.highlight .sa { color: #4070a0 } /* Literal.String.Affix */ +.highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ +.highlight .sc { color: #4070a0 } /* Literal.String.Char */ +.highlight .dl { color: #4070a0 } /* Literal.String.Delimiter */ +.highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #4070a0 } /* Literal.String.Double */ +.highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ +.highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ +.highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ +.highlight .sx { color: #c65d09 } /* Literal.String.Other */ +.highlight .sr { color: #235388 } /* Literal.String.Regex */ +.highlight .s1 { color: #4070a0 } /* Literal.String.Single */ +.highlight .ss { color: #517918 } /* Literal.String.Symbol */ +.highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #06287e } /* Name.Function.Magic */ +.highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ +.highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ +.highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ +.highlight .vm { color: #bb60d5 } /* Name.Variable.Magic */ +.highlight .il { color: #208050 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/docs/readthedocsFiles/_build/html/_static/searchtools.js b/docs/readthedocsFiles/_build/html/_static/searchtools.js new file mode 100644 index 00000000..41b83367 --- /dev/null +++ b/docs/readthedocsFiles/_build/html/_static/searchtools.js @@ -0,0 +1,761 @@ +/* + * searchtools.js_t + * ~~~~~~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for the full-text search. + * + * :copyright: Copyright 2007-2018 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + + +/* Non-minified version JS is _stemmer.js if file is provided */ +/** + * Porter Stemmer + */ +var Stemmer = function() { + + var step2list = { + ational: 'ate', + tional: 'tion', + enci: 'ence', + anci: 'ance', + izer: 'ize', + bli: 'ble', + alli: 'al', + entli: 'ent', + eli: 'e', + ousli: 'ous', + ization: 'ize', + ation: 'ate', + ator: 'ate', + alism: 'al', + iveness: 'ive', + fulness: 'ful', + ousness: 'ous', + aliti: 'al', + iviti: 'ive', + biliti: 'ble', + logi: 'log' + }; + + var step3list = { + icate: 'ic', + ative: '', + alize: 'al', + iciti: 'ic', + ical: 'ic', + ful: '', + ness: '' + }; + + var c = "[^aeiou]"; // consonant + var v = "[aeiouy]"; // vowel + var C = c + "[^aeiouy]*"; // consonant sequence + var V = v + "[aeiou]*"; // vowel sequence + + var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0 + var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 + var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 + var s_v = "^(" + C + ")?" + v; // vowel in stem + + this.stemWord = function (w) { + var stem; + var suffix; + var firstch; + var origword = w; + + if (w.length < 3) + return w; + + var re; + var re2; + var re3; + var re4; + + firstch = w.substr(0,1); + if (firstch == "y") + w = firstch.toUpperCase() + w.substr(1); + + // Step 1a + re = /^(.+?)(ss|i)es$/; + re2 = /^(.+?)([^s])s$/; + + if (re.test(w)) + w = w.replace(re,"$1$2"); + else if (re2.test(w)) + w = w.replace(re2,"$1$2"); + + // Step 1b + re = /^(.+?)eed$/; + re2 = /^(.+?)(ed|ing)$/; + if (re.test(w)) { + var fp = re.exec(w); + re = new RegExp(mgr0); + if (re.test(fp[1])) { + re = /.$/; + w = w.replace(re,""); + } + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1]; + re2 = new RegExp(s_v); + if (re2.test(stem)) { + w = stem; + re2 = /(at|bl|iz)$/; + re3 = new RegExp("([^aeiouylsz])\\1$"); + re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re2.test(w)) + w = w + "e"; + else if (re3.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + else if (re4.test(w)) + w = w + "e"; + } + } + + // Step 1c + re = /^(.+?)y$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(s_v); + if (re.test(stem)) + w = stem + "i"; + } + + // Step 2 + re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step2list[suffix]; + } + + // Step 3 + re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step3list[suffix]; + } + + // Step 4 + re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; + re2 = /^(.+?)(s|t)(ion)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + if (re.test(stem)) + w = stem; + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1] + fp[2]; + re2 = new RegExp(mgr1); + if (re2.test(stem)) + w = stem; + } + + // Step 5 + re = /^(.+?)e$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + re2 = new RegExp(meq1); + re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) + w = stem; + } + re = /ll$/; + re2 = new RegExp(mgr1); + if (re.test(w) && re2.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + + // and turn initial Y back to y + if (firstch == "y") + w = firstch.toLowerCase() + w.substr(1); + return w; + } +} + + + +/** + * Simple result scoring code. + */ +var Scorer = { + // Implement the following function to further tweak the score for each result + // The function takes a result array [filename, title, anchor, descr, score] + // and returns the new score. + /* + score: function(result) { + return result[4]; + }, + */ + + // query matches the full name of an object + objNameMatch: 11, + // or matches in the last dotted part of the object name + objPartialMatch: 6, + // Additive scores depending on the priority of the object + objPrio: {0: 15, // used to be importantResults + 1: 5, // used to be objectResults + 2: -5}, // used to be unimportantResults + // Used when the priority is not in the mapping. + objPrioDefault: 0, + + // query found in title + title: 15, + // query found in terms + term: 5 +}; + + + + + +var splitChars = (function() { + var result = {}; + var singles = [96, 180, 187, 191, 215, 247, 749, 885, 903, 907, 909, 930, 1014, 1648, + 1748, 1809, 2416, 2473, 2481, 2526, 2601, 2609, 2612, 2615, 2653, 2702, + 2706, 2729, 2737, 2740, 2857, 2865, 2868, 2910, 2928, 2948, 2961, 2971, + 2973, 3085, 3089, 3113, 3124, 3213, 3217, 3241, 3252, 3295, 3341, 3345, + 3369, 3506, 3516, 3633, 3715, 3721, 3736, 3744, 3748, 3750, 3756, 3761, + 3781, 3912, 4239, 4347, 4681, 4695, 4697, 4745, 4785, 4799, 4801, 4823, + 4881, 5760, 5901, 5997, 6313, 7405, 8024, 8026, 8028, 8030, 8117, 8125, + 8133, 8181, 8468, 8485, 8487, 8489, 8494, 8527, 11311, 11359, 11687, 11695, + 11703, 11711, 11719, 11727, 11735, 12448, 12539, 43010, 43014, 43019, 43587, + 43696, 43713, 64286, 64297, 64311, 64317, 64319, 64322, 64325, 65141]; + var i, j, start, end; + for (i = 0; i < singles.length; i++) { + result[singles[i]] = true; + } + var ranges = [[0, 47], [58, 64], [91, 94], [123, 169], [171, 177], [182, 184], [706, 709], + [722, 735], [741, 747], [751, 879], [888, 889], [894, 901], [1154, 1161], + [1318, 1328], [1367, 1368], [1370, 1376], [1416, 1487], [1515, 1519], [1523, 1568], + [1611, 1631], [1642, 1645], [1750, 1764], [1767, 1773], [1789, 1790], [1792, 1807], + [1840, 1868], [1958, 1968], [1970, 1983], [2027, 2035], [2038, 2041], [2043, 2047], + [2070, 2073], [2075, 2083], [2085, 2087], [2089, 2307], [2362, 2364], [2366, 2383], + [2385, 2391], [2402, 2405], [2419, 2424], [2432, 2436], [2445, 2446], [2449, 2450], + [2483, 2485], [2490, 2492], [2494, 2509], [2511, 2523], [2530, 2533], [2546, 2547], + [2554, 2564], [2571, 2574], [2577, 2578], [2618, 2648], [2655, 2661], [2672, 2673], + [2677, 2692], [2746, 2748], [2750, 2767], [2769, 2783], [2786, 2789], [2800, 2820], + [2829, 2830], [2833, 2834], [2874, 2876], [2878, 2907], [2914, 2917], [2930, 2946], + [2955, 2957], [2966, 2968], [2976, 2978], [2981, 2983], [2987, 2989], [3002, 3023], + [3025, 3045], [3059, 3076], [3130, 3132], [3134, 3159], [3162, 3167], [3170, 3173], + [3184, 3191], [3199, 3204], [3258, 3260], [3262, 3293], [3298, 3301], [3312, 3332], + [3386, 3388], [3390, 3423], [3426, 3429], [3446, 3449], [3456, 3460], [3479, 3481], + [3518, 3519], [3527, 3584], [3636, 3647], [3655, 3663], [3674, 3712], [3717, 3718], + [3723, 3724], [3726, 3731], [3752, 3753], [3764, 3772], [3774, 3775], [3783, 3791], + [3802, 3803], [3806, 3839], [3841, 3871], [3892, 3903], [3949, 3975], [3980, 4095], + [4139, 4158], [4170, 4175], [4182, 4185], [4190, 4192], [4194, 4196], [4199, 4205], + [4209, 4212], [4226, 4237], [4250, 4255], [4294, 4303], [4349, 4351], [4686, 4687], + [4702, 4703], [4750, 4751], [4790, 4791], [4806, 4807], [4886, 4887], [4955, 4968], + [4989, 4991], [5008, 5023], [5109, 5120], [5741, 5742], [5787, 5791], [5867, 5869], + [5873, 5887], [5906, 5919], [5938, 5951], [5970, 5983], [6001, 6015], [6068, 6102], + [6104, 6107], [6109, 6111], [6122, 6127], [6138, 6159], [6170, 6175], [6264, 6271], + [6315, 6319], [6390, 6399], [6429, 6469], [6510, 6511], [6517, 6527], [6572, 6592], + [6600, 6607], [6619, 6655], [6679, 6687], [6741, 6783], [6794, 6799], [6810, 6822], + [6824, 6916], [6964, 6980], [6988, 6991], [7002, 7042], [7073, 7085], [7098, 7167], + [7204, 7231], [7242, 7244], [7294, 7400], [7410, 7423], [7616, 7679], [7958, 7959], + [7966, 7967], [8006, 8007], [8014, 8015], [8062, 8063], [8127, 8129], [8141, 8143], + [8148, 8149], [8156, 8159], [8173, 8177], [8189, 8303], [8306, 8307], [8314, 8318], + [8330, 8335], [8341, 8449], [8451, 8454], [8456, 8457], [8470, 8472], [8478, 8483], + [8506, 8507], [8512, 8516], [8522, 8525], [8586, 9311], [9372, 9449], [9472, 10101], + [10132, 11263], [11493, 11498], [11503, 11516], [11518, 11519], [11558, 11567], + [11622, 11630], [11632, 11647], [11671, 11679], [11743, 11822], [11824, 12292], + [12296, 12320], [12330, 12336], [12342, 12343], [12349, 12352], [12439, 12444], + [12544, 12548], [12590, 12592], [12687, 12689], [12694, 12703], [12728, 12783], + [12800, 12831], [12842, 12880], [12896, 12927], [12938, 12976], [12992, 13311], + [19894, 19967], [40908, 40959], [42125, 42191], [42238, 42239], [42509, 42511], + [42540, 42559], [42592, 42593], [42607, 42622], [42648, 42655], [42736, 42774], + [42784, 42785], [42889, 42890], [42893, 43002], [43043, 43055], [43062, 43071], + [43124, 43137], [43188, 43215], [43226, 43249], [43256, 43258], [43260, 43263], + [43302, 43311], [43335, 43359], [43389, 43395], [43443, 43470], [43482, 43519], + [43561, 43583], [43596, 43599], [43610, 43615], [43639, 43641], [43643, 43647], + [43698, 43700], [43703, 43704], [43710, 43711], [43715, 43738], [43742, 43967], + [44003, 44015], [44026, 44031], [55204, 55215], [55239, 55242], [55292, 55295], + [57344, 63743], [64046, 64047], [64110, 64111], [64218, 64255], [64263, 64274], + [64280, 64284], [64434, 64466], [64830, 64847], [64912, 64913], [64968, 65007], + [65020, 65135], [65277, 65295], [65306, 65312], [65339, 65344], [65371, 65381], + [65471, 65473], [65480, 65481], [65488, 65489], [65496, 65497]]; + for (i = 0; i < ranges.length; i++) { + start = ranges[i][0]; + end = ranges[i][1]; + for (j = start; j <= end; j++) { + result[j] = true; + } + } + return result; +})(); + +function splitQuery(query) { + var result = []; + var start = -1; + for (var i = 0; i < query.length; i++) { + if (splitChars[query.charCodeAt(i)]) { + if (start !== -1) { + result.push(query.slice(start, i)); + start = -1; + } + } else if (start === -1) { + start = i; + } + } + if (start !== -1) { + result.push(query.slice(start)); + } + return result; +} + + + + +/** + * Search Module + */ +var Search = { + + _index : null, + _queued_query : null, + _pulse_status : -1, + + init : function() { + var params = $.getQueryParameters(); + if (params.q) { + var query = params.q[0]; + $('input[name="q"]')[0].value = query; + this.performSearch(query); + } + }, + + loadIndex : function(url) { + $.ajax({type: "GET", url: url, data: null, + dataType: "script", cache: true, + complete: function(jqxhr, textstatus) { + if (textstatus != "success") { + document.getElementById("searchindexloader").src = url; + } + }}); + }, + + setIndex : function(index) { + var q; + this._index = index; + if ((q = this._queued_query) !== null) { + this._queued_query = null; + Search.query(q); + } + }, + + hasIndex : function() { + return this._index !== null; + }, + + deferQuery : function(query) { + this._queued_query = query; + }, + + stopPulse : function() { + this._pulse_status = 0; + }, + + startPulse : function() { + if (this._pulse_status >= 0) + return; + function pulse() { + var i; + Search._pulse_status = (Search._pulse_status + 1) % 4; + var dotString = ''; + for (i = 0; i < Search._pulse_status; i++) + dotString += '.'; + Search.dots.text(dotString); + if (Search._pulse_status > -1) + window.setTimeout(pulse, 500); + } + pulse(); + }, + + /** + * perform a search for something (or wait until index is loaded) + */ + performSearch : function(query) { + // create the required interface elements + this.out = $('#search-results'); + this.title = $('<h2>' + _('Searching') + '</h2>').appendTo(this.out); + this.dots = $('<span></span>').appendTo(this.title); + this.status = $('<p style="display: none"></p>').appendTo(this.out); + this.output = $('<ul class="search"/>').appendTo(this.out); + + $('#search-progress').text(_('Preparing search...')); + this.startPulse(); + + // index already loaded, the browser was quick! + if (this.hasIndex()) + this.query(query); + else + this.deferQuery(query); + }, + + /** + * execute search (requires search index to be loaded) + */ + query : function(query) { + var i; + var stopwords = ["a","and","are","as","at","be","but","by","for","if","in","into","is","it","near","no","not","of","on","or","such","that","the","their","then","there","these","they","this","to","was","will","with"]; + + // stem the searchterms and add them to the correct list + var stemmer = new Stemmer(); + var searchterms = []; + var excluded = []; + var hlterms = []; + var tmp = splitQuery(query); + var objectterms = []; + for (i = 0; i < tmp.length; i++) { + if (tmp[i] !== "") { + objectterms.push(tmp[i].toLowerCase()); + } + + if ($u.indexOf(stopwords, tmp[i].toLowerCase()) != -1 || tmp[i].match(/^\d+$/) || + tmp[i] === "") { + // skip this "word" + continue; + } + // stem the word + var word = stemmer.stemWord(tmp[i].toLowerCase()); + // prevent stemmer from cutting word smaller than two chars + if(word.length < 3 && tmp[i].length >= 3) { + word = tmp[i]; + } + var toAppend; + // select the correct list + if (word[0] == '-') { + toAppend = excluded; + word = word.substr(1); + } + else { + toAppend = searchterms; + hlterms.push(tmp[i].toLowerCase()); + } + // only add if not already in the list + if (!$u.contains(toAppend, word)) + toAppend.push(word); + } + var highlightstring = '?highlight=' + $.urlencode(hlterms.join(" ")); + + // console.debug('SEARCH: searching for:'); + // console.info('required: ', searchterms); + // console.info('excluded: ', excluded); + + // prepare search + var terms = this._index.terms; + var titleterms = this._index.titleterms; + + // array of [filename, title, anchor, descr, score] + var results = []; + $('#search-progress').empty(); + + // lookup as object + for (i = 0; i < objectterms.length; i++) { + var others = [].concat(objectterms.slice(0, i), + objectterms.slice(i+1, objectterms.length)); + results = results.concat(this.performObjectSearch(objectterms[i], others)); + } + + // lookup as search terms in fulltext + results = results.concat(this.performTermsSearch(searchterms, excluded, terms, titleterms)); + + // let the scorer override scores with a custom scoring function + if (Scorer.score) { + for (i = 0; i < results.length; i++) + results[i][4] = Scorer.score(results[i]); + } + + // now sort the results by score (in opposite order of appearance, since the + // display function below uses pop() to retrieve items) and then + // alphabetically + results.sort(function(a, b) { + var left = a[4]; + var right = b[4]; + if (left > right) { + return 1; + } else if (left < right) { + return -1; + } else { + // same score: sort alphabetically + left = a[1].toLowerCase(); + right = b[1].toLowerCase(); + return (left > right) ? -1 : ((left < right) ? 1 : 0); + } + }); + + // for debugging + //Search.lastresults = results.slice(); // a copy + //console.info('search results:', Search.lastresults); + + // print the results + var resultCount = results.length; + function displayNextItem() { + // results left, load the summary and display it + if (results.length) { + var item = results.pop(); + var listItem = $('<li style="display:none"></li>'); + if (DOCUMENTATION_OPTIONS.FILE_SUFFIX === '') { + // dirhtml builder + var dirname = item[0] + '/'; + if (dirname.match(/\/index\/$/)) { + dirname = dirname.substring(0, dirname.length-6); + } else if (dirname == 'index/') { + dirname = ''; + } + listItem.append($('<a/>').attr('href', + DOCUMENTATION_OPTIONS.URL_ROOT + dirname + + highlightstring + item[2]).html(item[1])); + } else { + // normal html builders + listItem.append($('<a/>').attr('href', + item[0] + DOCUMENTATION_OPTIONS.FILE_SUFFIX + + highlightstring + item[2]).html(item[1])); + } + if (item[3]) { + listItem.append($('<span> (' + item[3] + ')</span>')); + Search.output.append(listItem); + listItem.slideDown(5, function() { + displayNextItem(); + }); + } else if (DOCUMENTATION_OPTIONS.HAS_SOURCE) { + var suffix = DOCUMENTATION_OPTIONS.SOURCELINK_SUFFIX; + if (suffix === undefined) { + suffix = '.txt'; + } + $.ajax({url: DOCUMENTATION_OPTIONS.URL_ROOT + '_sources/' + item[5] + (item[5].slice(-suffix.length) === suffix ? '' : suffix), + dataType: "text", + complete: function(jqxhr, textstatus) { + var data = jqxhr.responseText; + if (data !== '' && data !== undefined) { + listItem.append(Search.makeSearchSummary(data, searchterms, hlterms)); + } + Search.output.append(listItem); + listItem.slideDown(5, function() { + displayNextItem(); + }); + }}); + } else { + // no source available, just display title + Search.output.append(listItem); + listItem.slideDown(5, function() { + displayNextItem(); + }); + } + } + // search finished, update title and status message + else { + Search.stopPulse(); + Search.title.text(_('Search Results')); + if (!resultCount) + Search.status.text(_('Your search did not match any documents. Please make sure that all words are spelled correctly and that you\'ve selected enough categories.')); + else + Search.status.text(_('Search finished, found %s page(s) matching the search query.').replace('%s', resultCount)); + Search.status.fadeIn(500); + } + } + displayNextItem(); + }, + + /** + * search for object names + */ + performObjectSearch : function(object, otherterms) { + var filenames = this._index.filenames; + var docnames = this._index.docnames; + var objects = this._index.objects; + var objnames = this._index.objnames; + var titles = this._index.titles; + + var i; + var results = []; + + for (var prefix in objects) { + for (var name in objects[prefix]) { + var fullname = (prefix ? prefix + '.' : '') + name; + if (fullname.toLowerCase().indexOf(object) > -1) { + var score = 0; + var parts = fullname.split('.'); + // check for different match types: exact matches of full name or + // "last name" (i.e. last dotted part) + if (fullname == object || parts[parts.length - 1] == object) { + score += Scorer.objNameMatch; + // matches in last name + } else if (parts[parts.length - 1].indexOf(object) > -1) { + score += Scorer.objPartialMatch; + } + var match = objects[prefix][name]; + var objname = objnames[match[1]][2]; + var title = titles[match[0]]; + // If more than one term searched for, we require other words to be + // found in the name/title/description + if (otherterms.length > 0) { + var haystack = (prefix + ' ' + name + ' ' + + objname + ' ' + title).toLowerCase(); + var allfound = true; + for (i = 0; i < otherterms.length; i++) { + if (haystack.indexOf(otherterms[i]) == -1) { + allfound = false; + break; + } + } + if (!allfound) { + continue; + } + } + var descr = objname + _(', in ') + title; + + var anchor = match[3]; + if (anchor === '') + anchor = fullname; + else if (anchor == '-') + anchor = objnames[match[1]][1] + '-' + fullname; + // add custom score for some objects according to scorer + if (Scorer.objPrio.hasOwnProperty(match[2])) { + score += Scorer.objPrio[match[2]]; + } else { + score += Scorer.objPrioDefault; + } + results.push([docnames[match[0]], fullname, '#'+anchor, descr, score, filenames[match[0]]]); + } + } + } + + return results; + }, + + /** + * search for full-text terms in the index + */ + performTermsSearch : function(searchterms, excluded, terms, titleterms) { + var docnames = this._index.docnames; + var filenames = this._index.filenames; + var titles = this._index.titles; + + var i, j, file; + var fileMap = {}; + var scoreMap = {}; + var results = []; + + // perform the search on the required terms + for (i = 0; i < searchterms.length; i++) { + var word = searchterms[i]; + var files = []; + var _o = [ + {files: terms[word], score: Scorer.term}, + {files: titleterms[word], score: Scorer.title} + ]; + + // no match but word was a required one + if ($u.every(_o, function(o){return o.files === undefined;})) { + break; + } + // found search word in contents + $u.each(_o, function(o) { + var _files = o.files; + if (_files === undefined) + return + + if (_files.length === undefined) + _files = [_files]; + files = files.concat(_files); + + // set score for the word in each file to Scorer.term + for (j = 0; j < _files.length; j++) { + file = _files[j]; + if (!(file in scoreMap)) + scoreMap[file] = {} + scoreMap[file][word] = o.score; + } + }); + + // create the mapping + for (j = 0; j < files.length; j++) { + file = files[j]; + if (file in fileMap) + fileMap[file].push(word); + else + fileMap[file] = [word]; + } + } + + // now check if the files don't contain excluded terms + for (file in fileMap) { + var valid = true; + + // check if all requirements are matched + if (fileMap[file].length != searchterms.length) + continue; + + // ensure that none of the excluded terms is in the search result + for (i = 0; i < excluded.length; i++) { + if (terms[excluded[i]] == file || + titleterms[excluded[i]] == file || + $u.contains(terms[excluded[i]] || [], file) || + $u.contains(titleterms[excluded[i]] || [], file)) { + valid = false; + break; + } + } + + // if we have still a valid result we can add it to the result list + if (valid) { + // select one (max) score for the file. + // for better ranking, we should calculate ranking by using words statistics like basic tf-idf... + var score = $u.max($u.map(fileMap[file], function(w){return scoreMap[file][w]})); + results.push([docnames[file], titles[file], '', null, score, filenames[file]]); + } + } + return results; + }, + + /** + * helper function to return a node containing the + * search summary for a given text. keywords is a list + * of stemmed words, hlwords is the list of normal, unstemmed + * words. the first one is used to find the occurrence, the + * latter for highlighting it. + */ + makeSearchSummary : function(text, keywords, hlwords) { + var textLower = text.toLowerCase(); + var start = 0; + $.each(keywords, function() { + var i = textLower.indexOf(this.toLowerCase()); + if (i > -1) + start = i; + }); + start = Math.max(start - 120, 0); + var excerpt = ((start > 0) ? '...' : '') + + $.trim(text.substr(start, 240)) + + ((start + 240 - text.length) ? '...' : ''); + var rv = $('<div class="context"></div>').text(excerpt); + $.each(hlwords, function() { + rv = rv.highlightText(this, 'highlighted'); + }); + return rv; + } +}; + +$(document).ready(function() { + Search.init(); +}); \ No newline at end of file diff --git a/docs/readthedocsFiles/_build/html/_static/underscore-1.3.1.js b/docs/readthedocsFiles/_build/html/_static/underscore-1.3.1.js new file mode 100644 index 00000000..208d4cd8 --- /dev/null +++ b/docs/readthedocsFiles/_build/html/_static/underscore-1.3.1.js @@ -0,0 +1,999 @@ +// Underscore.js 1.3.1 +// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. +// Underscore is freely distributable under the MIT license. +// Portions of Underscore are inspired or borrowed from Prototype, +// Oliver Steele's Functional, and John Resig's Micro-Templating. +// For all details and documentation: +// http://documentcloud.github.com/underscore + +(function() { + + // Baseline setup + // -------------- + + // Establish the root object, `window` in the browser, or `global` on the server. + var root = this; + + // Save the previous value of the `_` variable. + var previousUnderscore = root._; + + // Establish the object that gets returned to break out of a loop iteration. + var breaker = {}; + + // Save bytes in the minified (but not gzipped) version: + var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; + + // Create quick reference variables for speed access to core prototypes. + var slice = ArrayProto.slice, + unshift = ArrayProto.unshift, + toString = ObjProto.toString, + hasOwnProperty = ObjProto.hasOwnProperty; + + // All **ECMAScript 5** native function implementations that we hope to use + // are declared here. + var + nativeForEach = ArrayProto.forEach, + nativeMap = ArrayProto.map, + nativeReduce = ArrayProto.reduce, + nativeReduceRight = ArrayProto.reduceRight, + nativeFilter = ArrayProto.filter, + nativeEvery = ArrayProto.every, + nativeSome = ArrayProto.some, + nativeIndexOf = ArrayProto.indexOf, + nativeLastIndexOf = ArrayProto.lastIndexOf, + nativeIsArray = Array.isArray, + nativeKeys = Object.keys, + nativeBind = FuncProto.bind; + + // Create a safe reference to the Underscore object for use below. + var _ = function(obj) { return new wrapper(obj); }; + + // Export the Underscore object for **Node.js**, with + // backwards-compatibility for the old `require()` API. If we're in + // the browser, add `_` as a global object via a string identifier, + // for Closure Compiler "advanced" mode. + if (typeof exports !== 'undefined') { + if (typeof module !== 'undefined' && module.exports) { + exports = module.exports = _; + } + exports._ = _; + } else { + root['_'] = _; + } + + // Current version. + _.VERSION = '1.3.1'; + + // Collection Functions + // -------------------- + + // The cornerstone, an `each` implementation, aka `forEach`. + // Handles objects with the built-in `forEach`, arrays, and raw objects. + // Delegates to **ECMAScript 5**'s native `forEach` if available. + var each = _.each = _.forEach = function(obj, iterator, context) { + if (obj == null) return; + if (nativeForEach && obj.forEach === nativeForEach) { + obj.forEach(iterator, context); + } else if (obj.length === +obj.length) { + for (var i = 0, l = obj.length; i < l; i++) { + if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) return; + } + } else { + for (var key in obj) { + if (_.has(obj, key)) { + if (iterator.call(context, obj[key], key, obj) === breaker) return; + } + } + } + }; + + // Return the results of applying the iterator to each element. + // Delegates to **ECMAScript 5**'s native `map` if available. + _.map = _.collect = function(obj, iterator, context) { + var results = []; + if (obj == null) return results; + if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); + each(obj, function(value, index, list) { + results[results.length] = iterator.call(context, value, index, list); + }); + if (obj.length === +obj.length) results.length = obj.length; + return results; + }; + + // **Reduce** builds up a single result from a list of values, aka `inject`, + // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available. + _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { + var initial = arguments.length > 2; + if (obj == null) obj = []; + if (nativeReduce && obj.reduce === nativeReduce) { + if (context) iterator = _.bind(iterator, context); + return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator); + } + each(obj, function(value, index, list) { + if (!initial) { + memo = value; + initial = true; + } else { + memo = iterator.call(context, memo, value, index, list); + } + }); + if (!initial) throw new TypeError('Reduce of empty array with no initial value'); + return memo; + }; + + // The right-associative version of reduce, also known as `foldr`. + // Delegates to **ECMAScript 5**'s native `reduceRight` if available. + _.reduceRight = _.foldr = function(obj, iterator, memo, context) { + var initial = arguments.length > 2; + if (obj == null) obj = []; + if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { + if (context) iterator = _.bind(iterator, context); + return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); + } + var reversed = _.toArray(obj).reverse(); + if (context && !initial) iterator = _.bind(iterator, context); + return initial ? _.reduce(reversed, iterator, memo, context) : _.reduce(reversed, iterator); + }; + + // Return the first value which passes a truth test. Aliased as `detect`. + _.find = _.detect = function(obj, iterator, context) { + var result; + any(obj, function(value, index, list) { + if (iterator.call(context, value, index, list)) { + result = value; + return true; + } + }); + return result; + }; + + // Return all the elements that pass a truth test. + // Delegates to **ECMAScript 5**'s native `filter` if available. + // Aliased as `select`. + _.filter = _.select = function(obj, iterator, context) { + var results = []; + if (obj == null) return results; + if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context); + each(obj, function(value, index, list) { + if (iterator.call(context, value, index, list)) results[results.length] = value; + }); + return results; + }; + + // Return all the elements for which a truth test fails. + _.reject = function(obj, iterator, context) { + var results = []; + if (obj == null) return results; + each(obj, function(value, index, list) { + if (!iterator.call(context, value, index, list)) results[results.length] = value; + }); + return results; + }; + + // Determine whether all of the elements match a truth test. + // Delegates to **ECMAScript 5**'s native `every` if available. + // Aliased as `all`. + _.every = _.all = function(obj, iterator, context) { + var result = true; + if (obj == null) return result; + if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context); + each(obj, function(value, index, list) { + if (!(result = result && iterator.call(context, value, index, list))) return breaker; + }); + return result; + }; + + // Determine if at least one element in the object matches a truth test. + // Delegates to **ECMAScript 5**'s native `some` if available. + // Aliased as `any`. + var any = _.some = _.any = function(obj, iterator, context) { + iterator || (iterator = _.identity); + var result = false; + if (obj == null) return result; + if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context); + each(obj, function(value, index, list) { + if (result || (result = iterator.call(context, value, index, list))) return breaker; + }); + return !!result; + }; + + // Determine if a given value is included in the array or object using `===`. + // Aliased as `contains`. + _.include = _.contains = function(obj, target) { + var found = false; + if (obj == null) return found; + if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; + found = any(obj, function(value) { + return value === target; + }); + return found; + }; + + // Invoke a method (with arguments) on every item in a collection. + _.invoke = function(obj, method) { + var args = slice.call(arguments, 2); + return _.map(obj, function(value) { + return (_.isFunction(method) ? method || value : value[method]).apply(value, args); + }); + }; + + // Convenience version of a common use case of `map`: fetching a property. + _.pluck = function(obj, key) { + return _.map(obj, function(value){ return value[key]; }); + }; + + // Return the maximum element or (element-based computation). + _.max = function(obj, iterator, context) { + if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj); + if (!iterator && _.isEmpty(obj)) return -Infinity; + var result = {computed : -Infinity}; + each(obj, function(value, index, list) { + var computed = iterator ? iterator.call(context, value, index, list) : value; + computed >= result.computed && (result = {value : value, computed : computed}); + }); + return result.value; + }; + + // Return the minimum element (or element-based computation). + _.min = function(obj, iterator, context) { + if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj); + if (!iterator && _.isEmpty(obj)) return Infinity; + var result = {computed : Infinity}; + each(obj, function(value, index, list) { + var computed = iterator ? iterator.call(context, value, index, list) : value; + computed < result.computed && (result = {value : value, computed : computed}); + }); + return result.value; + }; + + // Shuffle an array. + _.shuffle = function(obj) { + var shuffled = [], rand; + each(obj, function(value, index, list) { + if (index == 0) { + shuffled[0] = value; + } else { + rand = Math.floor(Math.random() * (index + 1)); + shuffled[index] = shuffled[rand]; + shuffled[rand] = value; + } + }); + return shuffled; + }; + + // Sort the object's values by a criterion produced by an iterator. + _.sortBy = function(obj, iterator, context) { + return _.pluck(_.map(obj, function(value, index, list) { + return { + value : value, + criteria : iterator.call(context, value, index, list) + }; + }).sort(function(left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + }), 'value'); + }; + + // Groups the object's values by a criterion. Pass either a string attribute + // to group by, or a function that returns the criterion. + _.groupBy = function(obj, val) { + var result = {}; + var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; }; + each(obj, function(value, index) { + var key = iterator(value, index); + (result[key] || (result[key] = [])).push(value); + }); + return result; + }; + + // Use a comparator function to figure out at what index an object should + // be inserted so as to maintain order. Uses binary search. + _.sortedIndex = function(array, obj, iterator) { + iterator || (iterator = _.identity); + var low = 0, high = array.length; + while (low < high) { + var mid = (low + high) >> 1; + iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid; + } + return low; + }; + + // Safely convert anything iterable into a real, live array. + _.toArray = function(iterable) { + if (!iterable) return []; + if (iterable.toArray) return iterable.toArray(); + if (_.isArray(iterable)) return slice.call(iterable); + if (_.isArguments(iterable)) return slice.call(iterable); + return _.values(iterable); + }; + + // Return the number of elements in an object. + _.size = function(obj) { + return _.toArray(obj).length; + }; + + // Array Functions + // --------------- + + // Get the first element of an array. Passing **n** will return the first N + // values in the array. Aliased as `head`. The **guard** check allows it to work + // with `_.map`. + _.first = _.head = function(array, n, guard) { + return (n != null) && !guard ? slice.call(array, 0, n) : array[0]; + }; + + // Returns everything but the last entry of the array. Especcialy useful on + // the arguments object. Passing **n** will return all the values in + // the array, excluding the last N. The **guard** check allows it to work with + // `_.map`. + _.initial = function(array, n, guard) { + return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n)); + }; + + // Get the last element of an array. Passing **n** will return the last N + // values in the array. The **guard** check allows it to work with `_.map`. + _.last = function(array, n, guard) { + if ((n != null) && !guard) { + return slice.call(array, Math.max(array.length - n, 0)); + } else { + return array[array.length - 1]; + } + }; + + // Returns everything but the first entry of the array. Aliased as `tail`. + // Especially useful on the arguments object. Passing an **index** will return + // the rest of the values in the array from that index onward. The **guard** + // check allows it to work with `_.map`. + _.rest = _.tail = function(array, index, guard) { + return slice.call(array, (index == null) || guard ? 1 : index); + }; + + // Trim out all falsy values from an array. + _.compact = function(array) { + return _.filter(array, function(value){ return !!value; }); + }; + + // Return a completely flattened version of an array. + _.flatten = function(array, shallow) { + return _.reduce(array, function(memo, value) { + if (_.isArray(value)) return memo.concat(shallow ? value : _.flatten(value)); + memo[memo.length] = value; + return memo; + }, []); + }; + + // Return a version of the array that does not contain the specified value(s). + _.without = function(array) { + return _.difference(array, slice.call(arguments, 1)); + }; + + // Produce a duplicate-free version of the array. If the array has already + // been sorted, you have the option of using a faster algorithm. + // Aliased as `unique`. + _.uniq = _.unique = function(array, isSorted, iterator) { + var initial = iterator ? _.map(array, iterator) : array; + var result = []; + _.reduce(initial, function(memo, el, i) { + if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) { + memo[memo.length] = el; + result[result.length] = array[i]; + } + return memo; + }, []); + return result; + }; + + // Produce an array that contains the union: each distinct element from all of + // the passed-in arrays. + _.union = function() { + return _.uniq(_.flatten(arguments, true)); + }; + + // Produce an array that contains every item shared between all the + // passed-in arrays. (Aliased as "intersect" for back-compat.) + _.intersection = _.intersect = function(array) { + var rest = slice.call(arguments, 1); + return _.filter(_.uniq(array), function(item) { + return _.every(rest, function(other) { + return _.indexOf(other, item) >= 0; + }); + }); + }; + + // Take the difference between one array and a number of other arrays. + // Only the elements present in just the first array will remain. + _.difference = function(array) { + var rest = _.flatten(slice.call(arguments, 1)); + return _.filter(array, function(value){ return !_.include(rest, value); }); + }; + + // Zip together multiple lists into a single array -- elements that share + // an index go together. + _.zip = function() { + var args = slice.call(arguments); + var length = _.max(_.pluck(args, 'length')); + var results = new Array(length); + for (var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i); + return results; + }; + + // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**), + // we need this function. Return the position of the first occurrence of an + // item in an array, or -1 if the item is not included in the array. + // Delegates to **ECMAScript 5**'s native `indexOf` if available. + // If the array is large and already in sort order, pass `true` + // for **isSorted** to use binary search. + _.indexOf = function(array, item, isSorted) { + if (array == null) return -1; + var i, l; + if (isSorted) { + i = _.sortedIndex(array, item); + return array[i] === item ? i : -1; + } + if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item); + for (i = 0, l = array.length; i < l; i++) if (i in array && array[i] === item) return i; + return -1; + }; + + // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. + _.lastIndexOf = function(array, item) { + if (array == null) return -1; + if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item); + var i = array.length; + while (i--) if (i in array && array[i] === item) return i; + return -1; + }; + + // Generate an integer Array containing an arithmetic progression. A port of + // the native Python `range()` function. See + // [the Python documentation](http://docs.python.org/library/functions.html#range). + _.range = function(start, stop, step) { + if (arguments.length <= 1) { + stop = start || 0; + start = 0; + } + step = arguments[2] || 1; + + var len = Math.max(Math.ceil((stop - start) / step), 0); + var idx = 0; + var range = new Array(len); + + while(idx < len) { + range[idx++] = start; + start += step; + } + + return range; + }; + + // Function (ahem) Functions + // ------------------ + + // Reusable constructor function for prototype setting. + var ctor = function(){}; + + // Create a function bound to a given object (assigning `this`, and arguments, + // optionally). Binding with arguments is also known as `curry`. + // Delegates to **ECMAScript 5**'s native `Function.bind` if available. + // We check for `func.bind` first, to fail fast when `func` is undefined. + _.bind = function bind(func, context) { + var bound, args; + if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); + if (!_.isFunction(func)) throw new TypeError; + args = slice.call(arguments, 2); + return bound = function() { + if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments))); + ctor.prototype = func.prototype; + var self = new ctor; + var result = func.apply(self, args.concat(slice.call(arguments))); + if (Object(result) === result) return result; + return self; + }; + }; + + // Bind all of an object's methods to that object. Useful for ensuring that + // all callbacks defined on an object belong to it. + _.bindAll = function(obj) { + var funcs = slice.call(arguments, 1); + if (funcs.length == 0) funcs = _.functions(obj); + each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); + return obj; + }; + + // Memoize an expensive function by storing its results. + _.memoize = function(func, hasher) { + var memo = {}; + hasher || (hasher = _.identity); + return function() { + var key = hasher.apply(this, arguments); + return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments)); + }; + }; + + // Delays a function for the given number of milliseconds, and then calls + // it with the arguments supplied. + _.delay = function(func, wait) { + var args = slice.call(arguments, 2); + return setTimeout(function(){ return func.apply(func, args); }, wait); + }; + + // Defers a function, scheduling it to run after the current call stack has + // cleared. + _.defer = function(func) { + return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); + }; + + // Returns a function, that, when invoked, will only be triggered at most once + // during a given window of time. + _.throttle = function(func, wait) { + var context, args, timeout, throttling, more; + var whenDone = _.debounce(function(){ more = throttling = false; }, wait); + return function() { + context = this; args = arguments; + var later = function() { + timeout = null; + if (more) func.apply(context, args); + whenDone(); + }; + if (!timeout) timeout = setTimeout(later, wait); + if (throttling) { + more = true; + } else { + func.apply(context, args); + } + whenDone(); + throttling = true; + }; + }; + + // Returns a function, that, as long as it continues to be invoked, will not + // be triggered. The function will be called after it stops being called for + // N milliseconds. + _.debounce = function(func, wait) { + var timeout; + return function() { + var context = this, args = arguments; + var later = function() { + timeout = null; + func.apply(context, args); + }; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + }; + }; + + // Returns a function that will be executed at most one time, no matter how + // often you call it. Useful for lazy initialization. + _.once = function(func) { + var ran = false, memo; + return function() { + if (ran) return memo; + ran = true; + return memo = func.apply(this, arguments); + }; + }; + + // Returns the first function passed as an argument to the second, + // allowing you to adjust arguments, run code before and after, and + // conditionally execute the original function. + _.wrap = function(func, wrapper) { + return function() { + var args = [func].concat(slice.call(arguments, 0)); + return wrapper.apply(this, args); + }; + }; + + // Returns a function that is the composition of a list of functions, each + // consuming the return value of the function that follows. + _.compose = function() { + var funcs = arguments; + return function() { + var args = arguments; + for (var i = funcs.length - 1; i >= 0; i--) { + args = [funcs[i].apply(this, args)]; + } + return args[0]; + }; + }; + + // Returns a function that will only be executed after being called N times. + _.after = function(times, func) { + if (times <= 0) return func(); + return function() { + if (--times < 1) { return func.apply(this, arguments); } + }; + }; + + // Object Functions + // ---------------- + + // Retrieve the names of an object's properties. + // Delegates to **ECMAScript 5**'s native `Object.keys` + _.keys = nativeKeys || function(obj) { + if (obj !== Object(obj)) throw new TypeError('Invalid object'); + var keys = []; + for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key; + return keys; + }; + + // Retrieve the values of an object's properties. + _.values = function(obj) { + return _.map(obj, _.identity); + }; + + // Return a sorted list of the function names available on the object. + // Aliased as `methods` + _.functions = _.methods = function(obj) { + var names = []; + for (var key in obj) { + if (_.isFunction(obj[key])) names.push(key); + } + return names.sort(); + }; + + // Extend a given object with all the properties in passed-in object(s). + _.extend = function(obj) { + each(slice.call(arguments, 1), function(source) { + for (var prop in source) { + obj[prop] = source[prop]; + } + }); + return obj; + }; + + // Fill in a given object with default properties. + _.defaults = function(obj) { + each(slice.call(arguments, 1), function(source) { + for (var prop in source) { + if (obj[prop] == null) obj[prop] = source[prop]; + } + }); + return obj; + }; + + // Create a (shallow-cloned) duplicate of an object. + _.clone = function(obj) { + if (!_.isObject(obj)) return obj; + return _.isArray(obj) ? obj.slice() : _.extend({}, obj); + }; + + // Invokes interceptor with the obj, and then returns obj. + // The primary purpose of this method is to "tap into" a method chain, in + // order to perform operations on intermediate results within the chain. + _.tap = function(obj, interceptor) { + interceptor(obj); + return obj; + }; + + // Internal recursive comparison function. + function eq(a, b, stack) { + // Identical objects are equal. `0 === -0`, but they aren't identical. + // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal. + if (a === b) return a !== 0 || 1 / a == 1 / b; + // A strict comparison is necessary because `null == undefined`. + if (a == null || b == null) return a === b; + // Unwrap any wrapped objects. + if (a._chain) a = a._wrapped; + if (b._chain) b = b._wrapped; + // Invoke a custom `isEqual` method if one is provided. + if (a.isEqual && _.isFunction(a.isEqual)) return a.isEqual(b); + if (b.isEqual && _.isFunction(b.isEqual)) return b.isEqual(a); + // Compare `[[Class]]` names. + var className = toString.call(a); + if (className != toString.call(b)) return false; + switch (className) { + // Strings, numbers, dates, and booleans are compared by value. + case '[object String]': + // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is + // equivalent to `new String("5")`. + return a == String(b); + case '[object Number]': + // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for + // other numeric values. + return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b); + case '[object Date]': + case '[object Boolean]': + // Coerce dates and booleans to numeric primitive values. Dates are compared by their + // millisecond representations. Note that invalid dates with millisecond representations + // of `NaN` are not equivalent. + return +a == +b; + // RegExps are compared by their source patterns and flags. + case '[object RegExp]': + return a.source == b.source && + a.global == b.global && + a.multiline == b.multiline && + a.ignoreCase == b.ignoreCase; + } + if (typeof a != 'object' || typeof b != 'object') return false; + // Assume equality for cyclic structures. The algorithm for detecting cyclic + // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. + var length = stack.length; + while (length--) { + // Linear search. Performance is inversely proportional to the number of + // unique nested structures. + if (stack[length] == a) return true; + } + // Add the first object to the stack of traversed objects. + stack.push(a); + var size = 0, result = true; + // Recursively compare objects and arrays. + if (className == '[object Array]') { + // Compare array lengths to determine if a deep comparison is necessary. + size = a.length; + result = size == b.length; + if (result) { + // Deep compare the contents, ignoring non-numeric properties. + while (size--) { + // Ensure commutative equality for sparse arrays. + if (!(result = size in a == size in b && eq(a[size], b[size], stack))) break; + } + } + } else { + // Objects with different constructors are not equivalent. + if ('constructor' in a != 'constructor' in b || a.constructor != b.constructor) return false; + // Deep compare objects. + for (var key in a) { + if (_.has(a, key)) { + // Count the expected number of properties. + size++; + // Deep compare each member. + if (!(result = _.has(b, key) && eq(a[key], b[key], stack))) break; + } + } + // Ensure that both objects contain the same number of properties. + if (result) { + for (key in b) { + if (_.has(b, key) && !(size--)) break; + } + result = !size; + } + } + // Remove the first object from the stack of traversed objects. + stack.pop(); + return result; + } + + // Perform a deep comparison to check if two objects are equal. + _.isEqual = function(a, b) { + return eq(a, b, []); + }; + + // Is a given array, string, or object empty? + // An "empty" object has no enumerable own-properties. + _.isEmpty = function(obj) { + if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; + for (var key in obj) if (_.has(obj, key)) return false; + return true; + }; + + // Is a given value a DOM element? + _.isElement = function(obj) { + return !!(obj && obj.nodeType == 1); + }; + + // Is a given value an array? + // Delegates to ECMA5's native Array.isArray + _.isArray = nativeIsArray || function(obj) { + return toString.call(obj) == '[object Array]'; + }; + + // Is a given variable an object? + _.isObject = function(obj) { + return obj === Object(obj); + }; + + // Is a given variable an arguments object? + _.isArguments = function(obj) { + return toString.call(obj) == '[object Arguments]'; + }; + if (!_.isArguments(arguments)) { + _.isArguments = function(obj) { + return !!(obj && _.has(obj, 'callee')); + }; + } + + // Is a given value a function? + _.isFunction = function(obj) { + return toString.call(obj) == '[object Function]'; + }; + + // Is a given value a string? + _.isString = function(obj) { + return toString.call(obj) == '[object String]'; + }; + + // Is a given value a number? + _.isNumber = function(obj) { + return toString.call(obj) == '[object Number]'; + }; + + // Is the given value `NaN`? + _.isNaN = function(obj) { + // `NaN` is the only value for which `===` is not reflexive. + return obj !== obj; + }; + + // Is a given value a boolean? + _.isBoolean = function(obj) { + return obj === true || obj === false || toString.call(obj) == '[object Boolean]'; + }; + + // Is a given value a date? + _.isDate = function(obj) { + return toString.call(obj) == '[object Date]'; + }; + + // Is the given value a regular expression? + _.isRegExp = function(obj) { + return toString.call(obj) == '[object RegExp]'; + }; + + // Is a given value equal to null? + _.isNull = function(obj) { + return obj === null; + }; + + // Is a given variable undefined? + _.isUndefined = function(obj) { + return obj === void 0; + }; + + // Has own property? + _.has = function(obj, key) { + return hasOwnProperty.call(obj, key); + }; + + // Utility Functions + // ----------------- + + // Run Underscore.js in *noConflict* mode, returning the `_` variable to its + // previous owner. Returns a reference to the Underscore object. + _.noConflict = function() { + root._ = previousUnderscore; + return this; + }; + + // Keep the identity function around for default iterators. + _.identity = function(value) { + return value; + }; + + // Run a function **n** times. + _.times = function (n, iterator, context) { + for (var i = 0; i < n; i++) iterator.call(context, i); + }; + + // Escape a string for HTML interpolation. + _.escape = function(string) { + return (''+string).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g,'/'); + }; + + // Add your own custom functions to the Underscore object, ensuring that + // they're correctly added to the OOP wrapper as well. + _.mixin = function(obj) { + each(_.functions(obj), function(name){ + addToWrapper(name, _[name] = obj[name]); + }); + }; + + // Generate a unique integer id (unique within the entire client session). + // Useful for temporary DOM ids. + var idCounter = 0; + _.uniqueId = function(prefix) { + var id = idCounter++; + return prefix ? prefix + id : id; + }; + + // By default, Underscore uses ERB-style template delimiters, change the + // following template settings to use alternative delimiters. + _.templateSettings = { + evaluate : /<%([\s\S]+?)%>/g, + interpolate : /<%=([\s\S]+?)%>/g, + escape : /<%-([\s\S]+?)%>/g + }; + + // When customizing `templateSettings`, if you don't want to define an + // interpolation, evaluation or escaping regex, we need one that is + // guaranteed not to match. + var noMatch = /.^/; + + // Within an interpolation, evaluation, or escaping, remove HTML escaping + // that had been previously added. + var unescape = function(code) { + return code.replace(/\\\\/g, '\\').replace(/\\'/g, "'"); + }; + + // JavaScript micro-templating, similar to John Resig's implementation. + // Underscore templating handles arbitrary delimiters, preserves whitespace, + // and correctly escapes quotes within interpolated code. + _.template = function(str, data) { + var c = _.templateSettings; + var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' + + 'with(obj||{}){__p.push(\'' + + str.replace(/\\/g, '\\\\') + .replace(/'/g, "\\'") + .replace(c.escape || noMatch, function(match, code) { + return "',_.escape(" + unescape(code) + "),'"; + }) + .replace(c.interpolate || noMatch, function(match, code) { + return "'," + unescape(code) + ",'"; + }) + .replace(c.evaluate || noMatch, function(match, code) { + return "');" + unescape(code).replace(/[\r\n\t]/g, ' ') + ";__p.push('"; + }) + .replace(/\r/g, '\\r') + .replace(/\n/g, '\\n') + .replace(/\t/g, '\\t') + + "');}return __p.join('');"; + var func = new Function('obj', '_', tmpl); + if (data) return func(data, _); + return function(data) { + return func.call(this, data, _); + }; + }; + + // Add a "chain" function, which will delegate to the wrapper. + _.chain = function(obj) { + return _(obj).chain(); + }; + + // The OOP Wrapper + // --------------- + + // If Underscore is called as a function, it returns a wrapped object that + // can be used OO-style. This wrapper holds altered versions of all the + // underscore functions. Wrapped objects may be chained. + var wrapper = function(obj) { this._wrapped = obj; }; + + // Expose `wrapper.prototype` as `_.prototype` + _.prototype = wrapper.prototype; + + // Helper function to continue chaining intermediate results. + var result = function(obj, chain) { + return chain ? _(obj).chain() : obj; + }; + + // A method to easily add functions to the OOP wrapper. + var addToWrapper = function(name, func) { + wrapper.prototype[name] = function() { + var args = slice.call(arguments); + unshift.call(args, this._wrapped); + return result(func.apply(_, args), this._chain); + }; + }; + + // Add all of the Underscore functions to the wrapper object. + _.mixin(_); + + // Add all mutator Array functions to the wrapper. + each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { + var method = ArrayProto[name]; + wrapper.prototype[name] = function() { + var wrapped = this._wrapped; + method.apply(wrapped, arguments); + var length = wrapped.length; + if ((name == 'shift' || name == 'splice') && length === 0) delete wrapped[0]; + return result(wrapped, this._chain); + }; + }); + + // Add all accessor Array functions to the wrapper. + each(['concat', 'join', 'slice'], function(name) { + var method = ArrayProto[name]; + wrapper.prototype[name] = function() { + return result(method.apply(this._wrapped, arguments), this._chain); + }; + }); + + // Start chaining a wrapped Underscore object. + wrapper.prototype.chain = function() { + this._chain = true; + return this; + }; + + // Extracts the result from a wrapped and chained object. + wrapper.prototype.value = function() { + return this._wrapped; + }; + +}).call(this); diff --git a/docs/readthedocsFiles/_build/html/_static/underscore.js b/docs/readthedocsFiles/_build/html/_static/underscore.js new file mode 100644 index 00000000..5b55f32b --- /dev/null +++ b/docs/readthedocsFiles/_build/html/_static/underscore.js @@ -0,0 +1,31 @@ +// Underscore.js 1.3.1 +// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. +// Underscore is freely distributable under the MIT license. +// Portions of Underscore are inspired or borrowed from Prototype, +// Oliver Steele's Functional, and John Resig's Micro-Templating. +// For all details and documentation: +// http://documentcloud.github.com/underscore +(function(){function q(a,c,d){if(a===c)return a!==0||1/a==1/c;if(a==null||c==null)return a===c;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return false;switch(e){case "[object String]":return a==String(c);case "[object Number]":return a!=+a?c!=+c:a==0?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source== +c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if(typeof a!="object"||typeof c!="object")return false;for(var f=d.length;f--;)if(d[f]==a)return true;d.push(a);var f=0,g=true;if(e=="[object Array]"){if(f=a.length,g=f==c.length)for(;f--;)if(!(g=f in a==f in c&&q(a[f],c[f],d)))break}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return false;for(var h in a)if(b.has(a,h)&&(f++,!(g=b.has(c,h)&&q(a[h],c[h],d))))break;if(g){for(h in c)if(b.has(c, +h)&&!f--)break;g=!f}}d.pop();return g}var r=this,G=r._,n={},k=Array.prototype,o=Object.prototype,i=k.slice,H=k.unshift,l=o.toString,I=o.hasOwnProperty,w=k.forEach,x=k.map,y=k.reduce,z=k.reduceRight,A=k.filter,B=k.every,C=k.some,p=k.indexOf,D=k.lastIndexOf,o=Array.isArray,J=Object.keys,s=Function.prototype.bind,b=function(a){return new m(a)};if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports)exports=module.exports=b;exports._=b}else r._=b;b.VERSION="1.3.1";var j=b.each= +b.forEach=function(a,c,d){if(a!=null)if(w&&a.forEach===w)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e<f;e++){if(e in a&&c.call(d,a[e],e,a)===n)break}else for(e in a)if(b.has(a,e)&&c.call(d,a[e],e,a)===n)break};b.map=b.collect=function(a,c,b){var e=[];if(a==null)return e;if(x&&a.map===x)return a.map(c,b);j(a,function(a,g,h){e[e.length]=c.call(b,a,g,h)});if(a.length===+a.length)e.length=a.length;return e};b.reduce=b.foldl=b.inject=function(a,c,d,e){var f=arguments.length>2;a== +null&&(a=[]);if(y&&a.reduce===y)return e&&(c=b.bind(c,e)),f?a.reduce(c,d):a.reduce(c);j(a,function(a,b,i){f?d=c.call(e,d,a,b,i):(d=a,f=true)});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(z&&a.reduceRight===z)return e&&(c=b.bind(c,e)),f?a.reduceRight(c,d):a.reduceRight(c);var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect= +function(a,c,b){var e;E(a,function(a,g,h){if(c.call(b,a,g,h))return e=a,true});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(A&&a.filter===A)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(B&&a.every===B)return a.every(c,b);j(a,function(a,g,h){if(!(e= +e&&c.call(b,a,g,h)))return n});return e};var E=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(C&&a.some===C)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return n});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;return p&&a.indexOf===p?a.indexOf(c)!=-1:b=E(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c||a:a[c]).apply(a,d)})};b.pluck= +function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b<e.computed&&(e={value:a,computed:b})}); +return e.value};b.shuffle=function(a){var b=[],d;j(a,function(a,f){f==0?b[0]=a:(d=Math.floor(Math.random()*(f+1)),b[f]=b[d],b[d]=a)});return b};b.sortBy=function(a,c,d){return b.pluck(b.map(a,function(a,b,g){return{value:a,criteria:c.call(d,a,b,g)}}).sort(function(a,b){var c=a.criteria,d=b.criteria;return c<d?-1:c>d?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a, +c,d){d||(d=b.identity);for(var e=0,f=a.length;e<f;){var g=e+f>>1;d(a[g])<d(c)?e=g+1:f=g}return e};b.toArray=function(a){return!a?[]:a.toArray?a.toArray():b.isArray(a)?i.call(a):b.isArguments(a)?i.call(a):b.values(a)};b.size=function(a){return b.toArray(a).length};b.first=b.head=function(a,b,d){return b!=null&&!d?i.call(a,0,b):a[0]};b.initial=function(a,b,d){return i.call(a,0,a.length-(b==null||d?1:b))};b.last=function(a,b,d){return b!=null&&!d?i.call(a,Math.max(a.length-b,0)):a[a.length-1]};b.rest= +b.tail=function(a,b,d){return i.call(a,b==null||d?1:b)};b.compact=function(a){return b.filter(a,function(a){return!!a})};b.flatten=function(a,c){return b.reduce(a,function(a,e){if(b.isArray(e))return a.concat(c?e:b.flatten(e));a[a.length]=e;return a},[])};b.without=function(a){return b.difference(a,i.call(arguments,1))};b.uniq=b.unique=function(a,c,d){var d=d?b.map(a,d):a,e=[];b.reduce(d,function(d,g,h){if(0==h||(c===true?b.last(d)!=g:!b.include(d,g)))d[d.length]=g,e[e.length]=a[h];return d},[]); +return e};b.union=function(){return b.uniq(b.flatten(arguments,true))};b.intersection=b.intersect=function(a){var c=i.call(arguments,1);return b.filter(b.uniq(a),function(a){return b.every(c,function(c){return b.indexOf(c,a)>=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1));return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e<c;e++)d[e]=b.pluck(a,""+e);return d};b.indexOf=function(a,c, +d){if(a==null)return-1;var e;if(d)return d=b.sortedIndex(a,c),a[d]===c?d:-1;if(p&&a.indexOf===p)return a.indexOf(c);for(d=0,e=a.length;d<e;d++)if(d in a&&a[d]===c)return d;return-1};b.lastIndexOf=function(a,b){if(a==null)return-1;if(D&&a.lastIndexOf===D)return a.lastIndexOf(b);for(var d=a.length;d--;)if(d in a&&a[d]===b)return d;return-1};b.range=function(a,b,d){arguments.length<=1&&(b=a||0,a=0);for(var d=arguments[2]||1,e=Math.max(Math.ceil((b-a)/d),0),f=0,g=Array(e);f<e;)g[f++]=a,a+=d;return g}; +var F=function(){};b.bind=function(a,c){var d,e;if(a.bind===s&&s)return s.apply(a,i.call(arguments,1));if(!b.isFunction(a))throw new TypeError;e=i.call(arguments,2);return d=function(){if(!(this instanceof d))return a.apply(c,e.concat(i.call(arguments)));F.prototype=a.prototype;var b=new F,g=a.apply(b,e.concat(i.call(arguments)));return Object(g)===g?g:b}};b.bindAll=function(a){var c=i.call(arguments,1);c.length==0&&(c=b.functions(a));j(c,function(c){a[c]=b.bind(a[c],a)});return a};b.memoize=function(a, +c){var d={};c||(c=b.identity);return function(){var e=c.apply(this,arguments);return b.has(d,e)?d[e]:d[e]=a.apply(this,arguments)}};b.delay=function(a,b){var d=i.call(arguments,2);return setTimeout(function(){return a.apply(a,d)},b)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(i.call(arguments,1)))};b.throttle=function(a,c){var d,e,f,g,h,i=b.debounce(function(){h=g=false},c);return function(){d=this;e=arguments;var b;f||(f=setTimeout(function(){f=null;h&&a.apply(d,e);i()},c));g?h=true: +a.apply(d,e);i();g=true}};b.debounce=function(a,b){var d;return function(){var e=this,f=arguments;clearTimeout(d);d=setTimeout(function(){d=null;a.apply(e,f)},b)}};b.once=function(a){var b=false,d;return function(){if(b)return d;b=true;return d=a.apply(this,arguments)}};b.wrap=function(a,b){return function(){var d=[a].concat(i.call(arguments,0));return b.apply(this,d)}};b.compose=function(){var a=arguments;return function(){for(var b=arguments,d=a.length-1;d>=0;d--)b=[a[d].apply(this,b)];return b[0]}}; +b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=J||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.defaults=function(a){j(i.call(arguments, +1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return q(a,b,[])};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=o||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)}; +b.isArguments=function(a){return l.call(a)=="[object Arguments]"};if(!b.isArguments(arguments))b.isArguments=function(a){return!(!a||!b.has(a,"callee"))};b.isFunction=function(a){return l.call(a)=="[object Function]"};b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"}; +b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a,b){return I.call(a,b)};b.noConflict=function(){r._=G;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e<a;e++)b.call(d,e)};b.escape=function(a){return(""+a).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};b.mixin=function(a){j(b.functions(a), +function(c){K(c,b[c]=a[c])})};var L=0;b.uniqueId=function(a){var b=L++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var t=/.^/,u=function(a){return a.replace(/\\\\/g,"\\").replace(/\\'/g,"'")};b.template=function(a,c){var d=b.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.escape||t,function(a,b){return"',_.escape("+ +u(b)+"),'"}).replace(d.interpolate||t,function(a,b){return"',"+u(b)+",'"}).replace(d.evaluate||t,function(a,b){return"');"+u(b).replace(/[\r\n\t]/g," ")+";__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",e=new Function("obj","_",d);return c?e(c,b):function(a){return e.call(this,a,b)}};b.chain=function(a){return b(a).chain()};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var v=function(a,c){return c?b(a).chain():a},K=function(a,c){m.prototype[a]= +function(){var a=i.call(arguments);H.call(a,this._wrapped);return v(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];m.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments);var e=d.length;(a=="shift"||a=="splice")&&e===0&&delete d[0];return v(d,this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return v(b.apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain= +true;return this};m.prototype.value=function(){return this._wrapped}}).call(this); diff --git a/docs/readthedocsFiles/_build/html/_static/up-pressed.png b/docs/readthedocsFiles/_build/html/_static/up-pressed.png new file mode 100644 index 0000000000000000000000000000000000000000..acee3b68efbbfb9de3bfa73fce2531380f4bd820 GIT binary patch literal 214 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`b3I)gLn;`PCGIF3$k*oP2u^Cy z1QG|_1=PYnb1D4eKdS9<Rfpq9eZvoV4(FRqOC&yU<ykZBR|+eZ3^{Bs(0fczS?$)o z1zBzqOjDB&rXLWUki_(ZOYf}S3_te-6HD<NwmmIcKX{MqZ}`C=pw3iiGvzP0)_49T zVzHmSR(wuwn8DsJUb!G^UBj0HEK?h9OzUY$<G#e5WDq>z%1p*-hql&#Z9G*2bSQ(T LtDnm{r-UW|Tp3Nf literal 0 HcmV?d00001 diff --git a/docs/readthedocsFiles/_build/html/_static/up.png b/docs/readthedocsFiles/_build/html/_static/up.png new file mode 100644 index 0000000000000000000000000000000000000000..2a940a7da7c14e6a36901e83306849ba7efad4d4 GIT binary patch literal 203 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6CV9FzhEy=Fov?Lbi-SbV{M7eb zSgRCN7I%METfw=?;;+>WH3cj0g7WUX+WNUO*ZcGT`ytIgVd8OSIh$j5tP~Z0O{ng4 zcW5}i$^L-SndMiXU*q89_#FKDQg!+}n<sY{bt~lf29~d`d|30$ek+#&XF;JwVZw1v z_UR0p3#2)&`AF%iuMw0u8SEq};^+A5QF+L(`y7{#wyduS<qiY7j=|H_&t;ucLK6TJ CK~C2I literal 0 HcmV?d00001 diff --git a/docs/readthedocsFiles/_build/html/_static/websupport.js b/docs/readthedocsFiles/_build/html/_static/websupport.js new file mode 100644 index 00000000..78e14bb4 --- /dev/null +++ b/docs/readthedocsFiles/_build/html/_static/websupport.js @@ -0,0 +1,808 @@ +/* + * websupport.js + * ~~~~~~~~~~~~~ + * + * sphinx.websupport utilities for all documentation. + * + * :copyright: Copyright 2007-2018 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +(function($) { + $.fn.autogrow = function() { + return this.each(function() { + var textarea = this; + + $.fn.autogrow.resize(textarea); + + $(textarea) + .focus(function() { + textarea.interval = setInterval(function() { + $.fn.autogrow.resize(textarea); + }, 500); + }) + .blur(function() { + clearInterval(textarea.interval); + }); + }); + }; + + $.fn.autogrow.resize = function(textarea) { + var lineHeight = parseInt($(textarea).css('line-height'), 10); + var lines = textarea.value.split('\n'); + var columns = textarea.cols; + var lineCount = 0; + $.each(lines, function() { + lineCount += Math.ceil(this.length / columns) || 1; + }); + var height = lineHeight * (lineCount + 1); + $(textarea).css('height', height); + }; +})(jQuery); + +(function($) { + var comp, by; + + function init() { + initEvents(); + initComparator(); + } + + function initEvents() { + $(document).on("click", 'a.comment-close', function(event) { + event.preventDefault(); + hide($(this).attr('id').substring(2)); + }); + $(document).on("click", 'a.vote', function(event) { + event.preventDefault(); + handleVote($(this)); + }); + $(document).on("click", 'a.reply', function(event) { + event.preventDefault(); + openReply($(this).attr('id').substring(2)); + }); + $(document).on("click", 'a.close-reply', function(event) { + event.preventDefault(); + closeReply($(this).attr('id').substring(2)); + }); + $(document).on("click", 'a.sort-option', function(event) { + event.preventDefault(); + handleReSort($(this)); + }); + $(document).on("click", 'a.show-proposal', function(event) { + event.preventDefault(); + showProposal($(this).attr('id').substring(2)); + }); + $(document).on("click", 'a.hide-proposal', function(event) { + event.preventDefault(); + hideProposal($(this).attr('id').substring(2)); + }); + $(document).on("click", 'a.show-propose-change', function(event) { + event.preventDefault(); + showProposeChange($(this).attr('id').substring(2)); + }); + $(document).on("click", 'a.hide-propose-change', function(event) { + event.preventDefault(); + hideProposeChange($(this).attr('id').substring(2)); + }); + $(document).on("click", 'a.accept-comment', function(event) { + event.preventDefault(); + acceptComment($(this).attr('id').substring(2)); + }); + $(document).on("click", 'a.delete-comment', function(event) { + event.preventDefault(); + deleteComment($(this).attr('id').substring(2)); + }); + $(document).on("click", 'a.comment-markup', function(event) { + event.preventDefault(); + toggleCommentMarkupBox($(this).attr('id').substring(2)); + }); + } + + /** + * Set comp, which is a comparator function used for sorting and + * inserting comments into the list. + */ + function setComparator() { + // If the first three letters are "asc", sort in ascending order + // and remove the prefix. + if (by.substring(0,3) == 'asc') { + var i = by.substring(3); + comp = function(a, b) { return a[i] - b[i]; }; + } else { + // Otherwise sort in descending order. + comp = function(a, b) { return b[by] - a[by]; }; + } + + // Reset link styles and format the selected sort option. + $('a.sel').attr('href', '#').removeClass('sel'); + $('a.by' + by).removeAttr('href').addClass('sel'); + } + + /** + * Create a comp function. If the user has preferences stored in + * the sortBy cookie, use those, otherwise use the default. + */ + function initComparator() { + by = 'rating'; // Default to sort by rating. + // If the sortBy cookie is set, use that instead. + if (document.cookie.length > 0) { + var start = document.cookie.indexOf('sortBy='); + if (start != -1) { + start = start + 7; + var end = document.cookie.indexOf(";", start); + if (end == -1) { + end = document.cookie.length; + by = unescape(document.cookie.substring(start, end)); + } + } + } + setComparator(); + } + + /** + * Show a comment div. + */ + function show(id) { + $('#ao' + id).hide(); + $('#ah' + id).show(); + var context = $.extend({id: id}, opts); + var popup = $(renderTemplate(popupTemplate, context)).hide(); + popup.find('textarea[name="proposal"]').hide(); + popup.find('a.by' + by).addClass('sel'); + var form = popup.find('#cf' + id); + form.submit(function(event) { + event.preventDefault(); + addComment(form); + }); + $('#s' + id).after(popup); + popup.slideDown('fast', function() { + getComments(id); + }); + } + + /** + * Hide a comment div. + */ + function hide(id) { + $('#ah' + id).hide(); + $('#ao' + id).show(); + var div = $('#sc' + id); + div.slideUp('fast', function() { + div.remove(); + }); + } + + /** + * Perform an ajax request to get comments for a node + * and insert the comments into the comments tree. + */ + function getComments(id) { + $.ajax({ + type: 'GET', + url: opts.getCommentsURL, + data: {node: id}, + success: function(data, textStatus, request) { + var ul = $('#cl' + id); + var speed = 100; + $('#cf' + id) + .find('textarea[name="proposal"]') + .data('source', data.source); + + if (data.comments.length === 0) { + ul.html('<li>No comments yet.</li>'); + ul.data('empty', true); + } else { + // If there are comments, sort them and put them in the list. + var comments = sortComments(data.comments); + speed = data.comments.length * 100; + appendComments(comments, ul); + ul.data('empty', false); + } + $('#cn' + id).slideUp(speed + 200); + ul.slideDown(speed); + }, + error: function(request, textStatus, error) { + showError('Oops, there was a problem retrieving the comments.'); + }, + dataType: 'json' + }); + } + + /** + * Add a comment via ajax and insert the comment into the comment tree. + */ + function addComment(form) { + var node_id = form.find('input[name="node"]').val(); + var parent_id = form.find('input[name="parent"]').val(); + var text = form.find('textarea[name="comment"]').val(); + var proposal = form.find('textarea[name="proposal"]').val(); + + if (text == '') { + showError('Please enter a comment.'); + return; + } + + // Disable the form that is being submitted. + form.find('textarea,input').attr('disabled', 'disabled'); + + // Send the comment to the server. + $.ajax({ + type: "POST", + url: opts.addCommentURL, + dataType: 'json', + data: { + node: node_id, + parent: parent_id, + text: text, + proposal: proposal + }, + success: function(data, textStatus, error) { + // Reset the form. + if (node_id) { + hideProposeChange(node_id); + } + form.find('textarea') + .val('') + .add(form.find('input')) + .removeAttr('disabled'); + var ul = $('#cl' + (node_id || parent_id)); + if (ul.data('empty')) { + $(ul).empty(); + ul.data('empty', false); + } + insertComment(data.comment); + var ao = $('#ao' + node_id); + ao.find('img').attr({'src': opts.commentBrightImage}); + if (node_id) { + // if this was a "root" comment, remove the commenting box + // (the user can get it back by reopening the comment popup) + $('#ca' + node_id).slideUp(); + } + }, + error: function(request, textStatus, error) { + form.find('textarea,input').removeAttr('disabled'); + showError('Oops, there was a problem adding the comment.'); + } + }); + } + + /** + * Recursively append comments to the main comment list and children + * lists, creating the comment tree. + */ + function appendComments(comments, ul) { + $.each(comments, function() { + var div = createCommentDiv(this); + ul.append($(document.createElement('li')).html(div)); + appendComments(this.children, div.find('ul.comment-children')); + // To avoid stagnating data, don't store the comments children in data. + this.children = null; + div.data('comment', this); + }); + } + + /** + * After adding a new comment, it must be inserted in the correct + * location in the comment tree. + */ + function insertComment(comment) { + var div = createCommentDiv(comment); + + // To avoid stagnating data, don't store the comments children in data. + comment.children = null; + div.data('comment', comment); + + var ul = $('#cl' + (comment.node || comment.parent)); + var siblings = getChildren(ul); + + var li = $(document.createElement('li')); + li.hide(); + + // Determine where in the parents children list to insert this comment. + for(var i=0; i < siblings.length; i++) { + if (comp(comment, siblings[i]) <= 0) { + $('#cd' + siblings[i].id) + .parent() + .before(li.html(div)); + li.slideDown('fast'); + return; + } + } + + // If we get here, this comment rates lower than all the others, + // or it is the only comment in the list. + ul.append(li.html(div)); + li.slideDown('fast'); + } + + function acceptComment(id) { + $.ajax({ + type: 'POST', + url: opts.acceptCommentURL, + data: {id: id}, + success: function(data, textStatus, request) { + $('#cm' + id).fadeOut('fast'); + $('#cd' + id).removeClass('moderate'); + }, + error: function(request, textStatus, error) { + showError('Oops, there was a problem accepting the comment.'); + } + }); + } + + function deleteComment(id) { + $.ajax({ + type: 'POST', + url: opts.deleteCommentURL, + data: {id: id}, + success: function(data, textStatus, request) { + var div = $('#cd' + id); + if (data == 'delete') { + // Moderator mode: remove the comment and all children immediately + div.slideUp('fast', function() { + div.remove(); + }); + return; + } + // User mode: only mark the comment as deleted + div + .find('span.user-id:first') + .text('[deleted]').end() + .find('div.comment-text:first') + .text('[deleted]').end() + .find('#cm' + id + ', #dc' + id + ', #ac' + id + ', #rc' + id + + ', #sp' + id + ', #hp' + id + ', #cr' + id + ', #rl' + id) + .remove(); + var comment = div.data('comment'); + comment.username = '[deleted]'; + comment.text = '[deleted]'; + div.data('comment', comment); + }, + error: function(request, textStatus, error) { + showError('Oops, there was a problem deleting the comment.'); + } + }); + } + + function showProposal(id) { + $('#sp' + id).hide(); + $('#hp' + id).show(); + $('#pr' + id).slideDown('fast'); + } + + function hideProposal(id) { + $('#hp' + id).hide(); + $('#sp' + id).show(); + $('#pr' + id).slideUp('fast'); + } + + function showProposeChange(id) { + $('#pc' + id).hide(); + $('#hc' + id).show(); + var textarea = $('#pt' + id); + textarea.val(textarea.data('source')); + $.fn.autogrow.resize(textarea[0]); + textarea.slideDown('fast'); + } + + function hideProposeChange(id) { + $('#hc' + id).hide(); + $('#pc' + id).show(); + var textarea = $('#pt' + id); + textarea.val('').removeAttr('disabled'); + textarea.slideUp('fast'); + } + + function toggleCommentMarkupBox(id) { + $('#mb' + id).toggle(); + } + + /** Handle when the user clicks on a sort by link. */ + function handleReSort(link) { + var classes = link.attr('class').split(/\s+/); + for (var i=0; i<classes.length; i++) { + if (classes[i] != 'sort-option') { + by = classes[i].substring(2); + } + } + setComparator(); + // Save/update the sortBy cookie. + var expiration = new Date(); + expiration.setDate(expiration.getDate() + 365); + document.cookie= 'sortBy=' + escape(by) + + ';expires=' + expiration.toUTCString(); + $('ul.comment-ul').each(function(index, ul) { + var comments = getChildren($(ul), true); + comments = sortComments(comments); + appendComments(comments, $(ul).empty()); + }); + } + + /** + * Function to process a vote when a user clicks an arrow. + */ + function handleVote(link) { + if (!opts.voting) { + showError("You'll need to login to vote."); + return; + } + + var id = link.attr('id'); + if (!id) { + // Didn't click on one of the voting arrows. + return; + } + // If it is an unvote, the new vote value is 0, + // Otherwise it's 1 for an upvote, or -1 for a downvote. + var value = 0; + if (id.charAt(1) != 'u') { + value = id.charAt(0) == 'u' ? 1 : -1; + } + // The data to be sent to the server. + var d = { + comment_id: id.substring(2), + value: value + }; + + // Swap the vote and unvote links. + link.hide(); + $('#' + id.charAt(0) + (id.charAt(1) == 'u' ? 'v' : 'u') + d.comment_id) + .show(); + + // The div the comment is displayed in. + var div = $('div#cd' + d.comment_id); + var data = div.data('comment'); + + // If this is not an unvote, and the other vote arrow has + // already been pressed, unpress it. + if ((d.value !== 0) && (data.vote === d.value * -1)) { + $('#' + (d.value == 1 ? 'd' : 'u') + 'u' + d.comment_id).hide(); + $('#' + (d.value == 1 ? 'd' : 'u') + 'v' + d.comment_id).show(); + } + + // Update the comments rating in the local data. + data.rating += (data.vote === 0) ? d.value : (d.value - data.vote); + data.vote = d.value; + div.data('comment', data); + + // Change the rating text. + div.find('.rating:first') + .text(data.rating + ' point' + (data.rating == 1 ? '' : 's')); + + // Send the vote information to the server. + $.ajax({ + type: "POST", + url: opts.processVoteURL, + data: d, + error: function(request, textStatus, error) { + showError('Oops, there was a problem casting that vote.'); + } + }); + } + + /** + * Open a reply form used to reply to an existing comment. + */ + function openReply(id) { + // Swap out the reply link for the hide link + $('#rl' + id).hide(); + $('#cr' + id).show(); + + // Add the reply li to the children ul. + var div = $(renderTemplate(replyTemplate, {id: id})).hide(); + $('#cl' + id) + .prepend(div) + // Setup the submit handler for the reply form. + .find('#rf' + id) + .submit(function(event) { + event.preventDefault(); + addComment($('#rf' + id)); + closeReply(id); + }) + .find('input[type=button]') + .click(function() { + closeReply(id); + }); + div.slideDown('fast', function() { + $('#rf' + id).find('textarea').focus(); + }); + } + + /** + * Close the reply form opened with openReply. + */ + function closeReply(id) { + // Remove the reply div from the DOM. + $('#rd' + id).slideUp('fast', function() { + $(this).remove(); + }); + + // Swap out the hide link for the reply link + $('#cr' + id).hide(); + $('#rl' + id).show(); + } + + /** + * Recursively sort a tree of comments using the comp comparator. + */ + function sortComments(comments) { + comments.sort(comp); + $.each(comments, function() { + this.children = sortComments(this.children); + }); + return comments; + } + + /** + * Get the children comments from a ul. If recursive is true, + * recursively include childrens' children. + */ + function getChildren(ul, recursive) { + var children = []; + ul.children().children("[id^='cd']") + .each(function() { + var comment = $(this).data('comment'); + if (recursive) + comment.children = getChildren($(this).find('#cl' + comment.id), true); + children.push(comment); + }); + return children; + } + + /** Create a div to display a comment in. */ + function createCommentDiv(comment) { + if (!comment.displayed && !opts.moderator) { + return $('<div class="moderate">Thank you! Your comment will show up ' + + 'once it is has been approved by a moderator.</div>'); + } + // Prettify the comment rating. + comment.pretty_rating = comment.rating + ' point' + + (comment.rating == 1 ? '' : 's'); + // Make a class (for displaying not yet moderated comments differently) + comment.css_class = comment.displayed ? '' : ' moderate'; + // Create a div for this comment. + var context = $.extend({}, opts, comment); + var div = $(renderTemplate(commentTemplate, context)); + + // If the user has voted on this comment, highlight the correct arrow. + if (comment.vote) { + var direction = (comment.vote == 1) ? 'u' : 'd'; + div.find('#' + direction + 'v' + comment.id).hide(); + div.find('#' + direction + 'u' + comment.id).show(); + } + + if (opts.moderator || comment.text != '[deleted]') { + div.find('a.reply').show(); + if (comment.proposal_diff) + div.find('#sp' + comment.id).show(); + if (opts.moderator && !comment.displayed) + div.find('#cm' + comment.id).show(); + if (opts.moderator || (opts.username == comment.username)) + div.find('#dc' + comment.id).show(); + } + return div; + } + + /** + * A simple template renderer. Placeholders such as <%id%> are replaced + * by context['id'] with items being escaped. Placeholders such as <#id#> + * are not escaped. + */ + function renderTemplate(template, context) { + var esc = $(document.createElement('div')); + + function handle(ph, escape) { + var cur = context; + $.each(ph.split('.'), function() { + cur = cur[this]; + }); + return escape ? esc.text(cur || "").html() : cur; + } + + return template.replace(/<([%#])([\w\.]*)\1>/g, function() { + return handle(arguments[2], arguments[1] == '%' ? true : false); + }); + } + + /** Flash an error message briefly. */ + function showError(message) { + $(document.createElement('div')).attr({'class': 'popup-error'}) + .append($(document.createElement('div')) + .attr({'class': 'error-message'}).text(message)) + .appendTo('body') + .fadeIn("slow") + .delay(2000) + .fadeOut("slow"); + } + + /** Add a link the user uses to open the comments popup. */ + $.fn.comment = function() { + return this.each(function() { + var id = $(this).attr('id').substring(1); + var count = COMMENT_METADATA[id]; + var title = count + ' comment' + (count == 1 ? '' : 's'); + var image = count > 0 ? opts.commentBrightImage : opts.commentImage; + var addcls = count == 0 ? ' nocomment' : ''; + $(this) + .append( + $(document.createElement('a')).attr({ + href: '#', + 'class': 'sphinx-comment-open' + addcls, + id: 'ao' + id + }) + .append($(document.createElement('img')).attr({ + src: image, + alt: 'comment', + title: title + })) + .click(function(event) { + event.preventDefault(); + show($(this).attr('id').substring(2)); + }) + ) + .append( + $(document.createElement('a')).attr({ + href: '#', + 'class': 'sphinx-comment-close hidden', + id: 'ah' + id + }) + .append($(document.createElement('img')).attr({ + src: opts.closeCommentImage, + alt: 'close', + title: 'close' + })) + .click(function(event) { + event.preventDefault(); + hide($(this).attr('id').substring(2)); + }) + ); + }); + }; + + var opts = { + processVoteURL: '/_process_vote', + addCommentURL: '/_add_comment', + getCommentsURL: '/_get_comments', + acceptCommentURL: '/_accept_comment', + deleteCommentURL: '/_delete_comment', + commentImage: '/static/_static/comment.png', + closeCommentImage: '/static/_static/comment-close.png', + loadingImage: '/static/_static/ajax-loader.gif', + commentBrightImage: '/static/_static/comment-bright.png', + upArrow: '/static/_static/up.png', + downArrow: '/static/_static/down.png', + upArrowPressed: '/static/_static/up-pressed.png', + downArrowPressed: '/static/_static/down-pressed.png', + voting: false, + moderator: false + }; + + if (typeof COMMENT_OPTIONS != "undefined") { + opts = jQuery.extend(opts, COMMENT_OPTIONS); + } + + var popupTemplate = '\ + <div class="sphinx-comments" id="sc<%id%>">\ + <p class="sort-options">\ + Sort by:\ + <a href="#" class="sort-option byrating">best rated</a>\ + <a href="#" class="sort-option byascage">newest</a>\ + <a href="#" class="sort-option byage">oldest</a>\ + </p>\ + <div class="comment-header">Comments</div>\ + <div class="comment-loading" id="cn<%id%>">\ + loading comments... <img src="<%loadingImage%>" alt="" /></div>\ + <ul id="cl<%id%>" class="comment-ul"></ul>\ + <div id="ca<%id%>">\ + <p class="add-a-comment">Add a comment\ + (<a href="#" class="comment-markup" id="ab<%id%>">markup</a>):</p>\ + <div class="comment-markup-box" id="mb<%id%>">\ + reStructured text markup: <i>*emph*</i>, <b>**strong**</b>, \ + <code>``code``</code>, \ + code blocks: <code>::</code> and an indented block after blank line</div>\ + <form method="post" id="cf<%id%>" class="comment-form" action="">\ + <textarea name="comment" cols="80"></textarea>\ + <p class="propose-button">\ + <a href="#" id="pc<%id%>" class="show-propose-change">\ + Propose a change ▹\ + </a>\ + <a href="#" id="hc<%id%>" class="hide-propose-change">\ + Propose a change ▿\ + </a>\ + </p>\ + <textarea name="proposal" id="pt<%id%>" cols="80"\ + spellcheck="false"></textarea>\ + <input type="submit" value="Add comment" />\ + <input type="hidden" name="node" value="<%id%>" />\ + <input type="hidden" name="parent" value="" />\ + </form>\ + </div>\ + </div>'; + + var commentTemplate = '\ + <div id="cd<%id%>" class="sphinx-comment<%css_class%>">\ + <div class="vote">\ + <div class="arrow">\ + <a href="#" id="uv<%id%>" class="vote" title="vote up">\ + <img src="<%upArrow%>" />\ + </a>\ + <a href="#" id="uu<%id%>" class="un vote" title="vote up">\ + <img src="<%upArrowPressed%>" />\ + </a>\ + </div>\ + <div class="arrow">\ + <a href="#" id="dv<%id%>" class="vote" title="vote down">\ + <img src="<%downArrow%>" id="da<%id%>" />\ + </a>\ + <a href="#" id="du<%id%>" class="un vote" title="vote down">\ + <img src="<%downArrowPressed%>" />\ + </a>\ + </div>\ + </div>\ + <div class="comment-content">\ + <p class="tagline comment">\ + <span class="user-id"><%username%></span>\ + <span class="rating"><%pretty_rating%></span>\ + <span class="delta"><%time.delta%></span>\ + </p>\ + <div class="comment-text comment"><#text#></div>\ + <p class="comment-opts comment">\ + <a href="#" class="reply hidden" id="rl<%id%>">reply ▹</a>\ + <a href="#" class="close-reply" id="cr<%id%>">reply ▿</a>\ + <a href="#" id="sp<%id%>" class="show-proposal">proposal ▹</a>\ + <a href="#" id="hp<%id%>" class="hide-proposal">proposal ▿</a>\ + <a href="#" id="dc<%id%>" class="delete-comment hidden">delete</a>\ + <span id="cm<%id%>" class="moderation hidden">\ + <a href="#" id="ac<%id%>" class="accept-comment">accept</a>\ + </span>\ + </p>\ + <pre class="proposal" id="pr<%id%>">\ +<#proposal_diff#>\ + </pre>\ + <ul class="comment-children" id="cl<%id%>"></ul>\ + </div>\ + <div class="clearleft"></div>\ + </div>\ + </div>'; + + var replyTemplate = '\ + <li>\ + <div class="reply-div" id="rd<%id%>">\ + <form id="rf<%id%>">\ + <textarea name="comment" cols="80"></textarea>\ + <input type="submit" value="Add reply" />\ + <input type="button" value="Cancel" />\ + <input type="hidden" name="parent" value="<%id%>" />\ + <input type="hidden" name="node" value="" />\ + </form>\ + </div>\ + </li>'; + + $(document).ready(function() { + init(); + }); +})(jQuery); + +$(document).ready(function() { + // add comment anchors for all paragraphs that are commentable + $('.sphinx-has-comment').comment(); + + // highlight search words in search results + $("div.context").each(function() { + var params = $.getQueryParameters(); + var terms = (params.q) ? params.q[0].split(/\s+/) : []; + var result = $(this); + $.each(terms, function() { + result.highlightText(this.toLowerCase(), 'highlighted'); + }); + }); + + // directly open comment window if requested + var anchor = document.location.hash; + if (anchor.substring(0, 9) == '#comment-') { + $('#ao' + anchor.substring(9)).click(); + document.location.hash = '#s' + anchor.substring(9); + } +}); diff --git a/docs/readthedocsFiles/_build/html/genindex.html b/docs/readthedocsFiles/_build/html/genindex.html new file mode 100644 index 00000000..d1d3289a --- /dev/null +++ b/docs/readthedocsFiles/_build/html/genindex.html @@ -0,0 +1,96 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="X-UA-Compatible" content="IE=Edge" /> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <title>Index — Rigbox 2.1 documentation</title> + <link rel="stylesheet" href="_static/alabaster.css" type="text/css" /> + <link rel="stylesheet" href="_static/pygments.css" type="text/css" /> + <script type="text/javascript" id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script> + <script type="text/javascript" src="_static/jquery.js"></script> + <script type="text/javascript" src="_static/underscore.js"></script> + <script type="text/javascript" src="_static/doctools.js"></script> + <link rel="index" title="Index" href="#" /> + <link rel="search" title="Search" href="search.html" /> + + <link rel="stylesheet" href="_static/custom.css" type="text/css" /> + + + <meta name="viewport" content="width=device-width, initial-scale=0.9, maximum-scale=0.9" /> + + </head><body> + + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + + + <div class="body" role="main"> + + +<h1 id="index">Index</h1> + +<div class="genindex-jumpbox"> + +</div> + + + </div> + + </div> + </div> + <div class="sphinxsidebar" role="navigation" aria-label="main navigation"> + <div class="sphinxsidebarwrapper"> +<h1 class="logo"><a href="root.html">Rigbox</a></h1> + + + + + + + + +<h3>Navigation</h3> + +<div class="relations"> +<h3>Related Topics</h3> +<ul> + <li><a href="root.html">Documentation overview</a><ul> + </ul></li> +</ul> +</div> +<div id="searchbox" style="display: none" role="search"> + <h3>Quick search</h3> + <div class="searchformwrapper"> + <form class="search" action="search.html" method="get"> + <input type="text" name="q" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + </div> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="footer"> + ©2019, Jai Bhagat, Miles Wells, Chris Burgess. + + | + Powered by <a href="http://sphinx-doc.org/">Sphinx 1.7.9</a> + & <a href="https://github.com/bitprophet/alabaster">Alabaster 0.7.11</a> + + </div> + + + + + </body> +</html> \ No newline at end of file diff --git a/docs/readthedocsFiles/_build/html/objects.inv b/docs/readthedocsFiles/_build/html/objects.inv new file mode 100644 index 0000000000000000000000000000000000000000..36158842b12aff9dd19820cebe424cc45d1fc455 GIT binary patch literal 248 zcmY#Z2rkIT%&Sny%qvUHE6FdaR47X=D$dN$Q!wIERtPA{&q_@$u~G=iOi#+M0E&b` zWUUl{?2wF9g`(8l#LT>u)FOraG=-9k%wmPK%$!sOAf23_TTql*T%4MsP+FXsm#$Ei zlbNK)RdLJP|Lo~A-kxg%H1s?-p7QkZIvaSwG{mF5>s9KMC(kr0nr6i8{HYlcA{O%M zkcvWA?dM>x;-H09U7tRCA9xk~X|m%g$C{2!AFj-K^5;%S>!h021!ul2`P1djYuqi` taLTJV__LRgx6$X%B0IZ+g}Wyi2k5qRIP!3|onTj4w!DauVOErfBLHvWX!ZaA literal 0 HcmV?d00001 diff --git a/docs/readthedocsFiles/_build/html/root.html b/docs/readthedocsFiles/_build/html/root.html new file mode 100644 index 00000000..0bf8e2bd --- /dev/null +++ b/docs/readthedocsFiles/_build/html/root.html @@ -0,0 +1,105 @@ + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="X-UA-Compatible" content="IE=Edge" /> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <title>Welcome to Rigbox’s documentation! — Rigbox 2.1 documentation</title> + <link rel="stylesheet" href="_static/alabaster.css" type="text/css" /> + <link rel="stylesheet" href="_static/pygments.css" type="text/css" /> + <script type="text/javascript" id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script> + <script type="text/javascript" src="_static/jquery.js"></script> + <script type="text/javascript" src="_static/underscore.js"></script> + <script type="text/javascript" src="_static/doctools.js"></script> + <link rel="index" title="Index" href="genindex.html" /> + <link rel="search" title="Search" href="search.html" /> + + <link rel="stylesheet" href="_static/custom.css" type="text/css" /> + + + <meta name="viewport" content="width=device-width, initial-scale=0.9, maximum-scale=0.9" /> + + </head><body> + + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + + + <div class="body" role="main"> + + <div class="section" id="welcome-to-rigbox-s-documentation"> +<h1>Welcome to Rigbox’s documentation!<a class="headerlink" href="#welcome-to-rigbox-s-documentation" title="Permalink to this headline">¶</a></h1> +<div class="toctree-wrapper compound"> +</div> +</div> +<div class="section" id="indices-and-tables"> +<h1>Indices and tables<a class="headerlink" href="#indices-and-tables" title="Permalink to this headline">¶</a></h1> +<ul class="simple"> +<li><a class="reference internal" href="genindex.html"><span class="std std-ref">Index</span></a></li> +<li><a class="reference internal" href="py-modindex.html"><span class="std std-ref">Module Index</span></a></li> +<li><a class="reference internal" href="search.html"><span class="std std-ref">Search Page</span></a></li> +</ul> +</div> + + + </div> + + </div> + </div> + <div class="sphinxsidebar" role="navigation" aria-label="main navigation"> + <div class="sphinxsidebarwrapper"> +<h1 class="logo"><a href="#">Rigbox</a></h1> + + + + + + + + +<h3>Navigation</h3> + +<div class="relations"> +<h3>Related Topics</h3> +<ul> + <li><a href="#">Documentation overview</a><ul> + </ul></li> +</ul> +</div> +<div id="searchbox" style="display: none" role="search"> + <h3>Quick search</h3> + <div class="searchformwrapper"> + <form class="search" action="search.html" method="get"> + <input type="text" name="q" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + </div> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="footer"> + ©2019, Jai Bhagat, Miles Wells, Chris Burgess. + + | + Powered by <a href="http://sphinx-doc.org/">Sphinx 1.7.9</a> + & <a href="https://github.com/bitprophet/alabaster">Alabaster 0.7.11</a> + + | + <a href="_sources/root.rst.txt" + rel="nofollow">Page source</a> + </div> + + + + + </body> +</html> \ No newline at end of file diff --git a/docs/readthedocsFiles/_build/html/search.html b/docs/readthedocsFiles/_build/html/search.html new file mode 100644 index 00000000..7c832514 --- /dev/null +++ b/docs/readthedocsFiles/_build/html/search.html @@ -0,0 +1,107 @@ + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="X-UA-Compatible" content="IE=Edge" /> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <title>Search — Rigbox 2.1 documentation</title> + <link rel="stylesheet" href="_static/alabaster.css" type="text/css" /> + <link rel="stylesheet" href="_static/pygments.css" type="text/css" /> + <script type="text/javascript" id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script> + <script type="text/javascript" src="_static/jquery.js"></script> + <script type="text/javascript" src="_static/underscore.js"></script> + <script type="text/javascript" src="_static/doctools.js"></script> + <script type="text/javascript" src="_static/searchtools.js"></script> + <link rel="index" title="Index" href="genindex.html" /> + <link rel="search" title="Search" href="#" /> + <script type="text/javascript"> + jQuery(function() { Search.loadIndex("searchindex.js"); }); + </script> + + <script type="text/javascript" id="searchindexloader"></script> + + + <link rel="stylesheet" href="_static/custom.css" type="text/css" /> + + + <meta name="viewport" content="width=device-width, initial-scale=0.9, maximum-scale=0.9" /> + + + </head><body> + + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + + + <div class="body" role="main"> + + <h1 id="search-documentation">Search</h1> + <div id="fallback" class="admonition warning"> + <script type="text/javascript">$('#fallback').hide();</script> + <p> + Please activate JavaScript to enable the search + functionality. + </p> + </div> + <p> + From here you can search these documents. Enter your search + words into the box below and click "search". Note that the search + function will automatically search for all of the words. Pages + containing fewer words won't appear in the result list. + </p> + <form action="" method="get"> + <input type="text" name="q" value="" /> + <input type="submit" value="search" /> + <span id="search-progress" style="padding-left: 10px"></span> + </form> + + <div id="search-results"> + + </div> + + </div> + + </div> + </div> + <div class="sphinxsidebar" role="navigation" aria-label="main navigation"> + <div class="sphinxsidebarwrapper"> +<h1 class="logo"><a href="root.html">Rigbox</a></h1> + + + + + + + + +<h3>Navigation</h3> + +<div class="relations"> +<h3>Related Topics</h3> +<ul> + <li><a href="root.html">Documentation overview</a><ul> + </ul></li> +</ul> +</div> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="footer"> + ©2019, Jai Bhagat, Miles Wells, Chris Burgess. + + | + Powered by <a href="http://sphinx-doc.org/">Sphinx 1.7.9</a> + & <a href="https://github.com/bitprophet/alabaster">Alabaster 0.7.11</a> + + </div> + + + + + </body> +</html> \ No newline at end of file diff --git a/docs/readthedocsFiles/_build/html/searchindex.js b/docs/readthedocsFiles/_build/html/searchindex.js new file mode 100644 index 00000000..f05d463b --- /dev/null +++ b/docs/readthedocsFiles/_build/html/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({docnames:["root"],envversion:55,filenames:["root.rst"],objects:{},objnames:{},objtypes:{},terms:{index:0,modul:0,page:0,search:0},titles:["Welcome to Rigbox\u2019s documentation!"],titleterms:{document:0,indic:0,rigbox:0,tabl:0,welcom:0}}) \ No newline at end of file diff --git a/docs/readthedocsFiles/conf.py b/docs/readthedocsFiles/conf.py new file mode 100644 index 00000000..4d304561 --- /dev/null +++ b/docs/readthedocsFiles/conf.py @@ -0,0 +1,197 @@ +# -*- coding: utf-8 -*- +# +# Configuration file for the Sphinx documentation builder. +# +# This file does only contain a selection of the most common options. For a +# full list see the documentation: +# http://www.sphinx-doc.org/en/master/config + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + + +# -- Project information ----------------------------------------------------- + +project = 'Rigbox' +copyright = '2019, Jai Bhagat, Miles Wells, Chris Burgess' +author = 'Jai Bhagat, Miles Wells, Chris Burgess' + +# The short X.Y version +version = '' +# The full version, including alpha/beta/rc tags +release = '2.1' + + +# -- General configuration --------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.doctest', + 'sphinx.ext.intersphinx', + 'sphinx.ext.todo', + 'sphinx.ext.coverage', + 'sphinx.ext.imgmath', + 'sphinx.ext.ifconfig', + 'sphinx.ext.githubpages', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'root' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path . +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'alabaster' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# The default sidebars (for documents that don't match any pattern) are +# defined by theme itself. Builtin themes are using these templates by +# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', +# 'searchbox.html']``. +# +# html_sidebars = {} + + +# -- Options for HTMLHelp output --------------------------------------------- + +# Output file base name for HTML help builder. +htmlhelp_basename = 'Rigboxdoc' + + +# -- Options for LaTeX output ------------------------------------------------ + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'Rigbox.tex', 'Rigbox Documentation', + 'Jai Bhagat, Miles Wells, Chris Burgess', 'manual'), +] + + +# -- Options for manual page output ------------------------------------------ + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'rigbox', 'Rigbox Documentation', + [author], 1) +] + + +# -- Options for Texinfo output ---------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'Rigbox', 'Rigbox Documentation', + author, 'Rigbox', 'One line description of project.', + 'Miscellaneous'), +] + + +# -- Options for Epub output ------------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = project +epub_author = author +epub_publisher = author +epub_copyright = copyright + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +# +# epub_identifier = '' + +# A unique identification for the text. +# +# epub_uid = '' + +# A list of files that should not be packed into the epub file. +epub_exclude_files = ['search.html'] + + +# -- Extension configuration ------------------------------------------------- + +# -- Options for intersphinx extension --------------------------------------- + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = {'https://docs.python.org/': None} + +# -- Options for todo extension ---------------------------------------------- + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = True \ No newline at end of file diff --git a/docs/readthedocsFiles/make.bat b/docs/readthedocsFiles/make.bat new file mode 100644 index 00000000..9ccf5f91 --- /dev/null +++ b/docs/readthedocsFiles/make.bat @@ -0,0 +1,36 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build +set SPHINXPROJ=Rigbox + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% + +:end +popd diff --git a/docs/readthedocsFiles/root.rst b/docs/readthedocsFiles/root.rst new file mode 100644 index 00000000..22c68b15 --- /dev/null +++ b/docs/readthedocsFiles/root.rst @@ -0,0 +1,20 @@ +.. Rigbox documentation master file, created by + sphinx-quickstart on Fri May 24 13:07:11 2019. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to Rigbox's documentation! +================================== + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` From 5f5eac7311089d1d41d5c8a7a935724deb521e8b Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Fri, 24 May 2019 15:42:53 +0300 Subject: [PATCH 373/393] Create index.rst Added bare-bones index file --- docs/readthedocsFiles/index.rst | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 docs/readthedocsFiles/index.rst diff --git a/docs/readthedocsFiles/index.rst b/docs/readthedocsFiles/index.rst new file mode 100644 index 00000000..0db42692 --- /dev/null +++ b/docs/readthedocsFiles/index.rst @@ -0,0 +1,22 @@ +Rigbox +================================= + +Rigbox is a a high-performance, open-source software toolbox for managing behavioral neuroscience experiments. Initially developed to probe mouse behavior for the Steering Wheel Setup, Rigbox is under active, test-driven development to encompass a variety of experimental paradigms across behavioral neuroscience. Rigbox simplifies hardware/software interfacing, synchronizes data streams from multiple sources, manages experimental data via communication with a remote database, and creates an environment where experimental parameters can be easily monitored and manipulated. Rigbox’s object-oriented paradigm facilitates a modular approach to designing experiments. Rigbox requires two machines, one for stimulus presentation ('the stimulus computer' or 'sc') and another for controlling and monitoring the experiment ('the master computer' or 'mc'). + +.. _MATLAB: http://uk.mathworks.com/products/matlab/ +.. _Psychtoolbox: http://psychtoolbox.org/ + +Table of contents: +================== + +.. toctree:: + :maxdepth: 2 + + installation.rst + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`UML` +* :ref:`search` From 8904582390f17f6853c6495ca3b1a4c1e8f34a34 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 28 May 2019 18:55:17 +0300 Subject: [PATCH 374/393] AlyxPanel automatically actived when databaseURL field is defined in paths file --- +eui/MControl.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/+eui/MControl.m b/+eui/MControl.m index ff44471b..4f4ff1df 100644 --- a/+eui/MControl.m +++ b/+eui/MControl.m @@ -763,7 +763,8 @@ function buildUI(obj, parent) % Parent here is the MC window (figure) leftSideBox.Heights = [55 22]; % Create the Alyx panel - obj.AlyxPanel = eui.AlyxPanel(headerBox); + url = char(getOr(dat.paths, 'databaseURL', '')); + obj.AlyxPanel = eui.AlyxPanel(headerBox, isempty(url)); addlistener(obj.NewExpSubject, 'SelectionChanged', @(src, evt)obj.AlyxPanel.dispWaterReq(src, evt)); addlistener(obj.LogSubject, 'SelectionChanged', @(src, evt)obj.AlyxPanel.dispWaterReq(src, evt)); From 5cf2270a14259631e3413d13682d9eb7397aeac5 Mon Sep 17 00:00:00 2001 From: jaib1 <j.bhagat@ucl.ac.uk> Date: Tue, 28 May 2019 17:25:27 +0100 Subject: [PATCH 375/393] updated signals submodule (added '+audstream' source code) --- signals | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/signals b/signals index 0152a842..500101aa 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit 0152a8424340f45b29b0fd8388c3bedc601d641b +Subproject commit 500101aaf970400fca6656723a7faad348eee884 From 0ab61b155eb87480d92adce95e668b25ea0de492 Mon Sep 17 00:00:00 2001 From: jaib1 <j.bhagat@ucl.ac.uk> Date: Tue, 28 May 2019 17:35:43 +0100 Subject: [PATCH 376/393] added java websocket source code --- LICENSE.md | 202 + cb-tools/java-source/.gitignore | 16 + cb-tools/java-source/.travis.yml | 5 + cb-tools/java-source/CodeFormatterProfile.xml | 279 + cb-tools/java-source/LICENSE | 22 + cb-tools/java-source/README.markdown | 163 + .../autobahn reports/clients/index.html | 3614 ++++++++++++ .../autobahn reports/servers/index.html | 3614 ++++++++++++ .../tootallnate_websocket_case_3_2.html | 305 ++ .../tootallnate_websocket_case_4_1_3.html | 305 ++ .../tootallnate_websocket_case_4_1_4.html | 306 ++ .../tootallnate_websocket_case_4_2_3.html | 305 ++ .../tootallnate_websocket_case_4_2_4.html | 306 ++ .../tootallnate_websocket_case_6_20_1.html | 304 + .../tootallnate_websocket_case_6_20_2.html | 304 + .../tootallnate_websocket_case_6_20_3.html | 304 + .../tootallnate_websocket_case_6_20_4.html | 304 + .../tootallnate_websocket_case_6_20_5.html | 304 + .../tootallnate_websocket_case_6_20_6.html | 304 + .../tootallnate_websocket_case_6_20_7.html | 304 + .../tootallnate_websocket_case_6_21_1.html | 304 + .../tootallnate_websocket_case_6_21_2.html | 304 + .../tootallnate_websocket_case_6_21_3.html | 304 + .../tootallnate_websocket_case_6_21_4.html | 304 + .../tootallnate_websocket_case_6_21_5.html | 304 + .../tootallnate_websocket_case_6_21_6.html | 304 + .../tootallnate_websocket_case_6_21_7.html | 304 + .../tootallnate_websocket_case_6_21_8.html | 304 + .../tootallnate_websocket_case_6_3_1.html | 304 + .../tootallnate_websocket_case_6_3_2.html | 365 ++ .../tootallnate_websocket_case_6_4_1.html | 318 ++ .../tootallnate_websocket_case_6_4_2.html | 316 ++ .../tootallnate_websocket_case_6_4_3.html | 312 ++ .../tootallnate_websocket_case_6_4_4.html | 312 ++ .../tootallnate_websocket_case_6_5_1.html | 303 + cb-tools/java-source/build.gradle | 44 + cb-tools/java-source/build.xml | 29 + cb-tools/java-source/dist/java_websocket.jar | Bin 0 -> 88934 bytes .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 51348 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + cb-tools/java-source/gradlew | 164 + cb-tools/java-source/gradlew.bat | 90 + cb-tools/java-source/index.html | 88 + cb-tools/java-source/java-source/.gitignore | 16 + cb-tools/java-source/java-source/.travis.yml | 5 + .../java-source/CodeFormatterProfile.xml | 279 + cb-tools/java-source/java-source/LICENSE | 22 + .../java-source/java-source/README.markdown | 163 + .../autobahn reports/clients/index.html | 3614 ++++++++++++ .../autobahn reports/servers/index.html | 3614 ++++++++++++ .../tootallnate_websocket_case_3_2.html | 305 ++ .../tootallnate_websocket_case_4_1_3.html | 305 ++ .../tootallnate_websocket_case_4_1_4.html | 306 ++ .../tootallnate_websocket_case_4_2_3.html | 305 ++ .../tootallnate_websocket_case_4_2_4.html | 306 ++ .../tootallnate_websocket_case_6_20_1.html | 304 + .../tootallnate_websocket_case_6_20_2.html | 304 + .../tootallnate_websocket_case_6_20_3.html | 304 + .../tootallnate_websocket_case_6_20_4.html | 304 + .../tootallnate_websocket_case_6_20_5.html | 304 + .../tootallnate_websocket_case_6_20_6.html | 304 + .../tootallnate_websocket_case_6_20_7.html | 304 + .../tootallnate_websocket_case_6_21_1.html | 304 + .../tootallnate_websocket_case_6_21_2.html | 304 + .../tootallnate_websocket_case_6_21_3.html | 304 + .../tootallnate_websocket_case_6_21_4.html | 304 + .../tootallnate_websocket_case_6_21_5.html | 304 + .../tootallnate_websocket_case_6_21_6.html | 304 + .../tootallnate_websocket_case_6_21_7.html | 304 + .../tootallnate_websocket_case_6_21_8.html | 304 + .../tootallnate_websocket_case_6_3_1.html | 304 + .../tootallnate_websocket_case_6_3_2.html | 365 ++ .../tootallnate_websocket_case_6_4_1.html | 318 ++ .../tootallnate_websocket_case_6_4_2.html | 316 ++ .../tootallnate_websocket_case_6_4_3.html | 312 ++ .../tootallnate_websocket_case_6_4_4.html | 312 ++ .../tootallnate_websocket_case_6_5_1.html | 303 + cb-tools/java-source/java-source/build.gradle | 44 + cb-tools/java-source/java-source/build.xml | 29 + .../java-source/dist/java_websocket.jar | Bin 0 -> 88934 bytes .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 51348 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + cb-tools/java-source/java-source/gradlew | 164 + cb-tools/java-source/java-source/gradlew.bat | 90 + cb-tools/java-source/java-source/index.html | 88 + cb-tools/java-source/java-source/pom.xml | 119 + .../src/main/example/ChatClient.java | 163 + .../src/main/example/ChatServer.java | 99 + .../src/main/example/ExampleClient.java | 54 + .../main/example/FragmentedFramesExample.java | 57 + .../src/main/example/ProxyClientExample.java | 12 + .../src/main/example/SSLClientExample.java | 99 + .../src/main/example/SSLServerExample.java | 50 + .../src/main/example/ServerStressTest.java | 217 + .../java-source/src/main/example/chat.html | 66 + .../java-source/src/main/example/prototype.js | 4874 +++++++++++++++++ .../example/web-socket-js/WebSocketMain.swf | Bin 0 -> 177114 bytes .../src/main/example/web-socket-js/index.html | 29 + .../main/example/web-socket-js/swfobject.js | 4 + .../main/example/web-socket-js/web_socket.js | 389 ++ .../AbstractWrappedByteChannel.java | 75 + .../org/java_websocket/SSLSocketChannel2.java | 380 ++ .../java_websocket/SocketChannelIOHelper.java | 71 + .../java/org/java_websocket/WebSocket.java | 124 + .../org/java_websocket/WebSocketAdapter.java | 104 + .../org/java_websocket/WebSocketFactory.java | 12 + .../org/java_websocket/WebSocketImpl.java | 737 +++ .../org/java_websocket/WebSocketListener.java | 151 + .../java_websocket/WrappedByteChannel.java | 26 + .../client/AbstractClientProxyChannel.java | 38 + .../client/WebSocketClient.java | 454 ++ .../java/org/java_websocket/drafts/Draft.java | 228 + .../org/java_websocket/drafts/Draft_10.java | 397 ++ .../org/java_websocket/drafts/Draft_17.java | 28 + .../org/java_websocket/drafts/Draft_75.java | 206 + .../org/java_websocket/drafts/Draft_76.java | 242 + .../IncompleteHandshakeException.java | 20 + .../exceptions/InvalidDataException.java | 34 + .../exceptions/InvalidFrameException.java | 27 + .../exceptions/InvalidHandshakeException.java | 28 + .../exceptions/LimitExedeedException.java | 20 + .../exceptions/NotSendableException.java | 25 + .../WebsocketNotConnectedException.java | 5 + .../java_websocket/framing/CloseFrame.java | 98 + .../framing/CloseFrameBuilder.java | 123 + .../java_websocket/framing/FrameBuilder.java | 17 + .../org/java_websocket/framing/Framedata.java | 17 + .../framing/FramedataImpl1.java | 110 + .../handshake/ClientHandshake.java | 6 + .../handshake/ClientHandshakeBuilder.java | 5 + .../handshake/HandshakeBuilder.java | 6 + .../handshake/HandshakeImpl1Client.java | 18 + .../handshake/HandshakeImpl1Server.java | 29 + .../handshake/Handshakedata.java | 10 + .../handshake/HandshakedataImpl1.java | 60 + .../handshake/ServerHandshake.java | 6 + .../handshake/ServerHandshakeBuilder.java | 6 + .../DefaultSSLWebSocketServerFactory.java | 51 + .../server/DefaultWebSocketServerFactory.java | 26 + .../server/WebSocketServer.java | 743 +++ .../java/org/java_websocket/util/Base64.java | 2065 +++++++ .../java_websocket/util/Charsetfunctions.java | 90 + .../src/test/java/AutobahnClientTest.java | 163 + .../src/test/java/AutobahnServerTest.java | 72 + cb-tools/java-source/pom.xml | 119 + .../src/main/example/ChatClient.java | 163 + .../src/main/example/ChatServer.java | 99 + .../src/main/example/ExampleClient.java | 54 + .../main/example/FragmentedFramesExample.java | 57 + .../src/main/example/ProxyClientExample.java | 12 + .../src/main/example/SSLClientExample.java | 99 + .../src/main/example/SSLServerExample.java | 50 + .../src/main/example/ServerStressTest.java | 217 + .../java-source/src/main/example/chat.html | 66 + .../java-source/src/main/example/prototype.js | 4874 +++++++++++++++++ .../example/web-socket-js/WebSocketMain.swf | Bin 0 -> 177114 bytes .../src/main/example/web-socket-js/index.html | 29 + .../main/example/web-socket-js/swfobject.js | 4 + .../main/example/web-socket-js/web_socket.js | 389 ++ .../AbstractWrappedByteChannel.java | 75 + .../org/java_websocket/SSLSocketChannel2.java | 380 ++ .../java_websocket/SocketChannelIOHelper.java | 71 + .../java/org/java_websocket/WebSocket.java | 124 + .../org/java_websocket/WebSocketAdapter.java | 104 + .../org/java_websocket/WebSocketFactory.java | 12 + .../org/java_websocket/WebSocketImpl.java | 737 +++ .../org/java_websocket/WebSocketListener.java | 151 + .../java_websocket/WrappedByteChannel.java | 26 + .../client/AbstractClientProxyChannel.java | 38 + .../client/WebSocketClient.java | 454 ++ .../java/org/java_websocket/drafts/Draft.java | 228 + .../org/java_websocket/drafts/Draft_10.java | 397 ++ .../org/java_websocket/drafts/Draft_17.java | 28 + .../org/java_websocket/drafts/Draft_75.java | 206 + .../org/java_websocket/drafts/Draft_76.java | 242 + .../IncompleteHandshakeException.java | 20 + .../exceptions/InvalidDataException.java | 34 + .../exceptions/InvalidFrameException.java | 27 + .../exceptions/InvalidHandshakeException.java | 28 + .../exceptions/LimitExedeedException.java | 20 + .../exceptions/NotSendableException.java | 25 + .../WebsocketNotConnectedException.java | 5 + .../java_websocket/framing/CloseFrame.java | 98 + .../framing/CloseFrameBuilder.java | 123 + .../java_websocket/framing/FrameBuilder.java | 17 + .../org/java_websocket/framing/Framedata.java | 17 + .../framing/FramedataImpl1.java | 110 + .../handshake/ClientHandshake.java | 6 + .../handshake/ClientHandshakeBuilder.java | 5 + .../handshake/HandshakeBuilder.java | 6 + .../handshake/HandshakeImpl1Client.java | 18 + .../handshake/HandshakeImpl1Server.java | 29 + .../handshake/Handshakedata.java | 10 + .../handshake/HandshakedataImpl1.java | 60 + .../handshake/ServerHandshake.java | 6 + .../handshake/ServerHandshakeBuilder.java | 6 + .../DefaultSSLWebSocketServerFactory.java | 51 + .../server/DefaultWebSocketServerFactory.java | 26 + .../server/WebSocketServer.java | 743 +++ .../java/org/java_websocket/util/Base64.java | 2065 +++++++ .../java_websocket/util/Charsetfunctions.java | 90 + .../src/test/java/AutobahnClientTest.java | 163 + .../src/test/java/AutobahnServerTest.java | 72 + 203 files changed, 59874 insertions(+) create mode 100644 LICENSE.md create mode 100644 cb-tools/java-source/.gitignore create mode 100644 cb-tools/java-source/.travis.yml create mode 100644 cb-tools/java-source/CodeFormatterProfile.xml create mode 100644 cb-tools/java-source/LICENSE create mode 100644 cb-tools/java-source/README.markdown create mode 100644 cb-tools/java-source/autobahn reports/clients/index.html create mode 100644 cb-tools/java-source/autobahn reports/servers/index.html create mode 100644 cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_3_2.html create mode 100644 cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_4_1_3.html create mode 100644 cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_4_1_4.html create mode 100644 cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_4_2_3.html create mode 100644 cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_4_2_4.html create mode 100644 cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_1.html create mode 100644 cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_2.html create mode 100644 cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_3.html create mode 100644 cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_4.html create mode 100644 cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_5.html create mode 100644 cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_6.html create mode 100644 cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_7.html create mode 100644 cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_1.html create mode 100644 cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_2.html create mode 100644 cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_3.html create mode 100644 cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_4.html create mode 100644 cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_5.html create mode 100644 cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_6.html create mode 100644 cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_7.html create mode 100644 cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_8.html create mode 100644 cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_3_1.html create mode 100644 cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_3_2.html create mode 100644 cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_4_1.html create mode 100644 cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_4_2.html create mode 100644 cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_4_3.html create mode 100644 cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_4_4.html create mode 100644 cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_5_1.html create mode 100644 cb-tools/java-source/build.gradle create mode 100644 cb-tools/java-source/build.xml create mode 100644 cb-tools/java-source/dist/java_websocket.jar create mode 100644 cb-tools/java-source/gradle/wrapper/gradle-wrapper.jar create mode 100644 cb-tools/java-source/gradle/wrapper/gradle-wrapper.properties create mode 100644 cb-tools/java-source/gradlew create mode 100644 cb-tools/java-source/gradlew.bat create mode 100644 cb-tools/java-source/index.html create mode 100644 cb-tools/java-source/java-source/.gitignore create mode 100644 cb-tools/java-source/java-source/.travis.yml create mode 100644 cb-tools/java-source/java-source/CodeFormatterProfile.xml create mode 100644 cb-tools/java-source/java-source/LICENSE create mode 100644 cb-tools/java-source/java-source/README.markdown create mode 100644 cb-tools/java-source/java-source/autobahn reports/clients/index.html create mode 100644 cb-tools/java-source/java-source/autobahn reports/servers/index.html create mode 100644 cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_3_2.html create mode 100644 cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_4_1_3.html create mode 100644 cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_4_1_4.html create mode 100644 cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_4_2_3.html create mode 100644 cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_4_2_4.html create mode 100644 cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_1.html create mode 100644 cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_2.html create mode 100644 cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_3.html create mode 100644 cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_4.html create mode 100644 cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_5.html create mode 100644 cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_6.html create mode 100644 cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_7.html create mode 100644 cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_1.html create mode 100644 cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_2.html create mode 100644 cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_3.html create mode 100644 cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_4.html create mode 100644 cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_5.html create mode 100644 cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_6.html create mode 100644 cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_7.html create mode 100644 cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_8.html create mode 100644 cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_3_1.html create mode 100644 cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_3_2.html create mode 100644 cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_4_1.html create mode 100644 cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_4_2.html create mode 100644 cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_4_3.html create mode 100644 cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_4_4.html create mode 100644 cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_5_1.html create mode 100644 cb-tools/java-source/java-source/build.gradle create mode 100644 cb-tools/java-source/java-source/build.xml create mode 100644 cb-tools/java-source/java-source/dist/java_websocket.jar create mode 100644 cb-tools/java-source/java-source/gradle/wrapper/gradle-wrapper.jar create mode 100644 cb-tools/java-source/java-source/gradle/wrapper/gradle-wrapper.properties create mode 100644 cb-tools/java-source/java-source/gradlew create mode 100644 cb-tools/java-source/java-source/gradlew.bat create mode 100644 cb-tools/java-source/java-source/index.html create mode 100644 cb-tools/java-source/java-source/pom.xml create mode 100644 cb-tools/java-source/java-source/src/main/example/ChatClient.java create mode 100644 cb-tools/java-source/java-source/src/main/example/ChatServer.java create mode 100644 cb-tools/java-source/java-source/src/main/example/ExampleClient.java create mode 100644 cb-tools/java-source/java-source/src/main/example/FragmentedFramesExample.java create mode 100644 cb-tools/java-source/java-source/src/main/example/ProxyClientExample.java create mode 100644 cb-tools/java-source/java-source/src/main/example/SSLClientExample.java create mode 100644 cb-tools/java-source/java-source/src/main/example/SSLServerExample.java create mode 100644 cb-tools/java-source/java-source/src/main/example/ServerStressTest.java create mode 100644 cb-tools/java-source/java-source/src/main/example/chat.html create mode 100644 cb-tools/java-source/java-source/src/main/example/prototype.js create mode 100644 cb-tools/java-source/java-source/src/main/example/web-socket-js/WebSocketMain.swf create mode 100644 cb-tools/java-source/java-source/src/main/example/web-socket-js/index.html create mode 100644 cb-tools/java-source/java-source/src/main/example/web-socket-js/swfobject.js create mode 100644 cb-tools/java-source/java-source/src/main/example/web-socket-js/web_socket.js create mode 100644 cb-tools/java-source/java-source/src/main/java/org/java_websocket/AbstractWrappedByteChannel.java create mode 100644 cb-tools/java-source/java-source/src/main/java/org/java_websocket/SSLSocketChannel2.java create mode 100644 cb-tools/java-source/java-source/src/main/java/org/java_websocket/SocketChannelIOHelper.java create mode 100644 cb-tools/java-source/java-source/src/main/java/org/java_websocket/WebSocket.java create mode 100644 cb-tools/java-source/java-source/src/main/java/org/java_websocket/WebSocketAdapter.java create mode 100644 cb-tools/java-source/java-source/src/main/java/org/java_websocket/WebSocketFactory.java create mode 100644 cb-tools/java-source/java-source/src/main/java/org/java_websocket/WebSocketImpl.java create mode 100644 cb-tools/java-source/java-source/src/main/java/org/java_websocket/WebSocketListener.java create mode 100644 cb-tools/java-source/java-source/src/main/java/org/java_websocket/WrappedByteChannel.java create mode 100644 cb-tools/java-source/java-source/src/main/java/org/java_websocket/client/AbstractClientProxyChannel.java create mode 100644 cb-tools/java-source/java-source/src/main/java/org/java_websocket/client/WebSocketClient.java create mode 100644 cb-tools/java-source/java-source/src/main/java/org/java_websocket/drafts/Draft.java create mode 100644 cb-tools/java-source/java-source/src/main/java/org/java_websocket/drafts/Draft_10.java create mode 100644 cb-tools/java-source/java-source/src/main/java/org/java_websocket/drafts/Draft_17.java create mode 100644 cb-tools/java-source/java-source/src/main/java/org/java_websocket/drafts/Draft_75.java create mode 100644 cb-tools/java-source/java-source/src/main/java/org/java_websocket/drafts/Draft_76.java create mode 100644 cb-tools/java-source/java-source/src/main/java/org/java_websocket/exceptions/IncompleteHandshakeException.java create mode 100644 cb-tools/java-source/java-source/src/main/java/org/java_websocket/exceptions/InvalidDataException.java create mode 100644 cb-tools/java-source/java-source/src/main/java/org/java_websocket/exceptions/InvalidFrameException.java create mode 100644 cb-tools/java-source/java-source/src/main/java/org/java_websocket/exceptions/InvalidHandshakeException.java create mode 100644 cb-tools/java-source/java-source/src/main/java/org/java_websocket/exceptions/LimitExedeedException.java create mode 100644 cb-tools/java-source/java-source/src/main/java/org/java_websocket/exceptions/NotSendableException.java create mode 100644 cb-tools/java-source/java-source/src/main/java/org/java_websocket/exceptions/WebsocketNotConnectedException.java create mode 100644 cb-tools/java-source/java-source/src/main/java/org/java_websocket/framing/CloseFrame.java create mode 100644 cb-tools/java-source/java-source/src/main/java/org/java_websocket/framing/CloseFrameBuilder.java create mode 100644 cb-tools/java-source/java-source/src/main/java/org/java_websocket/framing/FrameBuilder.java create mode 100644 cb-tools/java-source/java-source/src/main/java/org/java_websocket/framing/Framedata.java create mode 100644 cb-tools/java-source/java-source/src/main/java/org/java_websocket/framing/FramedataImpl1.java create mode 100644 cb-tools/java-source/java-source/src/main/java/org/java_websocket/handshake/ClientHandshake.java create mode 100644 cb-tools/java-source/java-source/src/main/java/org/java_websocket/handshake/ClientHandshakeBuilder.java create mode 100644 cb-tools/java-source/java-source/src/main/java/org/java_websocket/handshake/HandshakeBuilder.java create mode 100644 cb-tools/java-source/java-source/src/main/java/org/java_websocket/handshake/HandshakeImpl1Client.java create mode 100644 cb-tools/java-source/java-source/src/main/java/org/java_websocket/handshake/HandshakeImpl1Server.java create mode 100644 cb-tools/java-source/java-source/src/main/java/org/java_websocket/handshake/Handshakedata.java create mode 100644 cb-tools/java-source/java-source/src/main/java/org/java_websocket/handshake/HandshakedataImpl1.java create mode 100644 cb-tools/java-source/java-source/src/main/java/org/java_websocket/handshake/ServerHandshake.java create mode 100644 cb-tools/java-source/java-source/src/main/java/org/java_websocket/handshake/ServerHandshakeBuilder.java create mode 100644 cb-tools/java-source/java-source/src/main/java/org/java_websocket/server/DefaultSSLWebSocketServerFactory.java create mode 100644 cb-tools/java-source/java-source/src/main/java/org/java_websocket/server/DefaultWebSocketServerFactory.java create mode 100644 cb-tools/java-source/java-source/src/main/java/org/java_websocket/server/WebSocketServer.java create mode 100644 cb-tools/java-source/java-source/src/main/java/org/java_websocket/util/Base64.java create mode 100644 cb-tools/java-source/java-source/src/main/java/org/java_websocket/util/Charsetfunctions.java create mode 100644 cb-tools/java-source/java-source/src/test/java/AutobahnClientTest.java create mode 100644 cb-tools/java-source/java-source/src/test/java/AutobahnServerTest.java create mode 100644 cb-tools/java-source/pom.xml create mode 100644 cb-tools/java-source/src/main/example/ChatClient.java create mode 100644 cb-tools/java-source/src/main/example/ChatServer.java create mode 100644 cb-tools/java-source/src/main/example/ExampleClient.java create mode 100644 cb-tools/java-source/src/main/example/FragmentedFramesExample.java create mode 100644 cb-tools/java-source/src/main/example/ProxyClientExample.java create mode 100644 cb-tools/java-source/src/main/example/SSLClientExample.java create mode 100644 cb-tools/java-source/src/main/example/SSLServerExample.java create mode 100644 cb-tools/java-source/src/main/example/ServerStressTest.java create mode 100644 cb-tools/java-source/src/main/example/chat.html create mode 100644 cb-tools/java-source/src/main/example/prototype.js create mode 100644 cb-tools/java-source/src/main/example/web-socket-js/WebSocketMain.swf create mode 100644 cb-tools/java-source/src/main/example/web-socket-js/index.html create mode 100644 cb-tools/java-source/src/main/example/web-socket-js/swfobject.js create mode 100644 cb-tools/java-source/src/main/example/web-socket-js/web_socket.js create mode 100644 cb-tools/java-source/src/main/java/org/java_websocket/AbstractWrappedByteChannel.java create mode 100644 cb-tools/java-source/src/main/java/org/java_websocket/SSLSocketChannel2.java create mode 100644 cb-tools/java-source/src/main/java/org/java_websocket/SocketChannelIOHelper.java create mode 100644 cb-tools/java-source/src/main/java/org/java_websocket/WebSocket.java create mode 100644 cb-tools/java-source/src/main/java/org/java_websocket/WebSocketAdapter.java create mode 100644 cb-tools/java-source/src/main/java/org/java_websocket/WebSocketFactory.java create mode 100644 cb-tools/java-source/src/main/java/org/java_websocket/WebSocketImpl.java create mode 100644 cb-tools/java-source/src/main/java/org/java_websocket/WebSocketListener.java create mode 100644 cb-tools/java-source/src/main/java/org/java_websocket/WrappedByteChannel.java create mode 100644 cb-tools/java-source/src/main/java/org/java_websocket/client/AbstractClientProxyChannel.java create mode 100644 cb-tools/java-source/src/main/java/org/java_websocket/client/WebSocketClient.java create mode 100644 cb-tools/java-source/src/main/java/org/java_websocket/drafts/Draft.java create mode 100644 cb-tools/java-source/src/main/java/org/java_websocket/drafts/Draft_10.java create mode 100644 cb-tools/java-source/src/main/java/org/java_websocket/drafts/Draft_17.java create mode 100644 cb-tools/java-source/src/main/java/org/java_websocket/drafts/Draft_75.java create mode 100644 cb-tools/java-source/src/main/java/org/java_websocket/drafts/Draft_76.java create mode 100644 cb-tools/java-source/src/main/java/org/java_websocket/exceptions/IncompleteHandshakeException.java create mode 100644 cb-tools/java-source/src/main/java/org/java_websocket/exceptions/InvalidDataException.java create mode 100644 cb-tools/java-source/src/main/java/org/java_websocket/exceptions/InvalidFrameException.java create mode 100644 cb-tools/java-source/src/main/java/org/java_websocket/exceptions/InvalidHandshakeException.java create mode 100644 cb-tools/java-source/src/main/java/org/java_websocket/exceptions/LimitExedeedException.java create mode 100644 cb-tools/java-source/src/main/java/org/java_websocket/exceptions/NotSendableException.java create mode 100644 cb-tools/java-source/src/main/java/org/java_websocket/exceptions/WebsocketNotConnectedException.java create mode 100644 cb-tools/java-source/src/main/java/org/java_websocket/framing/CloseFrame.java create mode 100644 cb-tools/java-source/src/main/java/org/java_websocket/framing/CloseFrameBuilder.java create mode 100644 cb-tools/java-source/src/main/java/org/java_websocket/framing/FrameBuilder.java create mode 100644 cb-tools/java-source/src/main/java/org/java_websocket/framing/Framedata.java create mode 100644 cb-tools/java-source/src/main/java/org/java_websocket/framing/FramedataImpl1.java create mode 100644 cb-tools/java-source/src/main/java/org/java_websocket/handshake/ClientHandshake.java create mode 100644 cb-tools/java-source/src/main/java/org/java_websocket/handshake/ClientHandshakeBuilder.java create mode 100644 cb-tools/java-source/src/main/java/org/java_websocket/handshake/HandshakeBuilder.java create mode 100644 cb-tools/java-source/src/main/java/org/java_websocket/handshake/HandshakeImpl1Client.java create mode 100644 cb-tools/java-source/src/main/java/org/java_websocket/handshake/HandshakeImpl1Server.java create mode 100644 cb-tools/java-source/src/main/java/org/java_websocket/handshake/Handshakedata.java create mode 100644 cb-tools/java-source/src/main/java/org/java_websocket/handshake/HandshakedataImpl1.java create mode 100644 cb-tools/java-source/src/main/java/org/java_websocket/handshake/ServerHandshake.java create mode 100644 cb-tools/java-source/src/main/java/org/java_websocket/handshake/ServerHandshakeBuilder.java create mode 100644 cb-tools/java-source/src/main/java/org/java_websocket/server/DefaultSSLWebSocketServerFactory.java create mode 100644 cb-tools/java-source/src/main/java/org/java_websocket/server/DefaultWebSocketServerFactory.java create mode 100644 cb-tools/java-source/src/main/java/org/java_websocket/server/WebSocketServer.java create mode 100644 cb-tools/java-source/src/main/java/org/java_websocket/util/Base64.java create mode 100644 cb-tools/java-source/src/main/java/org/java_websocket/util/Charsetfunctions.java create mode 100644 cb-tools/java-source/src/test/java/AutobahnClientTest.java create mode 100644 cb-tools/java-source/src/test/java/AutobahnServerTest.java diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 00000000..e06d2081 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,202 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/cb-tools/java-source/.gitignore b/cb-tools/java-source/.gitignore new file mode 100644 index 00000000..d7e41aae --- /dev/null +++ b/cb-tools/java-source/.gitignore @@ -0,0 +1,16 @@ +*.class +.git +.gradle +build +*.svn +*~ +target +*.ipr +*.iml +*.iws +.settings +.project +.classpath +bin + +/doc \ No newline at end of file diff --git a/cb-tools/java-source/.travis.yml b/cb-tools/java-source/.travis.yml new file mode 100644 index 00000000..067c53e4 --- /dev/null +++ b/cb-tools/java-source/.travis.yml @@ -0,0 +1,5 @@ +language: java +jdk: + - oraclejdk7 + +script: "./gradlew test" diff --git a/cb-tools/java-source/CodeFormatterProfile.xml b/cb-tools/java-source/CodeFormatterProfile.xml new file mode 100644 index 00000000..73325ded --- /dev/null +++ b/cb-tools/java-source/CodeFormatterProfile.xml @@ -0,0 +1,279 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<profiles version="11"> +<profile kind="CodeFormatterProfile" name="Java-WebSocket CodeFormatterProfile" version="11"> +<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.disabling_tag" value="@formatter:off"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value="end_of_line"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" value="0"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value="16"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value="16"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value="48"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_binary_operator" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value="end_of_line"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="0"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.continuation_indentation" value="2"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value="16"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="0"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_binary_operator" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="0"/> +<setting id="org.eclipse.jdt.core.compiler.source" value="1.5"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value="16"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="0"/> +<setting id="org.eclipse.jdt.core.formatter.align_type_members_on_columns" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="16"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.lineSplit" value="600"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value="0"/> +<setting id="org.eclipse.jdt.core.formatter.indentation.size" value="4"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.enabling_tag" value="@formatter:on"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value="64"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_assignment" value="0"/> +<setting id="org.eclipse.jdt.core.compiler.problem.assertIdentifier" value="error"/> +<setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="tab"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method" value="0"/> +<setting id="org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="16"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value="end_of_line"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_method_declaration" value="0"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch" value="end_of_line"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value="do not insert"/> +<setting id="org.eclipse.jdt.core.compiler.problem.enumIdentifier" value="error"/> +<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" value="end_of_line"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value="end_of_line"/> +<setting id="org.eclipse.jdt.core.formatter.compact_else_if" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value="end_of_line"/> +<setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="4"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value="16"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.indent_empty_lines" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value="end_of_line"/> +<setting id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="16"/> +<setting id="org.eclipse.jdt.core.compiler.compliance" value="1.5"/> +<setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression" value="16"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="64"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_binary_expression" value="16"/> +<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value="end_of_line"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value="insert"/> +<setting id="org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode" value="enabled"/> +<setting id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_label" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value="16"/> +<setting id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="1000"/> +<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" value="1"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value="end_of_line"/> +<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="0"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.wrap_before_binary_operator" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value="0"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="64"/> +<setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_if" value="52"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="0"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.comment.format_html" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="16"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.comment.format_source_code" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value="16"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="insert"/> +<setting id="org.eclipse.jdt.core.compiler.codegen.targetPlatform" value="1.5"/> +<setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation" value="0"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.comment.format_header" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.comment.format_block_comments" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="0"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value="end_of_line"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value="0"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value="false"/> +</profile> +</profiles> diff --git a/cb-tools/java-source/LICENSE b/cb-tools/java-source/LICENSE new file mode 100644 index 00000000..5a93449e --- /dev/null +++ b/cb-tools/java-source/LICENSE @@ -0,0 +1,22 @@ + Copyright (c) 2010-2012 Nathan Rajlich + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. diff --git a/cb-tools/java-source/README.markdown b/cb-tools/java-source/README.markdown new file mode 100644 index 00000000..0aaab7a2 --- /dev/null +++ b/cb-tools/java-source/README.markdown @@ -0,0 +1,163 @@ +[![Build Status](https://travis-ci.org/ck1125/Java-WebSocket.png?branch=master)](https://travis-ci.org/ck1125/Java-WebSocket) +Java WebSockets +=============== + +This repository contains a barebones WebSocket server and client implementation +written in 100% Java. The underlying classes are implemented `java.nio`, which allows for a +non-blocking event-driven model (similar to the +[WebSocket API](http://dev.w3.org/html5/websockets/) for web browsers). + +Implemented WebSocket protocol versions are: + + * [RFC 6455](http://tools.ietf.org/html/rfc6455) + * [Hybi 17](http://tools.ietf.org/id/draft-ietf-hybi-thewebsocketprotocol-17.txt) + * [Hybi 10](http://tools.ietf.org/id/draft-ietf-hybi-thewebsocketprotocol-10.txt) + * [Hixie 76](http://tools.ietf.org/id/draft-hixie-thewebsocketprotocol-76.txt) + * [Hixie 75](http://tools.ietf.org/id/draft-hixie-thewebsocketprotocol-75.txt) + +[Here](https://github.com/TooTallNate/Java-WebSocket/wiki/Drafts) some more details about protocol versions/drafts. + + +##Build +You can build using Ant or Maven but there is nothing against just putting the source path ```src/main/java ``` on your applications buildpath. + +###Ant + +``` bash +ant +``` + +will create the javadoc of this library at ```doc/``` and build the library itself: ```dest/java_websocket.jar``` + +The ant targets are: ```compile```, ```jar```, ```doc``` and ```clean``` + +###Maven + +To use maven just add this dependency to your pom.xml: +```xml +<dependency> + <groupId>org.java-websocket</groupId> + <artifactId>Java-WebSocket</artifactId> + <version>1.3.0</version> +</dependency> +``` + +Running the Examples +------------------- + +**Note:** If you're on Windows, then replace the `:` (colon) in the classpath +in the commands below with a `;` (semicolon). + +After you build the library you can start the chat server (a `WebSocketServer` subclass): + +``` bash +java -cp build/examples:dist/java_websocket.jar ChatServer +``` + +Now that the server is started, you need to connect some clients. Run the +Java chat client (a `WebSocketClient` subclass): + +``` bash +java -cp build/examples:dist/java_websocket.jar ChatClient +``` + +The chat client is a simple Swing GUI application that allows you to send +messages to all other connected clients, and receive messages from others in a +text box. + +In the example folder is also a simple HTML file chat client `chat.html`, which can be opened by any browser. If the browser natively supports the WebSocket API, then it's +implementation will be used, otherwise it will fall back to a +[Flash-based WebSocket Implementation](http://github.com/gimite/web-socket-js). + + +Writing your own WebSocket Server +--------------------------------- + +The `org.java_websocket.server.WebSocketServer` abstract class implements the +server-side of the +[WebSocket Protocol](http://www.whatwg.org/specs/web-socket-protocol/). +A WebSocket server by itself doesn't do anything except establish socket +connections though HTTP. After that it's up to **your** subclass to add purpose. + + +Writing your own WebSocket Client +--------------------------------- + +The `org.java_websocket.server.WebSocketClient` abstract class can connect to +valid WebSocket servers. The constructor expects a valid `ws://` URI to +connect to. Important events `onOpen`, `onClose`, `onMessage` and `onIOError` +get fired throughout the life of the WebSocketClient, and must be implemented +in **your** subclass. + +WSS Support +--------------------------------- +This library supports wss. +To see how to use wss please take a look at the examples.<br> + +If you do not have a valid **certificate** in place then you will have to create a self signed one. +Browsers will simply refuse the connection in case of a bad certificate and will not ask the user to accept it. +So the first step will be to make a browser to accept your self signed certificate. ( https://bugzilla.mozilla.org/show_bug.cgi?id=594502 ).<br> +If the websocket server url is `wss://localhost:8000` visit the url `https://localhost:8000` with your browser. The browser will recognize the handshake and allow you to accept the certificate. This technique is also demonstrated in this [video](http://www.youtube.com/watch?v=F8lBdfAZPkU). + +The vm option `-Djavax.net.debug=all` can help to find out if there is a problem with the certificate. + +It is currently not possible to accept ws and wss connections at the same time via the same websocket server instance. + +For some reason firefox does not allow multible connections to the same wss server if the server uses a different port than the default port(443). + + +If you want to use `wss` on the android platfrom you should take a look at [this](http://blog.antoine.li/2010/10/22/android-trusting-ssl-certificates/). + +I ( @Davidiusdadi ) would be glad if you would give some feedback whether wss is working fine for you or not. + +Minimum Required JDK +-------------------- + +`Java-WebSocket` is known to work with: + + * Java 1.5 (aka SE 6) + * Android 1.6 (API 4) + +Other JRE implementations may work as well, but haven't been tested. + + +Testing in Android Emulator +--------------------------- + +Please note Android Emulator has issues using `IPv6 addresses`. Executing any +socket related code (like this library) inside it will address an error + +``` bash +java.net.SocketException: Bad address family +``` + +You have to manually disable `IPv6` by calling + +``` java +java.lang.System.setProperty("java.net.preferIPv6Addresses", "false"); +java.lang.System.setProperty("java.net.preferIPv4Stack", "true"); +``` + +somewhere in your project, before instantiating the `WebSocketClient` class. +You can check if you are currently testing in the Android Emulator like this + +``` java +if ("google_sdk".equals( Build.PRODUCT )) { + // ... disable IPv6 +} +``` + + +Getting Support +--------------- + +If you are looking for help using `Java-WebSocket` you might want to check out the +[#java-websocket](http://webchat.freenode.net/?channels=java-websocket) IRC room +on the FreeNode IRC network. + + +License +------- + +Everything found in this repo is licensed under an MIT license. See +the `LICENSE` file for specifics. diff --git a/cb-tools/java-source/autobahn reports/clients/index.html b/cb-tools/java-source/autobahn reports/clients/index.html new file mode 100644 index 00000000..5fa8f93d --- /dev/null +++ b/cb-tools/java-source/autobahn reports/clients/index.html @@ -0,0 +1,3614 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +table { + border-collapse: collapse; + border-spacing: 0px; +} + +td { + margin: 0; + border: 1px solid #fff; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + font-size: 0.9em; + color: #fff; +} + +table#agent_case_results { + border-collapse: collapse; + border-spacing: 0px; + border-radius: 10px; + margin-left: 20px; + margin-right: 20px; + margin-bottom: 40px; +} + +td.outcome_desc { + width: 100%; + color: #333; + font-size: 0.8em; +} + +tr.agent_case_result_row a { + color: #eee; +} + +td.agent { + color: #fff; + font-size: 1.0em; + text-align: center; + background-color: #048; + font-size: 0.8em; + word-wrap: break-word; + padding: 4px; + width: 140px; +} + +td.case { + background-color: #666; + text-align: left; + padding-left: 40px; + font-size: 0.9em; +} + +td.case_category { + color: #fff; + background-color: #000; + text-align: left; + padding-left: 20px; + font-size: 1.0em; +} + +td.case_subcategory { + color: #fff; + background-color: #333; + text-align: left; + padding-left: 30px; + font-size: 0.9em; +} + +td.close { + width: 15px; + padding: 6px; + font-size: 0.7em; + color: #fff; + min-width: 0px; +} + +td.case_ok { + background-color: #0a0; + text-align: center; +} + +td.case_almost { + background-color: #6d6; + text-align: center; +} + +td.case_non_strict, td.case_no_close { + background-color: #9a0; + text-align: center; +} + +td.case_info { + background-color: #4095BF; + text-align: center; +} + +td.case_failed { + background-color: #900; + text-align: center; +} + +td.case_missing { + color: #fff; + background-color: #a05a2c; + text-align: center; +} + +span.case_duration { + font-size: 0.7em; + color: #fff; +} + +*.unselectable { + user-select: none; + -moz-user-select: -moz-none; + -webkit-user-select: none; + -khtml-user-select: none; +} + +div#toggle_button { + position: fixed; + bottom: 10px; + right: 10px; + background-color: rgba(60, 60, 60, 0.5); + border-radius: 12px; + color: #fff; + font-size: 0.7em; + padding: 5px 10px; +} + +div#toggle_button:hover { + background-color: #028ec9; +} +</style> + <script language="javascript"> +var isClosed = false; + +function closeHelper(display,colspan) { + // hide all close codes + var a = document.getElementsByClassName("close_hide"); + for (var i in a) { + if (a[i].style) { + a[i].style.display = display; + } + } + + // set colspans + var a = document.getElementsByClassName("close_flex"); + for (var i in a) { + a[i].colSpan = colspan; + } + + var a = document.getElementsByClassName("case_subcategory"); + for (var i in a) { + a[i].colSpan = 1 * colspan + 1; + } +} + +function toggleClose() { + if (window.isClosed == false) { + closeHelper("none",1); + window.isClosed = true; + } else { + closeHelper("table-cell",2); + window.isClosed = false; + } +} +</script> + </head> + <body> + <a href="#"><div id="toggle_button" class="unselectable" onclick="toggleClose();">Toggle Details</div></a> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <div id="master_report_header" class="block"> + <p id="intro">Summary report generated on 2012-02-04T14:47:30Z (UTC) by <a href="http://www.tavendo.de/autobahn">Autobahn WebSockets</a> v0.4.10.</p> + + <table id="case_outcome_desc"> + <tr> + <td class="case_ok">Pass</td> + <td class="outcome_desc">Test case was executed and passed successfully.</td> + </tr> + <tr> + <td class="case_non_strict">Non-Strict</td> + <td class="outcome_desc">Test case was executed and passed non-strictly. + A non-strict behavior is one that does not adhere to a SHOULD-behavior as described in the protocol specification or + a well-defined, canonical behavior that appears to be desirable but left open in the protocol specification. + An implementation with non-strict behavior is still conformant to the protocol specification.</td> + </tr> + <tr> + <td class="case_failed">Fail</td> + <td class="outcome_desc">Test case was executed and failed. An implementation which fails a test case - other + than a performance/limits related one - is non-conforming to a MUST-behavior as described in the protocol specification.</td> + </tr> + <tr> + <td class="case_info">Info</td> + <td class="outcome_desc">Informational test case which detects certain implementation behavior left unspecified by the spec + but nevertheless potentially interesting to implementors.</td> + </tr> + <tr> + <td class="case_missing">Missing</td> + <td class="outcome_desc">Test case is missing, either because it was skipped via the test suite configuration + or deactivated, i.e. because the implementation does not implement the tested feature or breaks during running + the test case.</td> + </tr> + </table> + </div> + <table id="agent_case_results"> + <tr class="case_category_row"> + <td class="case_category">1 Framing</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">1.1 Text Messages</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_1_1">Case 1.1.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_1_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_1_2">Case 1.1.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_1_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_1_3">Case 1.1.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_1_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_1_4">Case 1.1.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_1_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_1_5">Case 1.1.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_1_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_1_6">Case 1.1.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_1_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_1_7">Case 1.1.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_1_7.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_1_8">Case 1.1.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_1_8.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">1 Framing</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">1.2 Binary Messages</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_2_1">Case 1.2.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_2_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_2_2">Case 1.2.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_2_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_2_3">Case 1.2.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_2_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_2_4">Case 1.2.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_2_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_2_5">Case 1.2.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_2_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_2_6">Case 1.2.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_2_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_2_7">Case 1.2.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_2_7.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_2_8">Case 1.2.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_2_8.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">2 Pings/Pongs</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_2_1">Case 2.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_2_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_2_2">Case 2.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_2_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_2_3">Case 2.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_2_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_2_4">Case 2.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_2_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_2_5">Case 2.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_2_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_2_6">Case 2.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_2_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_2_7">Case 2.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_2_7.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_2_8">Case 2.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_2_8.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_2_9">Case 2.9</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_2_9.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_2_10">Case 2.10</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_2_10.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_2_11">Case 2.11</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_2_11.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">3 Reserved Bits</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_3_1">Case 3.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_3_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_3_2">Case 3.2</a></td> + <td class="case_non_strict"><a href="tootallnate_websocket_case_3_2.html">Non-Strict</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_3_3">Case 3.3</a></td> + <td class="case_non_strict"><a href="tootallnate_websocket_case_3_3.html">Non-Strict</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_3_4">Case 3.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_3_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_3_5">Case 3.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_3_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_3_6">Case 3.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_3_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_3_7">Case 3.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_3_7.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">4 Opcodes</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">4.1 Non-control Opcodes</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_4_1_1">Case 4.1.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_4_1_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_4_1_2">Case 4.1.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_4_1_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_4_1_3">Case 4.1.3</a></td> + <td class="case_non_strict"><a href="tootallnate_websocket_case_4_1_3.html">Non-Strict</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_4_1_4">Case 4.1.4</a></td> + <td class="case_non_strict"><a href="tootallnate_websocket_case_4_1_4.html">Non-Strict</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_4_1_5">Case 4.1.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_4_1_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">4 Opcodes</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">4.2 Control Opcodes</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_4_2_1">Case 4.2.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_4_2_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_4_2_2">Case 4.2.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_4_2_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_4_2_3">Case 4.2.3</a></td> + <td class="case_non_strict"><a href="tootallnate_websocket_case_4_2_3.html">Non-Strict</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_4_2_4">Case 4.2.4</a></td> + <td class="case_non_strict"><a href="tootallnate_websocket_case_4_2_4.html">Non-Strict</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_4_2_5">Case 4.2.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_4_2_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">5 Fragmentation</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_1">Case 5.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_2">Case 5.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_3">Case 5.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_4">Case 5.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_5">Case 5.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_6">Case 5.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_7">Case 5.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_7.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_8">Case 5.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_8.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_9">Case 5.9</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_9.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_10">Case 5.10</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_10.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_11">Case 5.11</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_11.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_12">Case 5.12</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_12.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_13">Case 5.13</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_13.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_14">Case 5.14</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_14.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_15">Case 5.15</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_15.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_16">Case 5.16</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_16.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_17">Case 5.17</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_17.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_18">Case 5.18</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_18.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_19">Case 5.19</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_19.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_20">Case 5.20</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_20.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.1 Valid UTF-8 with zero payload fragments</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_1_1">Case 6.1.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_1_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_1_2">Case 6.1.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_1_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_1_3">Case 6.1.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_1_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.2 Valid UTF-8 unfragmented, fragmented on code-points and within code-points</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_2_1">Case 6.2.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_2_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_2_2">Case 6.2.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_2_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_2_3">Case 6.2.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_2_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_2_4">Case 6.2.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_2_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.3 Invalid UTF-8 differently fragmented</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_3_1">Case 6.3.1</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_3_1.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_3_2">Case 6.3.2</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_3_2.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.4 Fail-fast on invalid UTF-8</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_4_1">Case 6.4.1</a></td> + <td class="case_non_strict"><a href="tootallnate_websocket_case_6_4_1.html">Non-Strict</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_4_2">Case 6.4.2</a></td> + <td class="case_non_strict"><a href="tootallnate_websocket_case_6_4_2.html">Non-Strict</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_4_3">Case 6.4.3</a></td> + <td class="case_non_strict"><a href="tootallnate_websocket_case_6_4_3.html">Non-Strict</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_4_4">Case 6.4.4</a></td> + <td class="case_non_strict"><a href="tootallnate_websocket_case_6_4_4.html">Non-Strict</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.5 Some valid UTF-8 sequences</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_5_1">Case 6.5.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_5_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.6 All prefixes of a valid UTF-8 string that contains multi-byte code points</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_6_1">Case 6.6.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_6_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_6_2">Case 6.6.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_6_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_6_3">Case 6.6.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_6_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_6_4">Case 6.6.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_6_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_6_5">Case 6.6.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_6_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_6_6">Case 6.6.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_6_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_6_7">Case 6.6.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_6_7.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_6_8">Case 6.6.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_6_8.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_6_9">Case 6.6.9</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_6_9.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_6_10">Case 6.6.10</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_6_10.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_6_11">Case 6.6.11</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_6_11.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.7 First possible sequence of a certain length</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_7_1">Case 6.7.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_7_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_7_2">Case 6.7.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_7_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_7_3">Case 6.7.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_7_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_7_4">Case 6.7.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_7_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.8 First possible sequence length 5/6 (invalid codepoints)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_8_1">Case 6.8.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_8_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_8_2">Case 6.8.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_8_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.9 Last possible sequence of a certain length</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_9_1">Case 6.9.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_9_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_9_2">Case 6.9.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_9_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_9_3">Case 6.9.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_9_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_9_4">Case 6.9.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_9_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.10 Last possible sequence length 4/5/6 (invalid codepoints)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_10_1">Case 6.10.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_10_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_10_2">Case 6.10.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_10_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_10_3">Case 6.10.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_10_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.11 Other boundary conditions</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_11_1">Case 6.11.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_11_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_11_2">Case 6.11.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_11_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_11_3">Case 6.11.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_11_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_11_4">Case 6.11.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_11_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_11_5">Case 6.11.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_11_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.12 Unexpected continuation bytes</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_12_1">Case 6.12.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_12_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_12_2">Case 6.12.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_12_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_12_3">Case 6.12.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_12_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_12_4">Case 6.12.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_12_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_12_5">Case 6.12.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_12_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_12_6">Case 6.12.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_12_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_12_7">Case 6.12.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_12_7.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_12_8">Case 6.12.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_12_8.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.13 Lonely start characters</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_13_1">Case 6.13.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_13_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_13_2">Case 6.13.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_13_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_13_3">Case 6.13.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_13_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_13_4">Case 6.13.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_13_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_13_5">Case 6.13.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_13_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.14 Sequences with last continuation byte missing</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_14_1">Case 6.14.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_14_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_14_2">Case 6.14.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_14_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_14_3">Case 6.14.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_14_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_14_4">Case 6.14.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_14_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_14_5">Case 6.14.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_14_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_14_6">Case 6.14.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_14_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_14_7">Case 6.14.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_14_7.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_14_8">Case 6.14.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_14_8.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_14_9">Case 6.14.9</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_14_9.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_14_10">Case 6.14.10</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_14_10.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.15 Concatenation of incomplete sequences</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_15_1">Case 6.15.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_15_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.16 Impossible bytes</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_16_1">Case 6.16.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_16_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_16_2">Case 6.16.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_16_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_16_3">Case 6.16.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_16_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.17 Examples of an overlong ASCII character</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_17_1">Case 6.17.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_17_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_17_2">Case 6.17.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_17_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_17_3">Case 6.17.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_17_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_17_4">Case 6.17.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_17_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_17_5">Case 6.17.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_17_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.18 Maximum overlong sequences</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_18_1">Case 6.18.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_18_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_18_2">Case 6.18.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_18_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_18_3">Case 6.18.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_18_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_18_4">Case 6.18.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_18_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_18_5">Case 6.18.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_18_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.19 Overlong representation of the NUL character</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_19_1">Case 6.19.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_19_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_19_2">Case 6.19.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_19_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_19_3">Case 6.19.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_19_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_19_4">Case 6.19.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_19_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_19_5">Case 6.19.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_19_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.20 Single UTF-16 surrogates</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_20_1">Case 6.20.1</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_20_1.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_20_2">Case 6.20.2</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_20_2.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_20_3">Case 6.20.3</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_20_3.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_20_4">Case 6.20.4</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_20_4.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_20_5">Case 6.20.5</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_20_5.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_20_6">Case 6.20.6</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_20_6.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_20_7">Case 6.20.7</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_20_7.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.21 Paired UTF-16 surrogates</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_21_1">Case 6.21.1</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_21_1.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_21_2">Case 6.21.2</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_21_2.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_21_3">Case 6.21.3</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_21_3.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_21_4">Case 6.21.4</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_21_4.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_21_5">Case 6.21.5</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_21_5.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_21_6">Case 6.21.6</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_21_6.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_21_7">Case 6.21.7</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_21_7.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_21_8">Case 6.21.8</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_21_8.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.22 Non-character code points (valid UTF-8)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_1">Case 6.22.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_2">Case 6.22.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_3">Case 6.22.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_4">Case 6.22.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_5">Case 6.22.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_6">Case 6.22.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_7">Case 6.22.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_7.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_8">Case 6.22.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_8.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_9">Case 6.22.9</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_9.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_10">Case 6.22.10</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_10.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_11">Case 6.22.11</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_11.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_12">Case 6.22.12</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_12.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_13">Case 6.22.13</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_13.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_14">Case 6.22.14</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_14.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_15">Case 6.22.15</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_15.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_16">Case 6.22.16</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_16.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_17">Case 6.22.17</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_17.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_18">Case 6.22.18</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_18.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_19">Case 6.22.19</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_19.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_20">Case 6.22.20</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_20.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_21">Case 6.22.21</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_21.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_22">Case 6.22.22</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_22.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_23">Case 6.22.23</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_23.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_24">Case 6.22.24</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_24.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_25">Case 6.22.25</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_25.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_26">Case 6.22.26</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_26.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_27">Case 6.22.27</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_27.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_28">Case 6.22.28</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_28.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_29">Case 6.22.29</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_29.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_30">Case 6.22.30</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_30.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_31">Case 6.22.31</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_31.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_32">Case 6.22.32</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_32.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_33">Case 6.22.33</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_33.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_34">Case 6.22.34</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_34.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.23 Unicode replacement character</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_23_1">Case 6.23.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_23_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">7 Close Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">7.1 Basic close behavior (fuzzer initiated)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_1_1">Case 7.1.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_1_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_1_2">Case 7.1.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_1_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_1_3">Case 7.1.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_1_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_1_4">Case 7.1.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_1_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_1_5">Case 7.1.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_1_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_1_6">Case 7.1.6</a></td> + <td class="case_info"><a href="tootallnate_websocket_case_7_1_6.html">Info</a></td><td class="close close_hide case_info"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">7 Close Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">7.3 Close frame structure: payload length (fuzzer initiated)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_3_1">Case 7.3.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_3_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_3_2">Case 7.3.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_3_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_3_3">Case 7.3.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_3_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_3_4">Case 7.3.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_3_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_3_5">Case 7.3.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_3_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_3_6">Case 7.3.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_3_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">7 Close Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">7.5 Close frame structure: payload value (fuzzer initiated)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_5_1">Case 7.5.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_5_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">7 Close Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">7.7 Close frame structure: valid close codes (fuzzer initiated)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_1">Case 7.7.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_2">Case 7.7.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_3">Case 7.7.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_4">Case 7.7.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_5">Case 7.7.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_6">Case 7.7.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_7">Case 7.7.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_7.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_8">Case 7.7.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_8.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_9">Case 7.7.9</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_9.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_10">Case 7.7.10</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_10.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_11">Case 7.7.11</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_11.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_12">Case 7.7.12</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_12.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_13">Case 7.7.13</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_13.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">7 Close Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">7.9 Close frame structure: invalid close codes (fuzzer initiated)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_1">Case 7.9.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_2">Case 7.9.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_3">Case 7.9.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_4">Case 7.9.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_5">Case 7.9.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_6">Case 7.9.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_7">Case 7.9.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_7.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_8">Case 7.9.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_8.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_9">Case 7.9.9</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_9.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_10">Case 7.9.10</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_10.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_11">Case 7.9.11</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_11.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_12">Case 7.9.12</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_12.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_13">Case 7.9.13</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_13.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">7 Close Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">7.13 Informational close information (fuzzer initiated)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_13_1">Case 7.13.1</a></td> + <td class="case_info"><a href="tootallnate_websocket_case_7_13_1.html">Info</a></td><td class="close close_hide case_info"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_13_2">Case 7.13.2</a></td> + <td class="case_info"><a href="tootallnate_websocket_case_7_13_2.html">Info</a></td><td class="close close_hide case_info"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">9 Limits/Performance</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">9.1 Text Message (increasing size)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_1_1">Case 9.1.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_1_1.html">Pass</a><br/><span class="case_duration">88 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_1_2">Case 9.1.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_1_2.html">Pass</a><br/><span class="case_duration">354 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_1_3">Case 9.1.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_1_3.html">Pass</a><br/><span class="case_duration">1278 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_1_4">Case 9.1.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_1_4.html">Pass</a><br/><span class="case_duration">5138 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_1_5">Case 9.1.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_1_5.html">Pass</a><br/><span class="case_duration">10252 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_1_6">Case 9.1.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_1_6.html">Pass</a><br/><span class="case_duration">20383 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">9 Limits/Performance</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">9.2 Binary Message (increasing size)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_2_1">Case 9.2.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_2_1.html">Pass</a><br/><span class="case_duration">50 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_2_2">Case 9.2.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_2_2.html">Pass</a><br/><span class="case_duration">149 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_2_3">Case 9.2.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_2_3.html">Pass</a><br/><span class="case_duration">576 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_2_4">Case 9.2.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_2_4.html">Pass</a><br/><span class="case_duration">2368 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_2_5">Case 9.2.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_2_5.html">Pass</a><br/><span class="case_duration">4974 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_2_6">Case 9.2.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_2_6.html">Pass</a><br/><span class="case_duration">9816 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">9 Limits/Performance</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">9.3 Fragmented Text Message (fixed size, increasing fragment size)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_3_1">Case 9.3.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_3_1.html">Pass</a><br/><span class="case_duration">73618 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_3_2">Case 9.3.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_3_2.html">Pass</a><br/><span class="case_duration">21904 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_3_3">Case 9.3.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_3_3.html">Pass</a><br/><span class="case_duration">9511 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_3_4">Case 9.3.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_3_4.html">Pass</a><br/><span class="case_duration">6355 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_3_5">Case 9.3.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_3_5.html">Pass</a><br/><span class="case_duration">5412 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_3_6">Case 9.3.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_3_6.html">Pass</a><br/><span class="case_duration">5167 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_3_7">Case 9.3.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_3_7.html">Pass</a><br/><span class="case_duration">5097 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_3_8">Case 9.3.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_3_8.html">Pass</a><br/><span class="case_duration">5109 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_3_9">Case 9.3.9</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_3_9.html">Pass</a><br/><span class="case_duration">5064 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">9 Limits/Performance</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">9.4 Fragmented Binary Message (fixed size, increasing fragment size)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_4_1">Case 9.4.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_4_1.html">Pass</a><br/><span class="case_duration">68983 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_4_2">Case 9.4.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_4_2.html">Pass</a><br/><span class="case_duration">19104 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_4_3">Case 9.4.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_4_3.html">Pass</a><br/><span class="case_duration">6678 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_4_4">Case 9.4.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_4_4.html">Pass</a><br/><span class="case_duration">3500 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_4_5">Case 9.4.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_4_5.html">Pass</a><br/><span class="case_duration">2588 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_4_6">Case 9.4.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_4_6.html">Pass</a><br/><span class="case_duration">2373 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_4_7">Case 9.4.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_4_7.html">Pass</a><br/><span class="case_duration">2271 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_4_8">Case 9.4.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_4_8.html">Pass</a><br/><span class="case_duration">2251 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_4_9">Case 9.4.9</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_4_9.html">Pass</a><br/><span class="case_duration">2252 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">9 Limits/Performance</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">9.5 Text Message (fixed size, increasing chop size)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_5_1">Case 9.5.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_5_1.html">Pass</a><br/><span class="case_duration">18135 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_5_2">Case 9.5.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_5_2.html">Pass</a><br/><span class="case_duration">9891 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_5_3">Case 9.5.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_5_3.html">Pass</a><br/><span class="case_duration">5464 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_5_4">Case 9.5.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_5_4.html">Pass</a><br/><span class="case_duration">3387 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_5_5">Case 9.5.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_5_5.html">Pass</a><br/><span class="case_duration">2355 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_5_6">Case 9.5.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_5_6.html">Pass</a><br/><span class="case_duration">1902 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">9 Limits/Performance</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">9.6 Binary Text Message (fixed size, increasing chop size)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_6_1">Case 9.6.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_6_1.html">Pass</a><br/><span class="case_duration">17756 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_6_2">Case 9.6.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_6_2.html">Pass</a><br/><span class="case_duration">9052 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_6_3">Case 9.6.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_6_3.html">Pass</a><br/><span class="case_duration">4858 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_6_4">Case 9.6.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_6_4.html">Pass</a><br/><span class="case_duration">2682 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_6_5">Case 9.6.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_6_5.html">Pass</a><br/><span class="case_duration">1675 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_6_6">Case 9.6.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_6_6.html">Pass</a><br/><span class="case_duration">1146 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">9 Limits/Performance</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">9.7 Text Message Roundtrip Time (fixed number, increasing size)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_7_1">Case 9.7.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_7_1.html">Pass</a><br/><span class="case_duration">258 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_7_2">Case 9.7.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_7_2.html">Pass</a><br/><span class="case_duration">273 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_7_3">Case 9.7.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_7_3.html">Pass</a><br/><span class="case_duration">348 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_7_4">Case 9.7.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_7_4.html">Pass</a><br/><span class="case_duration">595 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_7_5">Case 9.7.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_7_5.html">Pass</a><br/><span class="case_duration">1671 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_7_6">Case 9.7.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_7_6.html">Pass</a><br/><span class="case_duration">5664 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">9 Limits/Performance</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">9.8 Binary Message Roundtrip Time (fixed number, increasing size)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_8_1">Case 9.8.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_8_1.html">Pass</a><br/><span class="case_duration">247 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_8_2">Case 9.8.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_8_2.html">Pass</a><br/><span class="case_duration">262 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_8_3">Case 9.8.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_8_3.html">Pass</a><br/><span class="case_duration">302 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_8_4">Case 9.8.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_8_4.html">Pass</a><br/><span class="case_duration">396 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_8_5">Case 9.8.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_8_5.html">Pass</a><br/><span class="case_duration">957 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_8_6">Case 9.8.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_8_6.html">Pass</a><br/><span class="case_duration">2814 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">10 Autobahn Protocol Options</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">10.1 Auto-Fragmentation</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_10_1_1">Case 10.1.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_10_1_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + </table> + <br/><hr/> + <div id="test_case_descriptions"> + <br/> + <a name="case_desc_1_1_1"></a> + <h2>Case 1.1.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message with payload 0.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with empty payload). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_1_2"></a> + <h2>Case 1.1.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 125.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_1_3"></a> + <h2>Case 1.1.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 126.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_1_4"></a> + <h2>Case 1.1.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 127.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_1_5"></a> + <h2>Case 1.1.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 128.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_1_6"></a> + <h2>Case 1.1.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 65535.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_1_7"></a> + <h2>Case 1.1.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 65536.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_1_8"></a> + <h2>Case 1.1.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 65536. Sent out data in chops of 997 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_2_1"></a> + <h2>Case 1.2.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message with payload 0.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with empty payload). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_2_2"></a> + <h2>Case 1.2.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 125.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_2_3"></a> + <h2>Case 1.2.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 126.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_2_4"></a> + <h2>Case 1.2.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 127.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_2_5"></a> + <h2>Case 1.2.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 128.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_2_6"></a> + <h2>Case 1.2.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 65535.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_2_7"></a> + <h2>Case 1.2.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 65536.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_2_8"></a> + <h2>Case 1.2.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 65536. Sent out data in chops of 997 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_2_1"></a> + <h2>Case 2.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send ping without payload.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Pong (with empty payload) is sent in reply to Ping. Clean close with normal code.</p> + <br/> + <a name="case_desc_2_2"></a> + <h2>Case 2.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send ping with small text payload.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Pong with payload echo'ed is sent in reply to Ping. Clean close with normal code.</p> + <br/> + <a name="case_desc_2_3"></a> + <h2>Case 2.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send ping with small binary (non UTF-8) payload.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Pong with payload echo'ed is sent in reply to Ping. Clean close with normal code.</p> + <br/> + <a name="case_desc_2_4"></a> + <h2>Case 2.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send ping with binary payload of 125 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Pong with payload echo'ed is sent in reply to Ping. Clean close with normal code.</p> + <br/> + <a name="case_desc_2_5"></a> + <h2>Case 2.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send ping with binary payload of 126 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Connection is failed immediately (1002/Protocol Error), since control frames are only allowed to have payload up to and including 125 octets..</p> + <br/> + <a name="case_desc_2_6"></a> + <h2>Case 2.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send ping with binary payload of 125 octets, send in octet-wise chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Pong with payload echo'ed is sent in reply to Ping. Implementations must be TCP clean. Clean close with normal code.</p> + <br/> + <a name="case_desc_2_7"></a> + <h2>Case 2.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send unsolicited pong without payload. Verify nothing is received. Clean close with normal code.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Nothing.</p> + <br/> + <a name="case_desc_2_8"></a> + <h2>Case 2.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send unsolicited pong with payload. Verify nothing is received. Clean close with normal code.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Nothing.</p> + <br/> + <a name="case_desc_2_9"></a> + <h2>Case 2.9</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send unsolicited pong with payload. Send ping with payload. Verify pong for ping is received.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Nothing in reply to own Pong, but Pong with payload echo'ed in reply to Ping. Clean close with normal code.</p> + <br/> + <a name="case_desc_2_10"></a> + <h2>Case 2.10</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 10 Pings with payload.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Pongs for our Pings with all the payloads. Note: This is not required by the Spec .. but we check for this behaviour anyway. Clean close with normal code.</p> + <br/> + <a name="case_desc_2_11"></a> + <h2>Case 2.11</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 10 Pings with payload. Send out octets in octet-wise chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Pongs for our Pings with all the payloads. Note: This is not required by the Spec .. but we check for this behaviour anyway. Clean close with normal code.</p> + <br/> + <a name="case_desc_3_1"></a> + <h2>Case 3.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message with <b>RSV = 1</b>.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately (1002/protocol error), since RSV must be 0, when no extension defining RSV meaning has been negoiated.</p> + <br/> + <a name="case_desc_3_2"></a> + <h2>Case 3.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message, then send again with <b>RSV = 2</b>, then send Ping.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echo for first message is received, but then connection is failed immediately, since RSV must be 0, when no extension defining RSV meaning has been negoiated. The Pong is not received.</p> + <br/> + <a name="case_desc_3_3"></a> + <h2>Case 3.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message, then send again with <b>RSV = 3</b>, then send Ping. Octets are sent in frame-wise chops. Octets are sent in octet-wise chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echo for first message is received, but then connection is failed immediately, since RSV must be 0, when no extension defining RSV meaning has been negoiated. The Pong is not received.</p> + <br/> + <a name="case_desc_3_4"></a> + <h2>Case 3.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message, then send again with <b>RSV = 4</b>, then send Ping. Octets are sent in octet-wise chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echo for first message is received, but then connection is failed immediately, since RSV must be 0, when no extension defining RSV meaning has been negoiated. The Pong is not received.</p> + <br/> + <a name="case_desc_3_5"></a> + <h2>Case 3.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small binary message with <b>RSV = 5</b>.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since RSV must be 0.</p> + <br/> + <a name="case_desc_3_6"></a> + <h2>Case 3.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send Ping with <b>RSV = 6</b>.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since RSV must be 0.</p> + <br/> + <a name="case_desc_3_7"></a> + <h2>Case 3.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send Close with <b>RSV = 7</b>.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since RSV must be 0.</p> + <br/> + <a name="case_desc_4_1_1"></a> + <h2>Case 4.1.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send frame with reserved non-control <b>Opcode = 3</b>.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately.</p> + <br/> + <a name="case_desc_4_1_2"></a> + <h2>Case 4.1.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send frame with reserved non-control <b>Opcode = 4</b> and non-empty payload.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately.</p> + <br/> + <a name="case_desc_4_1_3"></a> + <h2>Case 4.1.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message, then send frame with reserved non-control <b>Opcode = 5</b>, then send Ping.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.</p> + <br/> + <a name="case_desc_4_1_4"></a> + <h2>Case 4.1.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message, then send frame with reserved non-control <b>Opcode = 6</b> and non-empty payload, then send Ping.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.</p> + <br/> + <a name="case_desc_4_1_5"></a> + <h2>Case 4.1.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message, then send frame with reserved non-control <b>Opcode = 7</b> and non-empty payload, then send Ping.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.</p> + <br/> + <a name="case_desc_4_2_1"></a> + <h2>Case 4.2.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send frame with reserved control <b>Opcode = 11</b>.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately.</p> + <br/> + <a name="case_desc_4_2_2"></a> + <h2>Case 4.2.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send frame with reserved control <b>Opcode = 12</b> and non-empty payload.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately.</p> + <br/> + <a name="case_desc_4_2_3"></a> + <h2>Case 4.2.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message, then send frame with reserved control <b>Opcode = 13</b>, then send Ping.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.</p> + <br/> + <a name="case_desc_4_2_4"></a> + <h2>Case 4.2.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message, then send frame with reserved control <b>Opcode = 14</b> and non-empty payload, then send Ping.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.</p> + <br/> + <a name="case_desc_4_2_5"></a> + <h2>Case 4.2.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message, then send frame with reserved control <b>Opcode = 15</b> and non-empty payload, then send Ping.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.</p> + <br/> + <a name="case_desc_5_1"></a> + <h2>Case 5.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send Ping fragmented into 2 fragments.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Connection is failed immediately, since control message MUST NOT be fragmented.</p> + <br/> + <a name="case_desc_5_2"></a> + <h2>Case 5.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send Pong fragmented into 2 fragments.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Connection is failed immediately, since control message MUST NOT be fragmented.</p> + <br/> + <a name="case_desc_5_3"></a> + <h2>Case 5.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text Message fragmented into 2 fragments.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Message is processed and echo'ed back to us.</p> + <br/> + <a name="case_desc_5_4"></a> + <h2>Case 5.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text Message fragmented into 2 fragments, octets are sent in frame-wise chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Message is processed and echo'ed back to us.</p> + <br/> + <a name="case_desc_5_5"></a> + <h2>Case 5.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text Message fragmented into 2 fragments, octets are sent in octet-wise chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Message is processed and echo'ed back to us.</p> + <br/> + <a name="case_desc_5_6"></a> + <h2>Case 5.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text Message fragmented into 2 fragments, one ping with payload in-between.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>A pong is received, then the message is echo'ed back to us.</p> + <br/> + <a name="case_desc_5_7"></a> + <h2>Case 5.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text Message fragmented into 2 fragments, one ping with payload in-between. Octets are sent in frame-wise chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>A pong is received, then the message is echo'ed back to us.</p> + <br/> + <a name="case_desc_5_8"></a> + <h2>Case 5.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text Message fragmented into 2 fragments, one ping with payload in-between. Octets are sent in octet-wise chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>A pong is received, then the message is echo'ed back to us.</p> + <br/> + <a name="case_desc_5_9"></a> + <h2>Case 5.9</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send unfragmented Text Message after Continuation Frame with FIN = true, where there is nothing to continue, sent in one chop.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since there is no message to continue.</p> + <br/> + <a name="case_desc_5_10"></a> + <h2>Case 5.10</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send unfragmented Text Message after Continuation Frame with FIN = true, where there is nothing to continue, sent in per-frame chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since there is no message to continue.</p> + <br/> + <a name="case_desc_5_11"></a> + <h2>Case 5.11</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send unfragmented Text Message after Continuation Frame with FIN = true, where there is nothing to continue, sent in octet-wise chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since there is no message to continue.</p> + <br/> + <a name="case_desc_5_12"></a> + <h2>Case 5.12</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send unfragmented Text Message after Continuation Frame with FIN = false, where there is nothing to continue, sent in one chop.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since there is no message to continue.</p> + <br/> + <a name="case_desc_5_13"></a> + <h2>Case 5.13</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send unfragmented Text Message after Continuation Frame with FIN = false, where there is nothing to continue, sent in per-frame chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since there is no message to continue.</p> + <br/> + <a name="case_desc_5_14"></a> + <h2>Case 5.14</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send unfragmented Text Message after Continuation Frame with FIN = false, where there is nothing to continue, sent in octet-wise chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since there is no message to continue.</p> + <br/> + <a name="case_desc_5_15"></a> + <h2>Case 5.15</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text Message fragmented into 2 fragments, then Continuation Frame with FIN = false where there is nothing to continue, then unfragmented Text Message, all sent in one chop.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since there is no message to continue.</p> + <br/> + <a name="case_desc_5_16"></a> + <h2>Case 5.16</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Repeated 2x: Continuation Frame with FIN = false (where there is nothing to continue), then text Message fragmented into 2 fragments.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since there is no message to continue.</p> + <br/> + <a name="case_desc_5_17"></a> + <h2>Case 5.17</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Repeated 2x: Continuation Frame with FIN = true (where there is nothing to continue), then text Message fragmented into 2 fragments.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since there is no message to continue.</p> + <br/> + <a name="case_desc_5_18"></a> + <h2>Case 5.18</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text Message fragmented into 2 fragments, with both frame opcodes set to text, sent in one chop.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since all data frames after the initial data frame must have opcode 0.</p> + <br/> + <a name="case_desc_5_19"></a> + <h2>Case 5.19</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>A fragmented text message is sent in multiple frames. After + sending the first 2 frames of the text message, a Ping is sent. Then we wait 1s, + then we send 2 more text fragments, another Ping and then the final text fragment. + Everything is legal.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The peer immediately answers the first Ping before + it has received the last text message fragment. The peer pong's back the Ping's + payload exactly, and echo's the payload of the fragmented message back to us.</p> + <br/> + <a name="case_desc_5_20"></a> + <h2>Case 5.20</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Same as Case 5.19, but send all frames with SYNC = True. + Note, this does not change the octets sent in any way, only how the stream + is chopped up on the wire.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Same as Case 5.19. Implementations must be agnostic to how + octet stream is chopped up on wire (must be TCP clean).</p> + <br/> + <a name="case_desc_6_1_1"></a> + <h2>Case 6.1.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message of length 0.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>A message is echo'ed back to us (with empty payload).</p> + <br/> + <a name="case_desc_6_1_2"></a> + <h2>Case 6.1.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented text message, 3 fragments each of length 0.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>A message is echo'ed back to us (with empty payload).</p> + <br/> + <a name="case_desc_6_1_3"></a> + <h2>Case 6.1.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented text message, 3 fragments, first and last of length 0, middle non-empty.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>A message is echo'ed back to us (with payload = payload of middle fragment).</p> + <br/> + <a name="case_desc_6_2_1"></a> + <h2>Case 6.2.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a valid UTF-8 text message in one fragment.<br><br>MESSAGE:<br>Hello-µ@ßöäüàá-UTF-8!!<br>48656c6c6f2dc2b540c39fc3b6c3a4c3bcc3a0c3a12d5554462d382121</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_2_2"></a> + <h2>Case 6.2.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a valid UTF-8 text message in two fragments, fragmented on UTF-8 code point boundary.<br><br>MESSAGE FRAGMENT 1:<br>Hello-µ@ßöä<br>48656c6c6f2dc2b540c39fc3b6c3a4<br><br>MESSAGE FRAGMENT 2:<br>üàá-UTF-8!!<br>c3bcc3a0c3a12d5554462d382121</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_2_3"></a> + <h2>Case 6.2.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a valid UTF-8 text message in fragments of 1 octet, resulting in frames ending on positions which are not code point ends.<br><br>MESSAGE:<br>Hello-µ@ßöäüàá-UTF-8!!<br>48656c6c6f2dc2b540c39fc3b6c3a4c3bcc3a0c3a12d5554462d382121</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_2_4"></a> + <h2>Case 6.2.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a valid UTF-8 text message in fragments of 1 octet, resulting in frames ending on positions which are not code point ends.<br><br>MESSAGE:<br>κόσμε<br>cebae1bdb9cf83cebcceb5</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_3_1"></a> + <h2>Case 6.3.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send invalid UTF-8 text message unfragmented.<br><br>MESSAGE:<br>κόσμε���edited<br>cebae1bdb9cf83cebcceb5eda080656469746564</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_3_2"></a> + <h2>Case 6.3.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send invalid UTF-8 text message in fragments of 1 octet, resulting in frames ending on positions which are not code point ends.<br><br>MESSAGE:<br>κόσμε���edited<br>cebae1bdb9cf83cebcceb5eda080656469746564</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_4_1"></a> + <h2>Case 6.4.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send invalid UTF-8 text message in 3 fragments (frames). +First frame payload is valid, then wait, then 2nd frame which contains the payload making the sequence invalid, then wait, then 3rd frame with rest. +Note that PART1 and PART3 are valid UTF-8 in themselves, PART2 is a 0x11000 encoded as in the UTF-8 integer encoding scheme, but the codepoint is invalid (out of range). +<br><br>MESSAGE PARTS:<br> +PART1 = κόσμε (cebae1bdb9cf83cebcceb5)<br> +PART2 = ���� (f4908080)<br> +PART3 = edited (656469746564)<br> +</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The first frame is accepted, we expect to timeout on the first wait. The 2nd frame should be rejected immediately (fail fast on UTF-8). If we timeout, we expect the connection is failed at least then, since the complete message payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_4_2"></a> + <h2>Case 6.4.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Same as Case 6.4.1, but in 2nd frame, we send only up to and including the octet making the complete payload invalid. +<br><br>MESSAGE PARTS:<br> +PART1 = κόσμε� (cebae1bdb9cf83cebcceb5f4)<br> +PART2 = � (90)<br> +PART3 = ��edited (8080656469746564)<br> +</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The first frame is accepted, we expect to timeout on the first wait. The 2nd frame should be rejected immediately (fail fast on UTF-8). If we timeout, we expect the connection is failed at least then, since the complete message payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_4_3"></a> + <h2>Case 6.4.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Same as Case 6.4.1, but we send message not in 3 frames, but in 3 chops of the same message frame. +<br><br>MESSAGE PARTS:<br> +PART1 = κόσμε (cebae1bdb9cf83cebcceb5)<br> +PART2 = ���� (f4908080)<br> +PART3 = edited (656469746564)<br> +</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The first chop is accepted, we expect to timeout on the first wait. The 2nd chop should be rejected immediately (fail fast on UTF-8). If we timeout, we expect the connection is failed at least then, since the complete message payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_4_4"></a> + <h2>Case 6.4.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Same as Case 6.4.2, but we send message not in 3 frames, but in 3 chops of the same message frame. +<br><br>MESSAGE PARTS:<br> +PART1 = κόσμε� (cebae1bdb9cf83cebcceb5f4)<br> +PART2 = � (90)<br> +PART3 = ()<br> +</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The first chop is accepted, we expect to timeout on the first wait. The 2nd chop should be rejected immediately (fail fast on UTF-8). If we timeout, we expect the connection is failed at least then, since the complete message payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_5_1"></a> + <h2>Case 6.5.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>κόσμε<br>cebae1bdb9cf83cebcceb5</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_6_1"></a> + <h2>Case 6.6.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�<br>ce</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_6_2"></a> + <h2>Case 6.6.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>κ<br>ceba</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_6_3"></a> + <h2>Case 6.6.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>κ�<br>cebae1</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_6_4"></a> + <h2>Case 6.6.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>κ�<br>cebae1bd</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_6_5"></a> + <h2>Case 6.6.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>κό<br>cebae1bdb9</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_6_6"></a> + <h2>Case 6.6.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>κό�<br>cebae1bdb9cf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_6_7"></a> + <h2>Case 6.6.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>κόσ<br>cebae1bdb9cf83</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_6_8"></a> + <h2>Case 6.6.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>κόσ�<br>cebae1bdb9cf83ce</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_6_9"></a> + <h2>Case 6.6.9</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>κόσμ<br>cebae1bdb9cf83cebc</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_6_10"></a> + <h2>Case 6.6.10</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>κόσμ�<br>cebae1bdb9cf83cebcce</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_6_11"></a> + <h2>Case 6.6.11</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>κόσμε<br>cebae1bdb9cf83cebcceb5</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_7_1"></a> + <h2>Case 6.7.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�<br>00</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_7_2"></a> + <h2>Case 6.7.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>c280</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_7_3"></a> + <h2>Case 6.7.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>ࠀ<br>e0a080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_7_4"></a> + <h2>Case 6.7.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>𐀀<br>f0908080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_8_1"></a> + <h2>Case 6.8.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�����<br>f888808080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_8_2"></a> + <h2>Case 6.8.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>fc8480808080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_9_1"></a> + <h2>Case 6.9.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>7f</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_9_2"></a> + <h2>Case 6.9.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>߿<br>dfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_9_3"></a> + <h2>Case 6.9.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>efbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_9_4"></a> + <h2>Case 6.9.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f48fbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_10_1"></a> + <h2>Case 6.10.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>����<br>f7bfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_10_2"></a> + <h2>Case 6.10.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�����<br>fbbfbfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_10_3"></a> + <h2>Case 6.10.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>fdbfbfbfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_11_1"></a> + <h2>Case 6.11.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>ed9fbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_11_2"></a> + <h2>Case 6.11.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>ee8080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_11_3"></a> + <h2>Case 6.11.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�<br>efbfbd</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_11_4"></a> + <h2>Case 6.11.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f48fbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_11_5"></a> + <h2>Case 6.11.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>����<br>f4908080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_12_1"></a> + <h2>Case 6.12.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�<br>80</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_12_2"></a> + <h2>Case 6.12.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�<br>bf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_12_3"></a> + <h2>Case 6.12.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>��<br>80bf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_12_4"></a> + <h2>Case 6.12.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>80bf80</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_12_5"></a> + <h2>Case 6.12.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>����<br>80bf80bf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_12_6"></a> + <h2>Case 6.12.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�����<br>80bf80bf80</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_12_7"></a> + <h2>Case 6.12.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>80bf80bf80bf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_12_8"></a> + <h2>Case 6.12.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���������������������������������������������������������������<br>808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_13_1"></a> + <h2>Case 6.13.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>� � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � <br>c020c120c220c320c420c520c620c720c820c920ca20cb20cc20cd20ce20cf20d020d120d220d320d420d520d620d720d820d920da20db20dc20dd20de20</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_13_2"></a> + <h2>Case 6.13.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>� � � � � � � � � � � � � � � <br>e020e120e220e320e420e520e620e720e820e920ea20eb20ec20ed20ee20</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_13_3"></a> + <h2>Case 6.13.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>� � � � � � � <br>f020f120f220f320f420f520f620</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_13_4"></a> + <h2>Case 6.13.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>� � � <br>f820f920fa20</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_13_5"></a> + <h2>Case 6.13.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>� <br>fc20</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_14_1"></a> + <h2>Case 6.14.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�<br>c0</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_14_2"></a> + <h2>Case 6.14.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>��<br>e080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_14_3"></a> + <h2>Case 6.14.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>f08080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_14_4"></a> + <h2>Case 6.14.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>����<br>f8808080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_14_5"></a> + <h2>Case 6.14.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�����<br>fc80808080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_14_6"></a> + <h2>Case 6.14.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�<br>df</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_14_7"></a> + <h2>Case 6.14.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�<br>efbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_14_8"></a> + <h2>Case 6.14.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>f7bfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_14_9"></a> + <h2>Case 6.14.9</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>����<br>fbbfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_14_10"></a> + <h2>Case 6.14.10</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�����<br>fdbfbfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_15_1"></a> + <h2>Case 6.15.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�����������������������������<br>c0e080f08080f8808080fc80808080dfefbff7bfbffbbfbfbffdbfbfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_16_1"></a> + <h2>Case 6.16.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�<br>fe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_16_2"></a> + <h2>Case 6.16.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�<br>ff</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_16_3"></a> + <h2>Case 6.16.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>����<br>fefeffff</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_17_1"></a> + <h2>Case 6.17.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>��<br>c0af</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_17_2"></a> + <h2>Case 6.17.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>e080af</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_17_3"></a> + <h2>Case 6.17.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>����<br>f08080af</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_17_4"></a> + <h2>Case 6.17.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�����<br>f8808080af</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_17_5"></a> + <h2>Case 6.17.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>fc80808080af</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_18_1"></a> + <h2>Case 6.18.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>��<br>c1bf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_18_2"></a> + <h2>Case 6.18.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>e09fbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_18_3"></a> + <h2>Case 6.18.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>����<br>f08fbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_18_4"></a> + <h2>Case 6.18.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�����<br>f887bfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_18_5"></a> + <h2>Case 6.18.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>fc83bfbfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_19_1"></a> + <h2>Case 6.19.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>��<br>c080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_19_2"></a> + <h2>Case 6.19.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>e08080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_19_3"></a> + <h2>Case 6.19.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>����<br>f0808080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_19_4"></a> + <h2>Case 6.19.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�����<br>f880808080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_19_5"></a> + <h2>Case 6.19.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>fc8080808080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_20_1"></a> + <h2>Case 6.20.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>eda080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_20_2"></a> + <h2>Case 6.20.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>edadbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_20_3"></a> + <h2>Case 6.20.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>edae80</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_20_4"></a> + <h2>Case 6.20.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>edafbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_20_5"></a> + <h2>Case 6.20.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>edb080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_20_6"></a> + <h2>Case 6.20.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>edbe80</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_20_7"></a> + <h2>Case 6.20.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>edbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_21_1"></a> + <h2>Case 6.21.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>eda080edb080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_21_2"></a> + <h2>Case 6.21.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>eda080edbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_21_3"></a> + <h2>Case 6.21.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>edadbfedb080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_21_4"></a> + <h2>Case 6.21.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>edadbfedbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_21_5"></a> + <h2>Case 6.21.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>edae80edb080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_21_6"></a> + <h2>Case 6.21.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>edae80edbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_21_7"></a> + <h2>Case 6.21.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>edafbfedb080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_21_8"></a> + <h2>Case 6.21.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>edafbfedbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_22_1"></a> + <h2>Case 6.22.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>efbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_2"></a> + <h2>Case 6.22.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>efbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_3"></a> + <h2>Case 6.22.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f09fbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_4"></a> + <h2>Case 6.22.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f09fbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_5"></a> + <h2>Case 6.22.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f0afbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_6"></a> + <h2>Case 6.22.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f0afbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_7"></a> + <h2>Case 6.22.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f0bfbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_8"></a> + <h2>Case 6.22.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f0bfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_9"></a> + <h2>Case 6.22.9</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f18fbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_10"></a> + <h2>Case 6.22.10</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f18fbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_11"></a> + <h2>Case 6.22.11</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f19fbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_12"></a> + <h2>Case 6.22.12</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f19fbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_13"></a> + <h2>Case 6.22.13</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f1afbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_14"></a> + <h2>Case 6.22.14</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f1afbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_15"></a> + <h2>Case 6.22.15</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f1bfbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_16"></a> + <h2>Case 6.22.16</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f1bfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_17"></a> + <h2>Case 6.22.17</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f28fbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_18"></a> + <h2>Case 6.22.18</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f28fbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_19"></a> + <h2>Case 6.22.19</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f29fbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_20"></a> + <h2>Case 6.22.20</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f29fbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_21"></a> + <h2>Case 6.22.21</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f2afbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_22"></a> + <h2>Case 6.22.22</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f2afbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_23"></a> + <h2>Case 6.22.23</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f2bfbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_24"></a> + <h2>Case 6.22.24</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f2bfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_25"></a> + <h2>Case 6.22.25</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f38fbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_26"></a> + <h2>Case 6.22.26</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f38fbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_27"></a> + <h2>Case 6.22.27</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f39fbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_28"></a> + <h2>Case 6.22.28</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f39fbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_29"></a> + <h2>Case 6.22.29</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f3afbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_30"></a> + <h2>Case 6.22.30</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f3afbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_31"></a> + <h2>Case 6.22.31</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f3bfbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_32"></a> + <h2>Case 6.22.32</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f3bfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_33"></a> + <h2>Case 6.22.33</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f48fbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_34"></a> + <h2>Case 6.22.34</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f48fbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_23_1"></a> + <h2>Case 6.23.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�<br>efbfbd</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_7_1_1"></a> + <h2>Case 7.1.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a message followed by a close frame</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echoed message followed by clean close with normal code.</p> + <br/> + <a name="case_desc_7_1_2"></a> + <h2>Case 7.1.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send two close frames</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal code. Second close frame ignored.</p> + <br/> + <a name="case_desc_7_1_3"></a> + <h2>Case 7.1.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a ping after close message</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal code, no pong.</p> + <br/> + <a name="case_desc_7_1_4"></a> + <h2>Case 7.1.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message after sending a close frame.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal code. Text message ignored.</p> + <br/> + <a name="case_desc_7_1_5"></a> + <h2>Case 7.1.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send message fragment1 followed by close then fragment</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal code.</p> + <br/> + <a name="case_desc_7_1_6"></a> + <h2>Case 7.1.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 256K message followed by close then a ping</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Case outcome depends on implimentation defined close behavior. Message and close frame are sent back to back. If the close frame is processed before the text message write is complete (as can happen in asyncronous processing models) the close frame is processed first and the text message may not be recieved or may only be partially recieved.</p> + <br/> + <a name="case_desc_7_3_1"></a> + <h2>Case 7.3.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a close frame with payload length 0 (no close code, no close reason)</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal code.</p> + <br/> + <a name="case_desc_7_3_2"></a> + <h2>Case 7.3.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a close frame with payload length 1</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error or drop TCP.</p> + <br/> + <a name="case_desc_7_3_3"></a> + <h2>Case 7.3.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a close frame with payload length 2 (regular close with a code)</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal code.</p> + <br/> + <a name="case_desc_7_3_4"></a> + <h2>Case 7.3.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a close frame with close code and close reason</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal code.</p> + <br/> + <a name="case_desc_7_3_5"></a> + <h2>Case 7.3.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a close frame with close code and close reason of maximum length (123)</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal code.</p> + <br/> + <a name="case_desc_7_3_6"></a> + <h2>Case 7.3.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a close frame with close code and close reason which is too long (124) - total frame payload 126 octets</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or dropped TCP connection.</p> + <br/> + <a name="case_desc_7_5_1"></a> + <h2>Case 7.5.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a close frame with invalid UTF8 payload</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error or invalid utf8 code or dropped TCP.</p> + <br/> + <a name="case_desc_7_7_1"></a> + <h2>Case 7.7.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 1000</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_2"></a> + <h2>Case 7.7.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 1001</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_3"></a> + <h2>Case 7.7.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 1002</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_4"></a> + <h2>Case 7.7.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 1003</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_5"></a> + <h2>Case 7.7.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 1007</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_6"></a> + <h2>Case 7.7.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 1008</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_7"></a> + <h2>Case 7.7.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 1009</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_8"></a> + <h2>Case 7.7.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 1010</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_9"></a> + <h2>Case 7.7.9</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 1011</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_10"></a> + <h2>Case 7.7.10</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 3000</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_11"></a> + <h2>Case 7.7.11</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 3999</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_12"></a> + <h2>Case 7.7.12</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 4000</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_13"></a> + <h2>Case 7.7.13</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 4999</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_9_1"></a> + <h2>Case 7.9.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 0</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_2"></a> + <h2>Case 7.9.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 999</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_3"></a> + <h2>Case 7.9.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 1004</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_4"></a> + <h2>Case 7.9.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 1005</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_5"></a> + <h2>Case 7.9.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 1006</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_6"></a> + <h2>Case 7.9.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 1012</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_7"></a> + <h2>Case 7.9.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 1013</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_8"></a> + <h2>Case 7.9.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 1014</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_9"></a> + <h2>Case 7.9.9</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 1015</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_10"></a> + <h2>Case 7.9.10</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 1016</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_11"></a> + <h2>Case 7.9.11</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 1100</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_12"></a> + <h2>Case 7.9.12</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 2000</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_13"></a> + <h2>Case 7.9.13</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 2999</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_13_1"></a> + <h2>Case 7.13.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with close code 5000</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Actual events are undefined by the spec.</p> + <br/> + <a name="case_desc_7_13_2"></a> + <h2>Case 7.13.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with close code 65536</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Actual events are undefined by the spec.</p> + <br/> + <a name="case_desc_9_1_1"></a> + <h2>Case 9.1.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 64 * 2**10 (64k).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_1_2"></a> + <h2>Case 9.1.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 256 * 2**10 (256k).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_1_3"></a> + <h2>Case 9.1.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 1 * 2**20 (1M).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_1_4"></a> + <h2>Case 9.1.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 4 * 2**20 (4M).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_1_5"></a> + <h2>Case 9.1.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 8 * 2**20 (8M).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_1_6"></a> + <h2>Case 9.1.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 16 * 2**20 (16M).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_2_1"></a> + <h2>Case 9.2.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 64 * 2**10 (64k).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_2_2"></a> + <h2>Case 9.2.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 256 * 2**10 (256k).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_2_3"></a> + <h2>Case 9.2.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 1 * 2**20 (1M).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_2_4"></a> + <h2>Case 9.2.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 4 * 2**20 (4M).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_2_5"></a> + <h2>Case 9.2.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 8 * 2**20 (16M).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_2_6"></a> + <h2>Case 9.2.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 16 * 2**20 (16M).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_3_1"></a> + <h2>Case 9.3.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 64.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_3_2"></a> + <h2>Case 9.3.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 256.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_3_3"></a> + <h2>Case 9.3.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 1k.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_3_4"></a> + <h2>Case 9.3.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 4k.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_3_5"></a> + <h2>Case 9.3.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 16k.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_3_6"></a> + <h2>Case 9.3.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 64k.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_3_7"></a> + <h2>Case 9.3.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 256k.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_3_8"></a> + <h2>Case 9.3.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 1M.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_3_9"></a> + <h2>Case 9.3.9</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented text message message with message payload of length 4 * 2**20 (8M). Sent out in fragments of 4M.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_4_1"></a> + <h2>Case 9.4.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 64.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_4_2"></a> + <h2>Case 9.4.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 256.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_4_3"></a> + <h2>Case 9.4.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 1k.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_4_4"></a> + <h2>Case 9.4.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 4k.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_4_5"></a> + <h2>Case 9.4.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 16k.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_4_6"></a> + <h2>Case 9.4.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 64k.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_4_7"></a> + <h2>Case 9.4.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 256k.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_4_8"></a> + <h2>Case 9.4.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 1M.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_4_9"></a> + <h2>Case 9.4.9</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 4M.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_5_1"></a> + <h2>Case 9.5.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 64 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_5_2"></a> + <h2>Case 9.5.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 128 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_5_3"></a> + <h2>Case 9.5.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 256 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_5_4"></a> + <h2>Case 9.5.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 512 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_5_5"></a> + <h2>Case 9.5.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 1024 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_5_6"></a> + <h2>Case 9.5.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 2048 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_6_1"></a> + <h2>Case 9.6.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 64 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_6_2"></a> + <h2>Case 9.6.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 128 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_6_3"></a> + <h2>Case 9.6.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 256 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_6_4"></a> + <h2>Case 9.6.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 512 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_6_5"></a> + <h2>Case 9.6.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 1024 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_6_6"></a> + <h2>Case 9.6.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 2048 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_7_1"></a> + <h2>Case 9.7.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 text messages of payload size 0 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text messages (with payload as sent). Timeout case after 60 secs.</p> + <br/> + <a name="case_desc_9_7_2"></a> + <h2>Case 9.7.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 text messages of payload size 16 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text messages (with payload as sent). Timeout case after 60 secs.</p> + <br/> + <a name="case_desc_9_7_3"></a> + <h2>Case 9.7.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 text messages of payload size 64 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text messages (with payload as sent). Timeout case after 60 secs.</p> + <br/> + <a name="case_desc_9_7_4"></a> + <h2>Case 9.7.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 text messages of payload size 256 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text messages (with payload as sent). Timeout case after 120 secs.</p> + <br/> + <a name="case_desc_9_7_5"></a> + <h2>Case 9.7.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 text messages of payload size 1024 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text messages (with payload as sent). Timeout case after 240 secs.</p> + <br/> + <a name="case_desc_9_7_6"></a> + <h2>Case 9.7.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 text messages of payload size 4096 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text messages (with payload as sent). Timeout case after 480 secs.</p> + <br/> + <a name="case_desc_9_8_1"></a> + <h2>Case 9.8.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 binary messages of payload size 0 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary messages (with payload as sent). Timeout case after 60 secs.</p> + <br/> + <a name="case_desc_9_8_2"></a> + <h2>Case 9.8.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 binary messages of payload size 16 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary messages (with payload as sent). Timeout case after 60 secs.</p> + <br/> + <a name="case_desc_9_8_3"></a> + <h2>Case 9.8.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 binary messages of payload size 64 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary messages (with payload as sent). Timeout case after 60 secs.</p> + <br/> + <a name="case_desc_9_8_4"></a> + <h2>Case 9.8.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 binary messages of payload size 256 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary messages (with payload as sent). Timeout case after 120 secs.</p> + <br/> + <a name="case_desc_9_8_5"></a> + <h2>Case 9.8.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 binary messages of payload size 1024 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary messages (with payload as sent). Timeout case after 240 secs.</p> + <br/> + <a name="case_desc_9_8_6"></a> + <h2>Case 9.8.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 binary messages of payload size 4096 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary messages (with payload as sent). Timeout case after 480 secs.</p> + <br/> + <a name="case_desc_10_1_1"></a> + <h2>Case 10.1.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message with payload of length 65536 and <b>autoFragmentSize = 1300</b>.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent and transmitted frame counts as expected). Clean close with normal code.</p> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/autobahn reports/servers/index.html b/cb-tools/java-source/autobahn reports/servers/index.html new file mode 100644 index 00000000..a5d20b2b --- /dev/null +++ b/cb-tools/java-source/autobahn reports/servers/index.html @@ -0,0 +1,3614 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +table { + border-collapse: collapse; + border-spacing: 0px; +} + +td { + margin: 0; + border: 1px solid #fff; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + font-size: 0.9em; + color: #fff; +} + +table#agent_case_results { + border-collapse: collapse; + border-spacing: 0px; + border-radius: 10px; + margin-left: 20px; + margin-right: 20px; + margin-bottom: 40px; +} + +td.outcome_desc { + width: 100%; + color: #333; + font-size: 0.8em; +} + +tr.agent_case_result_row a { + color: #eee; +} + +td.agent { + color: #fff; + font-size: 1.0em; + text-align: center; + background-color: #048; + font-size: 0.8em; + word-wrap: break-word; + padding: 4px; + width: 140px; +} + +td.case { + background-color: #666; + text-align: left; + padding-left: 40px; + font-size: 0.9em; +} + +td.case_category { + color: #fff; + background-color: #000; + text-align: left; + padding-left: 20px; + font-size: 1.0em; +} + +td.case_subcategory { + color: #fff; + background-color: #333; + text-align: left; + padding-left: 30px; + font-size: 0.9em; +} + +td.close { + width: 15px; + padding: 6px; + font-size: 0.7em; + color: #fff; + min-width: 0px; +} + +td.case_ok { + background-color: #0a0; + text-align: center; +} + +td.case_almost { + background-color: #6d6; + text-align: center; +} + +td.case_non_strict, td.case_no_close { + background-color: #9a0; + text-align: center; +} + +td.case_info { + background-color: #4095BF; + text-align: center; +} + +td.case_failed { + background-color: #900; + text-align: center; +} + +td.case_missing { + color: #fff; + background-color: #a05a2c; + text-align: center; +} + +span.case_duration { + font-size: 0.7em; + color: #fff; +} + +*.unselectable { + user-select: none; + -moz-user-select: -moz-none; + -webkit-user-select: none; + -khtml-user-select: none; +} + +div#toggle_button { + position: fixed; + bottom: 10px; + right: 10px; + background-color: rgba(60, 60, 60, 0.5); + border-radius: 12px; + color: #fff; + font-size: 0.7em; + padding: 5px 10px; +} + +div#toggle_button:hover { + background-color: #028ec9; +} +</style> + <script language="javascript"> +var isClosed = false; + +function closeHelper(display,colspan) { + // hide all close codes + var a = document.getElementsByClassName("close_hide"); + for (var i in a) { + if (a[i].style) { + a[i].style.display = display; + } + } + + // set colspans + var a = document.getElementsByClassName("close_flex"); + for (var i in a) { + a[i].colSpan = colspan; + } + + var a = document.getElementsByClassName("case_subcategory"); + for (var i in a) { + a[i].colSpan = 1 * colspan + 1; + } +} + +function toggleClose() { + if (window.isClosed == false) { + closeHelper("none",1); + window.isClosed = true; + } else { + closeHelper("table-cell",2); + window.isClosed = false; + } +} +</script> + </head> + <body> + <a href="#"><div id="toggle_button" class="unselectable" onclick="toggleClose();">Toggle Details</div></a> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <div id="master_report_header" class="block"> + <p id="intro">Summary report generated on 2012-02-04T15:47:22Z (UTC) by <a href="http://www.tavendo.de/autobahn">Autobahn WebSockets</a> v0.4.10.</p> + + <table id="case_outcome_desc"> + <tr> + <td class="case_ok">Pass</td> + <td class="outcome_desc">Test case was executed and passed successfully.</td> + </tr> + <tr> + <td class="case_non_strict">Non-Strict</td> + <td class="outcome_desc">Test case was executed and passed non-strictly. + A non-strict behavior is one that does not adhere to a SHOULD-behavior as described in the protocol specification or + a well-defined, canonical behavior that appears to be desirable but left open in the protocol specification. + An implementation with non-strict behavior is still conformant to the protocol specification.</td> + </tr> + <tr> + <td class="case_failed">Fail</td> + <td class="outcome_desc">Test case was executed and failed. An implementation which fails a test case - other + than a performance/limits related one - is non-conforming to a MUST-behavior as described in the protocol specification.</td> + </tr> + <tr> + <td class="case_info">Info</td> + <td class="outcome_desc">Informational test case which detects certain implementation behavior left unspecified by the spec + but nevertheless potentially interesting to implementors.</td> + </tr> + <tr> + <td class="case_missing">Missing</td> + <td class="outcome_desc">Test case is missing, either because it was skipped via the test suite configuration + or deactivated, i.e. because the implementation does not implement the tested feature or breaks during running + the test case.</td> + </tr> + </table> + </div> + <table id="agent_case_results"> + <tr class="case_category_row"> + <td class="case_category">1 Framing</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">1.1 Text Messages</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_1_1">Case 1.1.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_1_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_1_2">Case 1.1.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_1_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_1_3">Case 1.1.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_1_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_1_4">Case 1.1.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_1_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_1_5">Case 1.1.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_1_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_1_6">Case 1.1.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_1_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_1_7">Case 1.1.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_1_7.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_1_8">Case 1.1.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_1_8.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">1 Framing</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">1.2 Binary Messages</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_2_1">Case 1.2.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_2_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_2_2">Case 1.2.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_2_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_2_3">Case 1.2.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_2_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_2_4">Case 1.2.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_2_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_2_5">Case 1.2.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_2_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_2_6">Case 1.2.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_2_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_2_7">Case 1.2.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_2_7.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_2_8">Case 1.2.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_2_8.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">2 Pings/Pongs</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_2_1">Case 2.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_2_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_2_2">Case 2.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_2_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_2_3">Case 2.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_2_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_2_4">Case 2.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_2_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_2_5">Case 2.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_2_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_2_6">Case 2.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_2_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_2_7">Case 2.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_2_7.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_2_8">Case 2.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_2_8.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_2_9">Case 2.9</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_2_9.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_2_10">Case 2.10</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_2_10.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_2_11">Case 2.11</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_2_11.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">3 Reserved Bits</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_3_1">Case 3.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_3_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_3_2">Case 3.2</a></td> + <td class="case_non_strict"><a href="tootallnate_websocket_case_3_2.html">Non-Strict</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_3_3">Case 3.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_3_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_3_4">Case 3.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_3_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_3_5">Case 3.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_3_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_3_6">Case 3.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_3_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_3_7">Case 3.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_3_7.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">4 Opcodes</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">4.1 Non-control Opcodes</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_4_1_1">Case 4.1.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_4_1_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_4_1_2">Case 4.1.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_4_1_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_4_1_3">Case 4.1.3</a></td> + <td class="case_non_strict"><a href="tootallnate_websocket_case_4_1_3.html">Non-Strict</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_4_1_4">Case 4.1.4</a></td> + <td class="case_non_strict"><a href="tootallnate_websocket_case_4_1_4.html">Non-Strict</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_4_1_5">Case 4.1.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_4_1_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">4 Opcodes</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">4.2 Control Opcodes</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_4_2_1">Case 4.2.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_4_2_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_4_2_2">Case 4.2.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_4_2_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_4_2_3">Case 4.2.3</a></td> + <td class="case_non_strict"><a href="tootallnate_websocket_case_4_2_3.html">Non-Strict</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_4_2_4">Case 4.2.4</a></td> + <td class="case_non_strict"><a href="tootallnate_websocket_case_4_2_4.html">Non-Strict</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_4_2_5">Case 4.2.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_4_2_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">5 Fragmentation</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_1">Case 5.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_2">Case 5.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_3">Case 5.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_4">Case 5.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_5">Case 5.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_6">Case 5.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_7">Case 5.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_7.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_8">Case 5.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_8.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_9">Case 5.9</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_9.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_10">Case 5.10</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_10.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_11">Case 5.11</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_11.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_12">Case 5.12</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_12.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_13">Case 5.13</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_13.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_14">Case 5.14</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_14.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_15">Case 5.15</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_15.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_16">Case 5.16</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_16.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_17">Case 5.17</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_17.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_18">Case 5.18</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_18.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_19">Case 5.19</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_19.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_20">Case 5.20</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_20.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.1 Valid UTF-8 with zero payload fragments</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_1_1">Case 6.1.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_1_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_1_2">Case 6.1.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_1_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_1_3">Case 6.1.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_1_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.2 Valid UTF-8 unfragmented, fragmented on code-points and within code-points</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_2_1">Case 6.2.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_2_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_2_2">Case 6.2.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_2_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_2_3">Case 6.2.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_2_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_2_4">Case 6.2.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_2_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.3 Invalid UTF-8 differently fragmented</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_3_1">Case 6.3.1</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_3_1.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_3_2">Case 6.3.2</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_3_2.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.4 Fail-fast on invalid UTF-8</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_4_1">Case 6.4.1</a></td> + <td class="case_non_strict"><a href="tootallnate_websocket_case_6_4_1.html">Non-Strict</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_4_2">Case 6.4.2</a></td> + <td class="case_non_strict"><a href="tootallnate_websocket_case_6_4_2.html">Non-Strict</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_4_3">Case 6.4.3</a></td> + <td class="case_non_strict"><a href="tootallnate_websocket_case_6_4_3.html">Non-Strict</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_4_4">Case 6.4.4</a></td> + <td class="case_non_strict"><a href="tootallnate_websocket_case_6_4_4.html">Non-Strict</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.5 Some valid UTF-8 sequences</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_5_1">Case 6.5.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_5_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.6 All prefixes of a valid UTF-8 string that contains multi-byte code points</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_6_1">Case 6.6.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_6_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_6_2">Case 6.6.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_6_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_6_3">Case 6.6.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_6_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_6_4">Case 6.6.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_6_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_6_5">Case 6.6.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_6_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_6_6">Case 6.6.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_6_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_6_7">Case 6.6.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_6_7.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_6_8">Case 6.6.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_6_8.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_6_9">Case 6.6.9</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_6_9.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_6_10">Case 6.6.10</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_6_10.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_6_11">Case 6.6.11</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_6_11.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.7 First possible sequence of a certain length</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_7_1">Case 6.7.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_7_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_7_2">Case 6.7.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_7_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_7_3">Case 6.7.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_7_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_7_4">Case 6.7.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_7_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.8 First possible sequence length 5/6 (invalid codepoints)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_8_1">Case 6.8.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_8_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_8_2">Case 6.8.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_8_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.9 Last possible sequence of a certain length</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_9_1">Case 6.9.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_9_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_9_2">Case 6.9.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_9_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_9_3">Case 6.9.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_9_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_9_4">Case 6.9.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_9_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.10 Last possible sequence length 4/5/6 (invalid codepoints)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_10_1">Case 6.10.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_10_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_10_2">Case 6.10.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_10_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_10_3">Case 6.10.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_10_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.11 Other boundary conditions</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_11_1">Case 6.11.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_11_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_11_2">Case 6.11.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_11_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_11_3">Case 6.11.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_11_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_11_4">Case 6.11.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_11_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_11_5">Case 6.11.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_11_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.12 Unexpected continuation bytes</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_12_1">Case 6.12.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_12_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_12_2">Case 6.12.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_12_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_12_3">Case 6.12.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_12_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_12_4">Case 6.12.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_12_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_12_5">Case 6.12.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_12_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_12_6">Case 6.12.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_12_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_12_7">Case 6.12.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_12_7.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_12_8">Case 6.12.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_12_8.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.13 Lonely start characters</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_13_1">Case 6.13.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_13_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_13_2">Case 6.13.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_13_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_13_3">Case 6.13.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_13_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_13_4">Case 6.13.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_13_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_13_5">Case 6.13.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_13_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.14 Sequences with last continuation byte missing</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_14_1">Case 6.14.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_14_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_14_2">Case 6.14.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_14_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_14_3">Case 6.14.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_14_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_14_4">Case 6.14.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_14_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_14_5">Case 6.14.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_14_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_14_6">Case 6.14.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_14_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_14_7">Case 6.14.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_14_7.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_14_8">Case 6.14.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_14_8.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_14_9">Case 6.14.9</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_14_9.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_14_10">Case 6.14.10</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_14_10.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.15 Concatenation of incomplete sequences</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_15_1">Case 6.15.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_15_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.16 Impossible bytes</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_16_1">Case 6.16.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_16_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_16_2">Case 6.16.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_16_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_16_3">Case 6.16.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_16_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.17 Examples of an overlong ASCII character</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_17_1">Case 6.17.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_17_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_17_2">Case 6.17.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_17_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_17_3">Case 6.17.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_17_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_17_4">Case 6.17.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_17_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_17_5">Case 6.17.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_17_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.18 Maximum overlong sequences</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_18_1">Case 6.18.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_18_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_18_2">Case 6.18.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_18_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_18_3">Case 6.18.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_18_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_18_4">Case 6.18.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_18_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_18_5">Case 6.18.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_18_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.19 Overlong representation of the NUL character</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_19_1">Case 6.19.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_19_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_19_2">Case 6.19.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_19_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_19_3">Case 6.19.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_19_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_19_4">Case 6.19.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_19_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_19_5">Case 6.19.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_19_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.20 Single UTF-16 surrogates</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_20_1">Case 6.20.1</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_20_1.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_20_2">Case 6.20.2</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_20_2.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_20_3">Case 6.20.3</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_20_3.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_20_4">Case 6.20.4</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_20_4.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_20_5">Case 6.20.5</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_20_5.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_20_6">Case 6.20.6</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_20_6.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_20_7">Case 6.20.7</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_20_7.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.21 Paired UTF-16 surrogates</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_21_1">Case 6.21.1</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_21_1.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_21_2">Case 6.21.2</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_21_2.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_21_3">Case 6.21.3</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_21_3.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_21_4">Case 6.21.4</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_21_4.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_21_5">Case 6.21.5</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_21_5.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_21_6">Case 6.21.6</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_21_6.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_21_7">Case 6.21.7</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_21_7.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_21_8">Case 6.21.8</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_21_8.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.22 Non-character code points (valid UTF-8)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_1">Case 6.22.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_2">Case 6.22.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_3">Case 6.22.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_4">Case 6.22.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_5">Case 6.22.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_6">Case 6.22.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_7">Case 6.22.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_7.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_8">Case 6.22.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_8.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_9">Case 6.22.9</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_9.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_10">Case 6.22.10</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_10.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_11">Case 6.22.11</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_11.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_12">Case 6.22.12</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_12.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_13">Case 6.22.13</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_13.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_14">Case 6.22.14</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_14.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_15">Case 6.22.15</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_15.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_16">Case 6.22.16</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_16.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_17">Case 6.22.17</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_17.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_18">Case 6.22.18</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_18.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_19">Case 6.22.19</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_19.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_20">Case 6.22.20</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_20.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_21">Case 6.22.21</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_21.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_22">Case 6.22.22</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_22.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_23">Case 6.22.23</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_23.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_24">Case 6.22.24</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_24.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_25">Case 6.22.25</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_25.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_26">Case 6.22.26</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_26.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_27">Case 6.22.27</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_27.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_28">Case 6.22.28</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_28.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_29">Case 6.22.29</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_29.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_30">Case 6.22.30</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_30.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_31">Case 6.22.31</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_31.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_32">Case 6.22.32</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_32.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_33">Case 6.22.33</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_33.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_34">Case 6.22.34</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_34.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.23 Unicode replacement character</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_23_1">Case 6.23.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_23_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">7 Close Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">7.1 Basic close behavior (fuzzer initiated)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_1_1">Case 7.1.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_1_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_1_2">Case 7.1.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_1_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_1_3">Case 7.1.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_1_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_1_4">Case 7.1.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_1_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_1_5">Case 7.1.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_1_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_1_6">Case 7.1.6</a></td> + <td class="case_info"><a href="tootallnate_websocket_case_7_1_6.html">Info</a></td><td class="close close_hide case_info"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">7 Close Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">7.3 Close frame structure: payload length (fuzzer initiated)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_3_1">Case 7.3.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_3_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_3_2">Case 7.3.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_3_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_3_3">Case 7.3.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_3_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_3_4">Case 7.3.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_3_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_3_5">Case 7.3.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_3_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_3_6">Case 7.3.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_3_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">7 Close Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">7.5 Close frame structure: payload value (fuzzer initiated)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_5_1">Case 7.5.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_5_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">7 Close Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">7.7 Close frame structure: valid close codes (fuzzer initiated)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_1">Case 7.7.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_2">Case 7.7.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_3">Case 7.7.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_4">Case 7.7.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_5">Case 7.7.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_6">Case 7.7.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_7">Case 7.7.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_7.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_8">Case 7.7.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_8.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_9">Case 7.7.9</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_9.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_10">Case 7.7.10</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_10.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_11">Case 7.7.11</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_11.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_12">Case 7.7.12</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_12.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_13">Case 7.7.13</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_13.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">7 Close Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">7.9 Close frame structure: invalid close codes (fuzzer initiated)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_1">Case 7.9.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_2">Case 7.9.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_3">Case 7.9.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_4">Case 7.9.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_5">Case 7.9.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_6">Case 7.9.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_7">Case 7.9.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_7.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_8">Case 7.9.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_8.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_9">Case 7.9.9</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_9.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_10">Case 7.9.10</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_10.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_11">Case 7.9.11</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_11.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_12">Case 7.9.12</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_12.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_13">Case 7.9.13</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_13.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">7 Close Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">7.13 Informational close information (fuzzer initiated)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_13_1">Case 7.13.1</a></td> + <td class="case_info"><a href="tootallnate_websocket_case_7_13_1.html">Info</a></td><td class="close close_hide case_info"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_13_2">Case 7.13.2</a></td> + <td class="case_info"><a href="tootallnate_websocket_case_7_13_2.html">Info</a></td><td class="close close_hide case_info"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">9 Limits/Performance</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">9.1 Text Message (increasing size)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_1_1">Case 9.1.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_1_1.html">Pass</a><br/><span class="case_duration">67 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_1_2">Case 9.1.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_1_2.html">Pass</a><br/><span class="case_duration">260 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_1_3">Case 9.1.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_1_3.html">Pass</a><br/><span class="case_duration">1042 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_1_4">Case 9.1.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_1_4.html">Pass</a><br/><span class="case_duration">4180 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_1_5">Case 9.1.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_1_5.html">Pass</a><br/><span class="case_duration">8364 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_1_6">Case 9.1.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_1_6.html">Pass</a><br/><span class="case_duration">16993 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">9 Limits/Performance</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">9.2 Binary Message (increasing size)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_2_1">Case 9.2.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_2_1.html">Pass</a><br/><span class="case_duration">39 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_2_2">Case 9.2.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_2_2.html">Pass</a><br/><span class="case_duration">85 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_2_3">Case 9.2.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_2_3.html">Pass</a><br/><span class="case_duration">338 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_2_4">Case 9.2.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_2_4.html">Pass</a><br/><span class="case_duration">1375 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_2_5">Case 9.2.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_2_5.html">Pass</a><br/><span class="case_duration">2740 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_2_6">Case 9.2.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_2_6.html">Pass</a><br/><span class="case_duration">5554 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">9 Limits/Performance</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">9.3 Fragmented Text Message (fixed size, increasing fragment size)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_3_1">Case 9.3.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_3_1.html">Pass</a><br/><span class="case_duration">70578 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_3_2">Case 9.3.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_3_2.html">Pass</a><br/><span class="case_duration">20991 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_3_3">Case 9.3.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_3_3.html">Pass</a><br/><span class="case_duration">8471 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_3_4">Case 9.3.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_3_4.html">Pass</a><br/><span class="case_duration">5286 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_3_5">Case 9.3.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_3_5.html">Pass</a><br/><span class="case_duration">4403 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_3_6">Case 9.3.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_3_6.html">Pass</a><br/><span class="case_duration">4160 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_3_7">Case 9.3.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_3_7.html">Pass</a><br/><span class="case_duration">4123 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_3_8">Case 9.3.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_3_8.html">Pass</a><br/><span class="case_duration">4096 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_3_9">Case 9.3.9</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_3_9.html">Pass</a><br/><span class="case_duration">4074 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">9 Limits/Performance</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">9.4 Fragmented Binary Message (fixed size, increasing fragment size)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_4_1">Case 9.4.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_4_1.html">Pass</a><br/><span class="case_duration">69384 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_4_2">Case 9.4.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_4_2.html">Pass</a><br/><span class="case_duration">17962 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_4_3">Case 9.4.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_4_3.html">Pass</a><br/><span class="case_duration">5637 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_4_4">Case 9.4.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_4_4.html">Pass</a><br/><span class="case_duration">2497 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_4_5">Case 9.4.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_4_5.html">Pass</a><br/><span class="case_duration">1597 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_4_6">Case 9.4.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_4_6.html">Pass</a><br/><span class="case_duration">1359 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_4_7">Case 9.4.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_4_7.html">Pass</a><br/><span class="case_duration">1299 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_4_8">Case 9.4.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_4_8.html">Pass</a><br/><span class="case_duration">1296 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_4_9">Case 9.4.9</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_4_9.html">Pass</a><br/><span class="case_duration">1273 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">9 Limits/Performance</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">9.5 Text Message (fixed size, increasing chop size)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_5_1">Case 9.5.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_5_1.html">Pass</a><br/><span class="case_duration">17529 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_5_2">Case 9.5.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_5_2.html">Pass</a><br/><span class="case_duration">9293 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_5_3">Case 9.5.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_5_3.html">Pass</a><br/><span class="case_duration">5166 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_5_4">Case 9.5.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_5_4.html">Pass</a><br/><span class="case_duration">3168 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_5_5">Case 9.5.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_5_5.html">Pass</a><br/><span class="case_duration">2137 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_5_6">Case 9.5.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_5_6.html">Pass</a><br/><span class="case_duration">1571 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">9 Limits/Performance</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">9.6 Binary Text Message (fixed size, increasing chop size)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_6_1">Case 9.6.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_6_1.html">Pass</a><br/><span class="case_duration">16825 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_6_2">Case 9.6.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_6_2.html">Pass</a><br/><span class="case_duration">8639 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_6_3">Case 9.6.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_6_3.html">Pass</a><br/><span class="case_duration">4504 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_6_4">Case 9.6.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_6_4.html">Pass</a><br/><span class="case_duration">2434 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_6_5">Case 9.6.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_6_5.html">Pass</a><br/><span class="case_duration">1461 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_6_6">Case 9.6.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_6_6.html">Pass</a><br/><span class="case_duration">909 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">9 Limits/Performance</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">9.7 Text Message Roundtrip Time (fixed number, increasing size)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_7_1">Case 9.7.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_7_1.html">Pass</a><br/><span class="case_duration">267 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_7_2">Case 9.7.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_7_2.html">Pass</a><br/><span class="case_duration">289 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_7_3">Case 9.7.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_7_3.html">Pass</a><br/><span class="case_duration">321 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_7_4">Case 9.7.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_7_4.html">Pass</a><br/><span class="case_duration">551 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_7_5">Case 9.7.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_7_5.html">Pass</a><br/><span class="case_duration">1435 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_7_6">Case 9.7.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_7_6.html">Pass</a><br/><span class="case_duration">4887 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">9 Limits/Performance</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">9.8 Binary Message Roundtrip Time (fixed number, increasing size)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_8_1">Case 9.8.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_8_1.html">Pass</a><br/><span class="case_duration">227 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_8_2">Case 9.8.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_8_2.html">Pass</a><br/><span class="case_duration">259 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_8_3">Case 9.8.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_8_3.html">Pass</a><br/><span class="case_duration">255 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_8_4">Case 9.8.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_8_4.html">Pass</a><br/><span class="case_duration">355 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_8_5">Case 9.8.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_8_5.html">Pass</a><br/><span class="case_duration">636 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_8_6">Case 9.8.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_8_6.html">Pass</a><br/><span class="case_duration">1940 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">10 Autobahn Protocol Options</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">10.1 Auto-Fragmentation</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_10_1_1">Case 10.1.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_10_1_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + </table> + <br/><hr/> + <div id="test_case_descriptions"> + <br/> + <a name="case_desc_1_1_1"></a> + <h2>Case 1.1.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message with payload 0.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with empty payload). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_1_2"></a> + <h2>Case 1.1.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 125.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_1_3"></a> + <h2>Case 1.1.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 126.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_1_4"></a> + <h2>Case 1.1.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 127.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_1_5"></a> + <h2>Case 1.1.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 128.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_1_6"></a> + <h2>Case 1.1.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 65535.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_1_7"></a> + <h2>Case 1.1.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 65536.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_1_8"></a> + <h2>Case 1.1.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 65536. Sent out data in chops of 997 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_2_1"></a> + <h2>Case 1.2.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message with payload 0.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with empty payload). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_2_2"></a> + <h2>Case 1.2.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 125.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_2_3"></a> + <h2>Case 1.2.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 126.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_2_4"></a> + <h2>Case 1.2.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 127.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_2_5"></a> + <h2>Case 1.2.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 128.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_2_6"></a> + <h2>Case 1.2.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 65535.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_2_7"></a> + <h2>Case 1.2.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 65536.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_2_8"></a> + <h2>Case 1.2.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 65536. Sent out data in chops of 997 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_2_1"></a> + <h2>Case 2.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send ping without payload.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Pong (with empty payload) is sent in reply to Ping. Clean close with normal code.</p> + <br/> + <a name="case_desc_2_2"></a> + <h2>Case 2.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send ping with small text payload.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Pong with payload echo'ed is sent in reply to Ping. Clean close with normal code.</p> + <br/> + <a name="case_desc_2_3"></a> + <h2>Case 2.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send ping with small binary (non UTF-8) payload.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Pong with payload echo'ed is sent in reply to Ping. Clean close with normal code.</p> + <br/> + <a name="case_desc_2_4"></a> + <h2>Case 2.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send ping with binary payload of 125 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Pong with payload echo'ed is sent in reply to Ping. Clean close with normal code.</p> + <br/> + <a name="case_desc_2_5"></a> + <h2>Case 2.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send ping with binary payload of 126 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Connection is failed immediately (1002/Protocol Error), since control frames are only allowed to have payload up to and including 125 octets..</p> + <br/> + <a name="case_desc_2_6"></a> + <h2>Case 2.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send ping with binary payload of 125 octets, send in octet-wise chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Pong with payload echo'ed is sent in reply to Ping. Implementations must be TCP clean. Clean close with normal code.</p> + <br/> + <a name="case_desc_2_7"></a> + <h2>Case 2.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send unsolicited pong without payload. Verify nothing is received. Clean close with normal code.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Nothing.</p> + <br/> + <a name="case_desc_2_8"></a> + <h2>Case 2.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send unsolicited pong with payload. Verify nothing is received. Clean close with normal code.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Nothing.</p> + <br/> + <a name="case_desc_2_9"></a> + <h2>Case 2.9</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send unsolicited pong with payload. Send ping with payload. Verify pong for ping is received.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Nothing in reply to own Pong, but Pong with payload echo'ed in reply to Ping. Clean close with normal code.</p> + <br/> + <a name="case_desc_2_10"></a> + <h2>Case 2.10</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 10 Pings with payload.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Pongs for our Pings with all the payloads. Note: This is not required by the Spec .. but we check for this behaviour anyway. Clean close with normal code.</p> + <br/> + <a name="case_desc_2_11"></a> + <h2>Case 2.11</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 10 Pings with payload. Send out octets in octet-wise chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Pongs for our Pings with all the payloads. Note: This is not required by the Spec .. but we check for this behaviour anyway. Clean close with normal code.</p> + <br/> + <a name="case_desc_3_1"></a> + <h2>Case 3.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message with <b>RSV = 1</b>.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately (1002/protocol error), since RSV must be 0, when no extension defining RSV meaning has been negoiated.</p> + <br/> + <a name="case_desc_3_2"></a> + <h2>Case 3.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message, then send again with <b>RSV = 2</b>, then send Ping.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echo for first message is received, but then connection is failed immediately, since RSV must be 0, when no extension defining RSV meaning has been negoiated. The Pong is not received.</p> + <br/> + <a name="case_desc_3_3"></a> + <h2>Case 3.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message, then send again with <b>RSV = 3</b>, then send Ping. Octets are sent in frame-wise chops. Octets are sent in octet-wise chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echo for first message is received, but then connection is failed immediately, since RSV must be 0, when no extension defining RSV meaning has been negoiated. The Pong is not received.</p> + <br/> + <a name="case_desc_3_4"></a> + <h2>Case 3.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message, then send again with <b>RSV = 4</b>, then send Ping. Octets are sent in octet-wise chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echo for first message is received, but then connection is failed immediately, since RSV must be 0, when no extension defining RSV meaning has been negoiated. The Pong is not received.</p> + <br/> + <a name="case_desc_3_5"></a> + <h2>Case 3.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small binary message with <b>RSV = 5</b>.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since RSV must be 0.</p> + <br/> + <a name="case_desc_3_6"></a> + <h2>Case 3.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send Ping with <b>RSV = 6</b>.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since RSV must be 0.</p> + <br/> + <a name="case_desc_3_7"></a> + <h2>Case 3.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send Close with <b>RSV = 7</b>.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since RSV must be 0.</p> + <br/> + <a name="case_desc_4_1_1"></a> + <h2>Case 4.1.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send frame with reserved non-control <b>Opcode = 3</b>.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately.</p> + <br/> + <a name="case_desc_4_1_2"></a> + <h2>Case 4.1.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send frame with reserved non-control <b>Opcode = 4</b> and non-empty payload.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately.</p> + <br/> + <a name="case_desc_4_1_3"></a> + <h2>Case 4.1.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message, then send frame with reserved non-control <b>Opcode = 5</b>, then send Ping.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.</p> + <br/> + <a name="case_desc_4_1_4"></a> + <h2>Case 4.1.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message, then send frame with reserved non-control <b>Opcode = 6</b> and non-empty payload, then send Ping.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.</p> + <br/> + <a name="case_desc_4_1_5"></a> + <h2>Case 4.1.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message, then send frame with reserved non-control <b>Opcode = 7</b> and non-empty payload, then send Ping.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.</p> + <br/> + <a name="case_desc_4_2_1"></a> + <h2>Case 4.2.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send frame with reserved control <b>Opcode = 11</b>.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately.</p> + <br/> + <a name="case_desc_4_2_2"></a> + <h2>Case 4.2.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send frame with reserved control <b>Opcode = 12</b> and non-empty payload.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately.</p> + <br/> + <a name="case_desc_4_2_3"></a> + <h2>Case 4.2.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message, then send frame with reserved control <b>Opcode = 13</b>, then send Ping.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.</p> + <br/> + <a name="case_desc_4_2_4"></a> + <h2>Case 4.2.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message, then send frame with reserved control <b>Opcode = 14</b> and non-empty payload, then send Ping.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.</p> + <br/> + <a name="case_desc_4_2_5"></a> + <h2>Case 4.2.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message, then send frame with reserved control <b>Opcode = 15</b> and non-empty payload, then send Ping.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.</p> + <br/> + <a name="case_desc_5_1"></a> + <h2>Case 5.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send Ping fragmented into 2 fragments.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Connection is failed immediately, since control message MUST NOT be fragmented.</p> + <br/> + <a name="case_desc_5_2"></a> + <h2>Case 5.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send Pong fragmented into 2 fragments.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Connection is failed immediately, since control message MUST NOT be fragmented.</p> + <br/> + <a name="case_desc_5_3"></a> + <h2>Case 5.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text Message fragmented into 2 fragments.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Message is processed and echo'ed back to us.</p> + <br/> + <a name="case_desc_5_4"></a> + <h2>Case 5.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text Message fragmented into 2 fragments, octets are sent in frame-wise chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Message is processed and echo'ed back to us.</p> + <br/> + <a name="case_desc_5_5"></a> + <h2>Case 5.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text Message fragmented into 2 fragments, octets are sent in octet-wise chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Message is processed and echo'ed back to us.</p> + <br/> + <a name="case_desc_5_6"></a> + <h2>Case 5.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text Message fragmented into 2 fragments, one ping with payload in-between.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>A pong is received, then the message is echo'ed back to us.</p> + <br/> + <a name="case_desc_5_7"></a> + <h2>Case 5.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text Message fragmented into 2 fragments, one ping with payload in-between. Octets are sent in frame-wise chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>A pong is received, then the message is echo'ed back to us.</p> + <br/> + <a name="case_desc_5_8"></a> + <h2>Case 5.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text Message fragmented into 2 fragments, one ping with payload in-between. Octets are sent in octet-wise chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>A pong is received, then the message is echo'ed back to us.</p> + <br/> + <a name="case_desc_5_9"></a> + <h2>Case 5.9</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send unfragmented Text Message after Continuation Frame with FIN = true, where there is nothing to continue, sent in one chop.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since there is no message to continue.</p> + <br/> + <a name="case_desc_5_10"></a> + <h2>Case 5.10</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send unfragmented Text Message after Continuation Frame with FIN = true, where there is nothing to continue, sent in per-frame chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since there is no message to continue.</p> + <br/> + <a name="case_desc_5_11"></a> + <h2>Case 5.11</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send unfragmented Text Message after Continuation Frame with FIN = true, where there is nothing to continue, sent in octet-wise chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since there is no message to continue.</p> + <br/> + <a name="case_desc_5_12"></a> + <h2>Case 5.12</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send unfragmented Text Message after Continuation Frame with FIN = false, where there is nothing to continue, sent in one chop.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since there is no message to continue.</p> + <br/> + <a name="case_desc_5_13"></a> + <h2>Case 5.13</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send unfragmented Text Message after Continuation Frame with FIN = false, where there is nothing to continue, sent in per-frame chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since there is no message to continue.</p> + <br/> + <a name="case_desc_5_14"></a> + <h2>Case 5.14</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send unfragmented Text Message after Continuation Frame with FIN = false, where there is nothing to continue, sent in octet-wise chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since there is no message to continue.</p> + <br/> + <a name="case_desc_5_15"></a> + <h2>Case 5.15</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text Message fragmented into 2 fragments, then Continuation Frame with FIN = false where there is nothing to continue, then unfragmented Text Message, all sent in one chop.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since there is no message to continue.</p> + <br/> + <a name="case_desc_5_16"></a> + <h2>Case 5.16</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Repeated 2x: Continuation Frame with FIN = false (where there is nothing to continue), then text Message fragmented into 2 fragments.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since there is no message to continue.</p> + <br/> + <a name="case_desc_5_17"></a> + <h2>Case 5.17</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Repeated 2x: Continuation Frame with FIN = true (where there is nothing to continue), then text Message fragmented into 2 fragments.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since there is no message to continue.</p> + <br/> + <a name="case_desc_5_18"></a> + <h2>Case 5.18</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text Message fragmented into 2 fragments, with both frame opcodes set to text, sent in one chop.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since all data frames after the initial data frame must have opcode 0.</p> + <br/> + <a name="case_desc_5_19"></a> + <h2>Case 5.19</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>A fragmented text message is sent in multiple frames. After + sending the first 2 frames of the text message, a Ping is sent. Then we wait 1s, + then we send 2 more text fragments, another Ping and then the final text fragment. + Everything is legal.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The peer immediately answers the first Ping before + it has received the last text message fragment. The peer pong's back the Ping's + payload exactly, and echo's the payload of the fragmented message back to us.</p> + <br/> + <a name="case_desc_5_20"></a> + <h2>Case 5.20</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Same as Case 5.19, but send all frames with SYNC = True. + Note, this does not change the octets sent in any way, only how the stream + is chopped up on the wire.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Same as Case 5.19. Implementations must be agnostic to how + octet stream is chopped up on wire (must be TCP clean).</p> + <br/> + <a name="case_desc_6_1_1"></a> + <h2>Case 6.1.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message of length 0.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>A message is echo'ed back to us (with empty payload).</p> + <br/> + <a name="case_desc_6_1_2"></a> + <h2>Case 6.1.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented text message, 3 fragments each of length 0.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>A message is echo'ed back to us (with empty payload).</p> + <br/> + <a name="case_desc_6_1_3"></a> + <h2>Case 6.1.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented text message, 3 fragments, first and last of length 0, middle non-empty.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>A message is echo'ed back to us (with payload = payload of middle fragment).</p> + <br/> + <a name="case_desc_6_2_1"></a> + <h2>Case 6.2.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a valid UTF-8 text message in one fragment.<br><br>MESSAGE:<br>Hello-µ@ßöäüàá-UTF-8!!<br>48656c6c6f2dc2b540c39fc3b6c3a4c3bcc3a0c3a12d5554462d382121</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_2_2"></a> + <h2>Case 6.2.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a valid UTF-8 text message in two fragments, fragmented on UTF-8 code point boundary.<br><br>MESSAGE FRAGMENT 1:<br>Hello-µ@ßöä<br>48656c6c6f2dc2b540c39fc3b6c3a4<br><br>MESSAGE FRAGMENT 2:<br>üàá-UTF-8!!<br>c3bcc3a0c3a12d5554462d382121</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_2_3"></a> + <h2>Case 6.2.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a valid UTF-8 text message in fragments of 1 octet, resulting in frames ending on positions which are not code point ends.<br><br>MESSAGE:<br>Hello-µ@ßöäüàá-UTF-8!!<br>48656c6c6f2dc2b540c39fc3b6c3a4c3bcc3a0c3a12d5554462d382121</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_2_4"></a> + <h2>Case 6.2.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a valid UTF-8 text message in fragments of 1 octet, resulting in frames ending on positions which are not code point ends.<br><br>MESSAGE:<br>κόσμε<br>cebae1bdb9cf83cebcceb5</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_3_1"></a> + <h2>Case 6.3.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send invalid UTF-8 text message unfragmented.<br><br>MESSAGE:<br>κόσμε���edited<br>cebae1bdb9cf83cebcceb5eda080656469746564</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_3_2"></a> + <h2>Case 6.3.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send invalid UTF-8 text message in fragments of 1 octet, resulting in frames ending on positions which are not code point ends.<br><br>MESSAGE:<br>κόσμε���edited<br>cebae1bdb9cf83cebcceb5eda080656469746564</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_4_1"></a> + <h2>Case 6.4.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send invalid UTF-8 text message in 3 fragments (frames). +First frame payload is valid, then wait, then 2nd frame which contains the payload making the sequence invalid, then wait, then 3rd frame with rest. +Note that PART1 and PART3 are valid UTF-8 in themselves, PART2 is a 0x11000 encoded as in the UTF-8 integer encoding scheme, but the codepoint is invalid (out of range). +<br><br>MESSAGE PARTS:<br> +PART1 = κόσμε (cebae1bdb9cf83cebcceb5)<br> +PART2 = ���� (f4908080)<br> +PART3 = edited (656469746564)<br> +</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The first frame is accepted, we expect to timeout on the first wait. The 2nd frame should be rejected immediately (fail fast on UTF-8). If we timeout, we expect the connection is failed at least then, since the complete message payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_4_2"></a> + <h2>Case 6.4.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Same as Case 6.4.1, but in 2nd frame, we send only up to and including the octet making the complete payload invalid. +<br><br>MESSAGE PARTS:<br> +PART1 = κόσμε� (cebae1bdb9cf83cebcceb5f4)<br> +PART2 = � (90)<br> +PART3 = ��edited (8080656469746564)<br> +</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The first frame is accepted, we expect to timeout on the first wait. The 2nd frame should be rejected immediately (fail fast on UTF-8). If we timeout, we expect the connection is failed at least then, since the complete message payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_4_3"></a> + <h2>Case 6.4.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Same as Case 6.4.1, but we send message not in 3 frames, but in 3 chops of the same message frame. +<br><br>MESSAGE PARTS:<br> +PART1 = κόσμε (cebae1bdb9cf83cebcceb5)<br> +PART2 = ���� (f4908080)<br> +PART3 = edited (656469746564)<br> +</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The first chop is accepted, we expect to timeout on the first wait. The 2nd chop should be rejected immediately (fail fast on UTF-8). If we timeout, we expect the connection is failed at least then, since the complete message payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_4_4"></a> + <h2>Case 6.4.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Same as Case 6.4.2, but we send message not in 3 frames, but in 3 chops of the same message frame. +<br><br>MESSAGE PARTS:<br> +PART1 = κόσμε� (cebae1bdb9cf83cebcceb5f4)<br> +PART2 = � (90)<br> +PART3 = ()<br> +</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The first chop is accepted, we expect to timeout on the first wait. The 2nd chop should be rejected immediately (fail fast on UTF-8). If we timeout, we expect the connection is failed at least then, since the complete message payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_5_1"></a> + <h2>Case 6.5.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>κόσμε<br>cebae1bdb9cf83cebcceb5</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_6_1"></a> + <h2>Case 6.6.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�<br>ce</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_6_2"></a> + <h2>Case 6.6.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>κ<br>ceba</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_6_3"></a> + <h2>Case 6.6.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>κ�<br>cebae1</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_6_4"></a> + <h2>Case 6.6.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>κ�<br>cebae1bd</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_6_5"></a> + <h2>Case 6.6.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>κό<br>cebae1bdb9</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_6_6"></a> + <h2>Case 6.6.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>κό�<br>cebae1bdb9cf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_6_7"></a> + <h2>Case 6.6.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>κόσ<br>cebae1bdb9cf83</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_6_8"></a> + <h2>Case 6.6.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>κόσ�<br>cebae1bdb9cf83ce</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_6_9"></a> + <h2>Case 6.6.9</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>κόσμ<br>cebae1bdb9cf83cebc</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_6_10"></a> + <h2>Case 6.6.10</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>κόσμ�<br>cebae1bdb9cf83cebcce</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_6_11"></a> + <h2>Case 6.6.11</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>κόσμε<br>cebae1bdb9cf83cebcceb5</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_7_1"></a> + <h2>Case 6.7.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�<br>00</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_7_2"></a> + <h2>Case 6.7.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>c280</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_7_3"></a> + <h2>Case 6.7.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>ࠀ<br>e0a080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_7_4"></a> + <h2>Case 6.7.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>𐀀<br>f0908080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_8_1"></a> + <h2>Case 6.8.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�����<br>f888808080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_8_2"></a> + <h2>Case 6.8.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>fc8480808080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_9_1"></a> + <h2>Case 6.9.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>7f</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_9_2"></a> + <h2>Case 6.9.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>߿<br>dfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_9_3"></a> + <h2>Case 6.9.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>efbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_9_4"></a> + <h2>Case 6.9.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f48fbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_10_1"></a> + <h2>Case 6.10.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>����<br>f7bfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_10_2"></a> + <h2>Case 6.10.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�����<br>fbbfbfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_10_3"></a> + <h2>Case 6.10.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>fdbfbfbfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_11_1"></a> + <h2>Case 6.11.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>ed9fbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_11_2"></a> + <h2>Case 6.11.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>ee8080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_11_3"></a> + <h2>Case 6.11.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�<br>efbfbd</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_11_4"></a> + <h2>Case 6.11.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f48fbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_11_5"></a> + <h2>Case 6.11.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>����<br>f4908080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_12_1"></a> + <h2>Case 6.12.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�<br>80</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_12_2"></a> + <h2>Case 6.12.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�<br>bf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_12_3"></a> + <h2>Case 6.12.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>��<br>80bf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_12_4"></a> + <h2>Case 6.12.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>80bf80</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_12_5"></a> + <h2>Case 6.12.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>����<br>80bf80bf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_12_6"></a> + <h2>Case 6.12.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�����<br>80bf80bf80</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_12_7"></a> + <h2>Case 6.12.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>80bf80bf80bf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_12_8"></a> + <h2>Case 6.12.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���������������������������������������������������������������<br>808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_13_1"></a> + <h2>Case 6.13.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>� � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � <br>c020c120c220c320c420c520c620c720c820c920ca20cb20cc20cd20ce20cf20d020d120d220d320d420d520d620d720d820d920da20db20dc20dd20de20</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_13_2"></a> + <h2>Case 6.13.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>� � � � � � � � � � � � � � � <br>e020e120e220e320e420e520e620e720e820e920ea20eb20ec20ed20ee20</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_13_3"></a> + <h2>Case 6.13.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>� � � � � � � <br>f020f120f220f320f420f520f620</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_13_4"></a> + <h2>Case 6.13.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>� � � <br>f820f920fa20</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_13_5"></a> + <h2>Case 6.13.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>� <br>fc20</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_14_1"></a> + <h2>Case 6.14.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�<br>c0</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_14_2"></a> + <h2>Case 6.14.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>��<br>e080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_14_3"></a> + <h2>Case 6.14.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>f08080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_14_4"></a> + <h2>Case 6.14.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>����<br>f8808080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_14_5"></a> + <h2>Case 6.14.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�����<br>fc80808080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_14_6"></a> + <h2>Case 6.14.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�<br>df</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_14_7"></a> + <h2>Case 6.14.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�<br>efbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_14_8"></a> + <h2>Case 6.14.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>f7bfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_14_9"></a> + <h2>Case 6.14.9</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>����<br>fbbfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_14_10"></a> + <h2>Case 6.14.10</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�����<br>fdbfbfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_15_1"></a> + <h2>Case 6.15.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�����������������������������<br>c0e080f08080f8808080fc80808080dfefbff7bfbffbbfbfbffdbfbfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_16_1"></a> + <h2>Case 6.16.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�<br>fe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_16_2"></a> + <h2>Case 6.16.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�<br>ff</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_16_3"></a> + <h2>Case 6.16.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>����<br>fefeffff</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_17_1"></a> + <h2>Case 6.17.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>��<br>c0af</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_17_2"></a> + <h2>Case 6.17.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>e080af</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_17_3"></a> + <h2>Case 6.17.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>����<br>f08080af</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_17_4"></a> + <h2>Case 6.17.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�����<br>f8808080af</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_17_5"></a> + <h2>Case 6.17.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>fc80808080af</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_18_1"></a> + <h2>Case 6.18.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>��<br>c1bf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_18_2"></a> + <h2>Case 6.18.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>e09fbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_18_3"></a> + <h2>Case 6.18.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>����<br>f08fbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_18_4"></a> + <h2>Case 6.18.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�����<br>f887bfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_18_5"></a> + <h2>Case 6.18.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>fc83bfbfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_19_1"></a> + <h2>Case 6.19.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>��<br>c080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_19_2"></a> + <h2>Case 6.19.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>e08080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_19_3"></a> + <h2>Case 6.19.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>����<br>f0808080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_19_4"></a> + <h2>Case 6.19.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�����<br>f880808080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_19_5"></a> + <h2>Case 6.19.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>fc8080808080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_20_1"></a> + <h2>Case 6.20.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>eda080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_20_2"></a> + <h2>Case 6.20.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>edadbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_20_3"></a> + <h2>Case 6.20.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>edae80</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_20_4"></a> + <h2>Case 6.20.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>edafbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_20_5"></a> + <h2>Case 6.20.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>edb080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_20_6"></a> + <h2>Case 6.20.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>edbe80</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_20_7"></a> + <h2>Case 6.20.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>edbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_21_1"></a> + <h2>Case 6.21.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>eda080edb080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_21_2"></a> + <h2>Case 6.21.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>eda080edbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_21_3"></a> + <h2>Case 6.21.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>edadbfedb080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_21_4"></a> + <h2>Case 6.21.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>edadbfedbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_21_5"></a> + <h2>Case 6.21.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>edae80edb080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_21_6"></a> + <h2>Case 6.21.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>edae80edbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_21_7"></a> + <h2>Case 6.21.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>edafbfedb080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_21_8"></a> + <h2>Case 6.21.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>edafbfedbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_22_1"></a> + <h2>Case 6.22.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>efbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_2"></a> + <h2>Case 6.22.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>efbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_3"></a> + <h2>Case 6.22.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f09fbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_4"></a> + <h2>Case 6.22.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f09fbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_5"></a> + <h2>Case 6.22.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f0afbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_6"></a> + <h2>Case 6.22.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f0afbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_7"></a> + <h2>Case 6.22.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f0bfbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_8"></a> + <h2>Case 6.22.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f0bfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_9"></a> + <h2>Case 6.22.9</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f18fbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_10"></a> + <h2>Case 6.22.10</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f18fbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_11"></a> + <h2>Case 6.22.11</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f19fbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_12"></a> + <h2>Case 6.22.12</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f19fbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_13"></a> + <h2>Case 6.22.13</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f1afbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_14"></a> + <h2>Case 6.22.14</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f1afbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_15"></a> + <h2>Case 6.22.15</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f1bfbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_16"></a> + <h2>Case 6.22.16</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f1bfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_17"></a> + <h2>Case 6.22.17</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f28fbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_18"></a> + <h2>Case 6.22.18</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f28fbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_19"></a> + <h2>Case 6.22.19</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f29fbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_20"></a> + <h2>Case 6.22.20</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f29fbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_21"></a> + <h2>Case 6.22.21</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f2afbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_22"></a> + <h2>Case 6.22.22</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f2afbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_23"></a> + <h2>Case 6.22.23</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f2bfbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_24"></a> + <h2>Case 6.22.24</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f2bfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_25"></a> + <h2>Case 6.22.25</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f38fbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_26"></a> + <h2>Case 6.22.26</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f38fbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_27"></a> + <h2>Case 6.22.27</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f39fbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_28"></a> + <h2>Case 6.22.28</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f39fbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_29"></a> + <h2>Case 6.22.29</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f3afbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_30"></a> + <h2>Case 6.22.30</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f3afbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_31"></a> + <h2>Case 6.22.31</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f3bfbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_32"></a> + <h2>Case 6.22.32</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f3bfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_33"></a> + <h2>Case 6.22.33</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f48fbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_34"></a> + <h2>Case 6.22.34</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f48fbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_23_1"></a> + <h2>Case 6.23.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�<br>efbfbd</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_7_1_1"></a> + <h2>Case 7.1.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a message followed by a close frame</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echoed message followed by clean close with normal code.</p> + <br/> + <a name="case_desc_7_1_2"></a> + <h2>Case 7.1.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send two close frames</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal code. Second close frame ignored.</p> + <br/> + <a name="case_desc_7_1_3"></a> + <h2>Case 7.1.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a ping after close message</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal code, no pong.</p> + <br/> + <a name="case_desc_7_1_4"></a> + <h2>Case 7.1.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message after sending a close frame.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal code. Text message ignored.</p> + <br/> + <a name="case_desc_7_1_5"></a> + <h2>Case 7.1.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send message fragment1 followed by close then fragment</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal code.</p> + <br/> + <a name="case_desc_7_1_6"></a> + <h2>Case 7.1.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 256K message followed by close then a ping</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Case outcome depends on implimentation defined close behavior. Message and close frame are sent back to back. If the close frame is processed before the text message write is complete (as can happen in asyncronous processing models) the close frame is processed first and the text message may not be recieved or may only be partially recieved.</p> + <br/> + <a name="case_desc_7_3_1"></a> + <h2>Case 7.3.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a close frame with payload length 0 (no close code, no close reason)</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal code.</p> + <br/> + <a name="case_desc_7_3_2"></a> + <h2>Case 7.3.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a close frame with payload length 1</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error or drop TCP.</p> + <br/> + <a name="case_desc_7_3_3"></a> + <h2>Case 7.3.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a close frame with payload length 2 (regular close with a code)</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal code.</p> + <br/> + <a name="case_desc_7_3_4"></a> + <h2>Case 7.3.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a close frame with close code and close reason</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal code.</p> + <br/> + <a name="case_desc_7_3_5"></a> + <h2>Case 7.3.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a close frame with close code and close reason of maximum length (123)</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal code.</p> + <br/> + <a name="case_desc_7_3_6"></a> + <h2>Case 7.3.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a close frame with close code and close reason which is too long (124) - total frame payload 126 octets</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or dropped TCP connection.</p> + <br/> + <a name="case_desc_7_5_1"></a> + <h2>Case 7.5.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a close frame with invalid UTF8 payload</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error or invalid utf8 code or dropped TCP.</p> + <br/> + <a name="case_desc_7_7_1"></a> + <h2>Case 7.7.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 1000</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_2"></a> + <h2>Case 7.7.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 1001</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_3"></a> + <h2>Case 7.7.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 1002</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_4"></a> + <h2>Case 7.7.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 1003</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_5"></a> + <h2>Case 7.7.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 1007</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_6"></a> + <h2>Case 7.7.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 1008</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_7"></a> + <h2>Case 7.7.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 1009</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_8"></a> + <h2>Case 7.7.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 1010</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_9"></a> + <h2>Case 7.7.9</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 1011</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_10"></a> + <h2>Case 7.7.10</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 3000</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_11"></a> + <h2>Case 7.7.11</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 3999</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_12"></a> + <h2>Case 7.7.12</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 4000</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_13"></a> + <h2>Case 7.7.13</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 4999</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_9_1"></a> + <h2>Case 7.9.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 0</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_2"></a> + <h2>Case 7.9.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 999</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_3"></a> + <h2>Case 7.9.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 1004</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_4"></a> + <h2>Case 7.9.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 1005</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_5"></a> + <h2>Case 7.9.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 1006</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_6"></a> + <h2>Case 7.9.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 1012</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_7"></a> + <h2>Case 7.9.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 1013</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_8"></a> + <h2>Case 7.9.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 1014</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_9"></a> + <h2>Case 7.9.9</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 1015</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_10"></a> + <h2>Case 7.9.10</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 1016</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_11"></a> + <h2>Case 7.9.11</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 1100</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_12"></a> + <h2>Case 7.9.12</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 2000</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_13"></a> + <h2>Case 7.9.13</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 2999</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_13_1"></a> + <h2>Case 7.13.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with close code 5000</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Actual events are undefined by the spec.</p> + <br/> + <a name="case_desc_7_13_2"></a> + <h2>Case 7.13.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with close code 65536</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Actual events are undefined by the spec.</p> + <br/> + <a name="case_desc_9_1_1"></a> + <h2>Case 9.1.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 64 * 2**10 (64k).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_1_2"></a> + <h2>Case 9.1.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 256 * 2**10 (256k).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_1_3"></a> + <h2>Case 9.1.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 1 * 2**20 (1M).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_1_4"></a> + <h2>Case 9.1.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 4 * 2**20 (4M).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_1_5"></a> + <h2>Case 9.1.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 8 * 2**20 (8M).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_1_6"></a> + <h2>Case 9.1.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 16 * 2**20 (16M).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_2_1"></a> + <h2>Case 9.2.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 64 * 2**10 (64k).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_2_2"></a> + <h2>Case 9.2.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 256 * 2**10 (256k).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_2_3"></a> + <h2>Case 9.2.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 1 * 2**20 (1M).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_2_4"></a> + <h2>Case 9.2.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 4 * 2**20 (4M).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_2_5"></a> + <h2>Case 9.2.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 8 * 2**20 (16M).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_2_6"></a> + <h2>Case 9.2.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 16 * 2**20 (16M).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_3_1"></a> + <h2>Case 9.3.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 64.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_3_2"></a> + <h2>Case 9.3.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 256.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_3_3"></a> + <h2>Case 9.3.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 1k.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_3_4"></a> + <h2>Case 9.3.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 4k.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_3_5"></a> + <h2>Case 9.3.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 16k.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_3_6"></a> + <h2>Case 9.3.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 64k.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_3_7"></a> + <h2>Case 9.3.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 256k.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_3_8"></a> + <h2>Case 9.3.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 1M.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_3_9"></a> + <h2>Case 9.3.9</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented text message message with message payload of length 4 * 2**20 (8M). Sent out in fragments of 4M.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_4_1"></a> + <h2>Case 9.4.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 64.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_4_2"></a> + <h2>Case 9.4.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 256.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_4_3"></a> + <h2>Case 9.4.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 1k.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_4_4"></a> + <h2>Case 9.4.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 4k.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_4_5"></a> + <h2>Case 9.4.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 16k.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_4_6"></a> + <h2>Case 9.4.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 64k.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_4_7"></a> + <h2>Case 9.4.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 256k.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_4_8"></a> + <h2>Case 9.4.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 1M.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_4_9"></a> + <h2>Case 9.4.9</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 4M.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_5_1"></a> + <h2>Case 9.5.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 64 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_5_2"></a> + <h2>Case 9.5.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 128 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_5_3"></a> + <h2>Case 9.5.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 256 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_5_4"></a> + <h2>Case 9.5.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 512 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_5_5"></a> + <h2>Case 9.5.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 1024 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_5_6"></a> + <h2>Case 9.5.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 2048 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_6_1"></a> + <h2>Case 9.6.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 64 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_6_2"></a> + <h2>Case 9.6.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 128 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_6_3"></a> + <h2>Case 9.6.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 256 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_6_4"></a> + <h2>Case 9.6.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 512 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_6_5"></a> + <h2>Case 9.6.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 1024 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_6_6"></a> + <h2>Case 9.6.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 2048 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_7_1"></a> + <h2>Case 9.7.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 text messages of payload size 0 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text messages (with payload as sent). Timeout case after 60 secs.</p> + <br/> + <a name="case_desc_9_7_2"></a> + <h2>Case 9.7.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 text messages of payload size 16 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text messages (with payload as sent). Timeout case after 60 secs.</p> + <br/> + <a name="case_desc_9_7_3"></a> + <h2>Case 9.7.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 text messages of payload size 64 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text messages (with payload as sent). Timeout case after 60 secs.</p> + <br/> + <a name="case_desc_9_7_4"></a> + <h2>Case 9.7.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 text messages of payload size 256 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text messages (with payload as sent). Timeout case after 120 secs.</p> + <br/> + <a name="case_desc_9_7_5"></a> + <h2>Case 9.7.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 text messages of payload size 1024 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text messages (with payload as sent). Timeout case after 240 secs.</p> + <br/> + <a name="case_desc_9_7_6"></a> + <h2>Case 9.7.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 text messages of payload size 4096 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text messages (with payload as sent). Timeout case after 480 secs.</p> + <br/> + <a name="case_desc_9_8_1"></a> + <h2>Case 9.8.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 binary messages of payload size 0 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary messages (with payload as sent). Timeout case after 60 secs.</p> + <br/> + <a name="case_desc_9_8_2"></a> + <h2>Case 9.8.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 binary messages of payload size 16 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary messages (with payload as sent). Timeout case after 60 secs.</p> + <br/> + <a name="case_desc_9_8_3"></a> + <h2>Case 9.8.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 binary messages of payload size 64 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary messages (with payload as sent). Timeout case after 60 secs.</p> + <br/> + <a name="case_desc_9_8_4"></a> + <h2>Case 9.8.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 binary messages of payload size 256 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary messages (with payload as sent). Timeout case after 120 secs.</p> + <br/> + <a name="case_desc_9_8_5"></a> + <h2>Case 9.8.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 binary messages of payload size 1024 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary messages (with payload as sent). Timeout case after 240 secs.</p> + <br/> + <a name="case_desc_9_8_6"></a> + <h2>Case 9.8.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 binary messages of payload size 4096 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary messages (with payload as sent). Timeout case after 480 secs.</p> + <br/> + <a name="case_desc_10_1_1"></a> + <h2>Case 10.1.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message with payload of length 65536 and <b>autoFragmentSize = 1300</b>.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent and transmitted frame counts as expected). Clean close with normal code.</p> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_3_2.html b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_3_2.html new file mode 100644 index 00000000..81b697dd --- /dev/null +++ b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_3_2.html @@ -0,0 +1,305 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_non_strict">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 3.2</b></span> : Non-Strict - <span style="font-size: 0.9em;"><b>2</b> ms @ 2012-02-04T15:41:06Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message, then send again with <b>RSV = 2</b>, then send Ping.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echo for first message is received, but then connection is failed immediately, since RSV must be 0, when no extension defining RSV meaning has been negoiated. The Pong is not received.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events match at least one expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'NON-STRICT': [], 'OK': [('message', 'Hello, world!', False)]}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>Connection was properly closed (OK)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: naQpeQXM16oTsQGgFhzkCw== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: GWrmDJGkZekAGDtxltB9SFScv/0=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">False</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">False</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1000</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">None</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td><td>131</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>6</td><td>1</td><td>6</td></tr> + <tr class="stats_row"><td>8</td><td>1</td><td>8</td></tr> + <tr class="stats_row"><td>19</td><td>2</td><td>38</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>5</td><td>253</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>1</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_row"><td>9</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>4</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a206e615170</pre> + <pre class="wirelog_tx_octets"> 6551584d31366f547351476746687a6b43773d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a20475772</pre> + <pre class="wirelog_rx_octets"> 6d444a476b5a656b41474474786c74423953465363762f303d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=c8e7cd39, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> Hello, world!</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 818dc8e7cd398082a155a7cbed4ea795a15de9</pre> + <pre class="wirelog_tx_frame">004 TX FRAME : OPCODE=1, FIN=True, RSV=2, MASK=48cf9b10, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> Hello, world!</pre> + <pre class="wirelog_tx_octets">005 TX OCTETS: a18d48cf9b1000aaf77c27e3bb6727bdf77469</pre> + <pre class="wirelog_tx_frame">006 TX FRAME : OPCODE=9, FIN=True, RSV=0, MASK=ba7b6257, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_octets">007 TX OCTETS: 8980ba7b6257</pre> + <pre class="wirelog_kill_after">008 FAIL CONNECTION AFTER 1.000000 sec</pre> + <pre class="wirelog_rx_octets">009 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">010 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tx_frame">011 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=c2125d8a, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">012 TX OCTETS: 8882c2125d8ac1fa</pre> + <pre class="wirelog_tcp_closed_by_peer">013 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_4_1_3.html b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_4_1_3.html new file mode 100644 index 00000000..ed037118 --- /dev/null +++ b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_4_1_3.html @@ -0,0 +1,305 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_non_strict">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 4.1.3</b></span> : Non-Strict - <span style="font-size: 0.9em;"><b>2</b> ms @ 2012-02-04T15:41:06Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message, then send frame with reserved non-control <b>Opcode = 5</b>, then send Ping.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events match at least one expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'NON-STRICT': [], 'OK': [('message', 'Hello, world!', False)]}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>Connection was properly closed (OK)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: xsPC4lQvjb4PhpJeBNWHdQ== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: ZT0DfgjT9h2dF3tlZwQbiVc8r5s=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">False</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">False</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1000</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">None</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td><td>131</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>6</td><td>2</td><td>12</td></tr> + <tr class="stats_row"><td>8</td><td>1</td><td>8</td></tr> + <tr class="stats_row"><td>19</td><td>1</td><td>19</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>5</td><td>240</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>1</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>5</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_row"><td>9</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>4</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a2078735043</pre> + <pre class="wirelog_tx_octets"> 346c51766a62345068704a65424e574864513d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a205a5430</pre> + <pre class="wirelog_rx_octets"> 4466676a54396832644633746c5a775162695663387235733d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=c591fc05, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> Hello, world!</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 818dc591fc058df49069aabddc72aae39061e4</pre> + <pre class="wirelog_tx_frame">004 TX FRAME : OPCODE=5, FIN=True, RSV=0, MASK=245c9b85, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_octets">005 TX OCTETS: 8580245c9b85</pre> + <pre class="wirelog_tx_frame">006 TX FRAME : OPCODE=9, FIN=True, RSV=0, MASK=235abec4, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_octets">007 TX OCTETS: 8980235abec4</pre> + <pre class="wirelog_kill_after">008 FAIL CONNECTION AFTER 1.000000 sec</pre> + <pre class="wirelog_rx_octets">009 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">010 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tx_frame">011 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=3b961406, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">012 TX OCTETS: 88823b961406387e</pre> + <pre class="wirelog_tcp_closed_by_peer">013 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_4_1_4.html b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_4_1_4.html new file mode 100644 index 00000000..534afc19 --- /dev/null +++ b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_4_1_4.html @@ -0,0 +1,306 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_non_strict">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 4.1.4</b></span> : Non-Strict - <span style="font-size: 0.9em;"><b>2</b> ms @ 2012-02-04T15:41:06Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message, then send frame with reserved non-control <b>Opcode = 6</b> and non-empty payload, then send Ping.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events match at least one expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'NON-STRICT': [], 'OK': [('message', 'Hello, world!', False)]}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>Connection was properly closed (OK)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: lWjk0P7OM1q+JBUPL0HfZA== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: d7k1A19qGfNKwuf0DJJ9pJTFpII=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">False</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">False</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1000</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">None</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td><td>131</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>6</td><td>1</td><td>6</td></tr> + <tr class="stats_row"><td>8</td><td>1</td><td>8</td></tr> + <tr class="stats_row"><td>19</td><td>2</td><td>38</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>5</td><td>253</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>1</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>6</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_row"><td>9</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>4</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a206c576a6b</pre> + <pre class="wirelog_tx_octets"> 3050374f4d31712b4a4255504c3048665a413d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a2064376b</pre> + <pre class="wirelog_rx_octets"> 314131397147664e4b77756630444a4a39704a54467049493d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=45843e64, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> Hello, world!</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 818d45843e640de152082aa81e132af6520064</pre> + <pre class="wirelog_tx_frame">004 TX FRAME : OPCODE=6, FIN=True, RSV=0, MASK=fa380a4b, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> Hello, world!</pre> + <pre class="wirelog_tx_octets">005 TX OCTETS: 868dfa380a4bb25d662795142a3c954a662fdb</pre> + <pre class="wirelog_tx_frame">006 TX FRAME : OPCODE=9, FIN=True, RSV=0, MASK=cfe1af5c, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_octets">007 TX OCTETS: 8980cfe1af5c</pre> + <pre class="wirelog_kill_after">008 FAIL CONNECTION AFTER 1.000000 sec</pre> + <pre class="wirelog_rx_octets">009 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">010 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tx_frame">011 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=4f85959a, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">012 TX OCTETS: 88824f85959a4c6d</pre> + <pre class="wirelog_tcp_closed_by_peer">013 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_4_2_3.html b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_4_2_3.html new file mode 100644 index 00000000..815e8025 --- /dev/null +++ b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_4_2_3.html @@ -0,0 +1,305 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_non_strict">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 4.2.3</b></span> : Non-Strict - <span style="font-size: 0.9em;"><b>3</b> ms @ 2012-02-04T15:41:06Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message, then send frame with reserved control <b>Opcode = 13</b>, then send Ping.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events match at least one expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'NON-STRICT': [], 'OK': [('message', 'Hello, world!', False)]}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>Connection was properly closed (OK)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: QWLCYu7pAj1LS5HIqlz7mg== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: ctI9BeFJRgdrhJO7B0QsoaPVOkI=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">False</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">False</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1000</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">None</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td><td>131</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>6</td><td>2</td><td>12</td></tr> + <tr class="stats_row"><td>8</td><td>1</td><td>8</td></tr> + <tr class="stats_row"><td>19</td><td>1</td><td>19</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>5</td><td>240</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>1</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_row"><td>9</td><td>1</td></tr> + <tr class="stats_row"><td>13</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>4</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a2051574c43</pre> + <pre class="wirelog_tx_octets"> 59753770416a314c53354849716c7a376d673d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a20637449</pre> + <pre class="wirelog_rx_octets"> 394265464a52676472684a4f37423051736f6150564f6b493d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=24435521, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> Hello, world!</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 818d244355216c26394d4b6f75564b31394505</pre> + <pre class="wirelog_tx_frame">004 TX FRAME : OPCODE=13, FIN=True, RSV=0, MASK=b81daa6e, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_octets">005 TX OCTETS: 8d80b81daa6e</pre> + <pre class="wirelog_tx_frame">006 TX FRAME : OPCODE=9, FIN=True, RSV=0, MASK=a52d50b5, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_octets">007 TX OCTETS: 8980a52d50b5</pre> + <pre class="wirelog_kill_after">008 FAIL CONNECTION AFTER 1.000000 sec</pre> + <pre class="wirelog_rx_octets">009 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">010 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tx_frame">011 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=1564e876, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">012 TX OCTETS: 88821564e876168c</pre> + <pre class="wirelog_tcp_closed_by_peer">013 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_4_2_4.html b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_4_2_4.html new file mode 100644 index 00000000..212d2a92 --- /dev/null +++ b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_4_2_4.html @@ -0,0 +1,306 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_non_strict">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 4.2.4</b></span> : Non-Strict - <span style="font-size: 0.9em;"><b>3</b> ms @ 2012-02-04T15:41:06Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message, then send frame with reserved control <b>Opcode = 14</b> and non-empty payload, then send Ping.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events match at least one expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'NON-STRICT': [], 'OK': [('message', 'Hello, world!', False)]}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>Connection was properly closed (OK)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: Kh3b3ltcf0ADlsGo2EnkTg== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: 1v44L9ZzUJ6KHZJ5yJYvz+wLNC4=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">False</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">False</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1000</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">None</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td><td>131</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>6</td><td>1</td><td>6</td></tr> + <tr class="stats_row"><td>8</td><td>1</td><td>8</td></tr> + <tr class="stats_row"><td>19</td><td>2</td><td>38</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>5</td><td>253</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>1</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_row"><td>9</td><td>1</td></tr> + <tr class="stats_row"><td>14</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>4</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a204b683362</pre> + <pre class="wirelog_tx_octets"> 336c7463663041446c73476f32456e6b54673d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a20317634</pre> + <pre class="wirelog_rx_octets"> 344c395a7a554a364b485a4a35794a59767a2b774c4e43343d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=4a1e0f77, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> Hello, world!</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 818d4a1e0f77027b631b25322f00256c63136b</pre> + <pre class="wirelog_tx_frame">004 TX FRAME : OPCODE=14, FIN=True, RSV=0, MASK=8a729523, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> Hello, world!</pre> + <pre class="wirelog_tx_octets">005 TX OCTETS: 8e8d8a729523c217f94fe55eb554e500f947ab</pre> + <pre class="wirelog_tx_frame">006 TX FRAME : OPCODE=9, FIN=True, RSV=0, MASK=158f23ed, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_octets">007 TX OCTETS: 8980158f23ed</pre> + <pre class="wirelog_kill_after">008 FAIL CONNECTION AFTER 1.000000 sec</pre> + <pre class="wirelog_rx_octets">009 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">010 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tx_frame">011 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=9ff83713, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">012 TX OCTETS: 88829ff837139c10</pre> + <pre class="wirelog_tcp_closed_by_peer">013 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_1.html b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_1.html new file mode 100644 index 00000000..b368498d --- /dev/null +++ b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_1.html @@ -0,0 +1,304 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_failed">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 6.20.1</b></span> : Fail - <span style="font-size: 0.9em;"><b>501</b> ms @ 2012-02-04T15:41:19Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>eda080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events differ from any expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'OK': []}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[('message', '?', False)]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>The connection was failed by the wrong endpoint (FAILED)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: 8hQi20KtwNAl024fziMiZA== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: 2vQQWjYeEC/rYsn25QkOUP1J64E=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">True</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">True</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1001</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">Going Away</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>3</td><td>1</td><td>3</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>134</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>9</td><td>1</td><td>9</td></tr> + <tr class="stats_row"><td>18</td><td>1</td><td>18</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>228</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a2038685169</pre> + <pre class="wirelog_tx_octets"> 32304b74774e416c303234667a694d695a413d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a20327651</pre> + <pre class="wirelog_rx_octets"> 51576a596545432f7259736e3235516b4f5550314a3634453d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=09392f03, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> ���</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 818309392f03e499af</pre> + <pre class="wirelog_kill_after">004 FAIL CONNECTION AFTER 0.500000 sec</pre> + <pre class="wirelog_rx_octets">005 RX OCTETS: 81013f</pre> + <pre class="wirelog_rx_frame">006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_rx_frame"> ?</pre> + <pre class="wirelog_kill_after">007 FAILING CONNECTION</pre> + <pre class="wirelog_tx_frame">008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=1a762475, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �Going Away</pre> + <pre class="wirelog_tx_octets">009 TX OCTETS: 888c1a762475199f631a731843555b01450c</pre> + <pre class="wirelog_rx_octets">010 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tcp_closed_by_peer">012 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_2.html b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_2.html new file mode 100644 index 00000000..85a01254 --- /dev/null +++ b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_2.html @@ -0,0 +1,304 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_failed">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 6.20.2</b></span> : Fail - <span style="font-size: 0.9em;"><b>501</b> ms @ 2012-02-04T15:41:19Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>edadbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events differ from any expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'OK': []}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[('message', '?', False)]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>The connection was failed by the wrong endpoint (FAILED)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: TIQj7T90U3MBd7UDV/Tkeg== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: mKV6+d9BjSDvtA484tge96+03HE=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">True</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">True</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1001</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">Going Away</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>3</td><td>1</td><td>3</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>134</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>9</td><td>1</td><td>9</td></tr> + <tr class="stats_row"><td>18</td><td>1</td><td>18</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>228</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a205449516a</pre> + <pre class="wirelog_tx_octets"> 3754393055334d4264375544562f546b65673d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a206d4b56</pre> + <pre class="wirelog_rx_octets"> 362b6439426a534476744134383474676539362b303348453d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=5711da46, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> ���</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 81835711da46babc65</pre> + <pre class="wirelog_kill_after">004 FAIL CONNECTION AFTER 0.500000 sec</pre> + <pre class="wirelog_rx_octets">005 RX OCTETS: 81013f</pre> + <pre class="wirelog_rx_frame">006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_rx_frame"> ?</pre> + <pre class="wirelog_kill_after">007 FAILING CONNECTION</pre> + <pre class="wirelog_tx_frame">008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=d65c7a61, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �Going Away</pre> + <pre class="wirelog_tx_octets">009 TX OCTETS: 888cd65c7a61d5b53d0ebf321d41972b1b18</pre> + <pre class="wirelog_rx_octets">010 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tcp_closed_by_peer">012 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_3.html b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_3.html new file mode 100644 index 00000000..4ae15cf2 --- /dev/null +++ b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_3.html @@ -0,0 +1,304 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_failed">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 6.20.3</b></span> : Fail - <span style="font-size: 0.9em;"><b>501</b> ms @ 2012-02-04T15:41:20Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>edae80</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events differ from any expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'OK': []}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[('message', '?', False)]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>The connection was failed by the wrong endpoint (FAILED)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: 0CmjM4vLTi5KG77RquVjKQ== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: rsX/MnJlDY6UxbWeADBpCfev0IQ=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">True</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">True</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1001</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">Going Away</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>3</td><td>1</td><td>3</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>134</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>9</td><td>1</td><td>9</td></tr> + <tr class="stats_row"><td>18</td><td>1</td><td>18</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>228</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a2030436d6a</pre> + <pre class="wirelog_tx_octets"> 4d34764c5469354b473737527175566a4b513d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a20727358</pre> + <pre class="wirelog_rx_octets"> 2f4d6e4a6c445936557862576541444270436665763049513d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=ccfdb4e5, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> ���</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 8183ccfdb4e5215334</pre> + <pre class="wirelog_kill_after">004 FAIL CONNECTION AFTER 0.500000 sec</pre> + <pre class="wirelog_rx_octets">005 RX OCTETS: 81013f</pre> + <pre class="wirelog_rx_frame">006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_rx_frame"> ?</pre> + <pre class="wirelog_kill_after">007 FAILING CONNECTION</pre> + <pre class="wirelog_tx_frame">008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=459bc411, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �Going Away</pre> + <pre class="wirelog_tx_octets">009 TX OCTETS: 888c459bc4114672837e2cf5a33104eca568</pre> + <pre class="wirelog_rx_octets">010 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tcp_closed_by_peer">012 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_4.html b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_4.html new file mode 100644 index 00000000..f555f5ce --- /dev/null +++ b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_4.html @@ -0,0 +1,304 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_failed">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 6.20.4</b></span> : Fail - <span style="font-size: 0.9em;"><b>501</b> ms @ 2012-02-04T15:41:20Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>edafbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events differ from any expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'OK': []}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[('message', '?', False)]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>The connection was failed by the wrong endpoint (FAILED)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: 9Xx5xE9r30LfukV3JZQxMg== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: IsjbPBLW/m9/EBoONc42PJANrK0=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">True</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">True</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1001</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">Going Away</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>3</td><td>1</td><td>3</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>134</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>9</td><td>1</td><td>9</td></tr> + <tr class="stats_row"><td>18</td><td>1</td><td>18</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>228</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a2039587835</pre> + <pre class="wirelog_tx_octets"> 7845397233304c66756b56334a5a51784d673d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a2049736a</pre> + <pre class="wirelog_rx_octets"> 6250424c572f6d392f45426f4f4e633432504a414e724b303d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=a58665e4, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> ���</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 8183a58665e44829da</pre> + <pre class="wirelog_kill_after">004 FAIL CONNECTION AFTER 0.500000 sec</pre> + <pre class="wirelog_rx_octets">005 RX OCTETS: 81013f</pre> + <pre class="wirelog_rx_frame">006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_rx_frame"> ?</pre> + <pre class="wirelog_kill_after">007 FAILING CONNECTION</pre> + <pre class="wirelog_tx_frame">008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=3ec8dc0c, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �Going Away</pre> + <pre class="wirelog_tx_octets">009 TX OCTETS: 888c3ec8dc0c3d219b6357a6bb2c7fbfbd75</pre> + <pre class="wirelog_rx_octets">010 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tcp_closed_by_peer">012 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_5.html b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_5.html new file mode 100644 index 00000000..bdea9156 --- /dev/null +++ b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_5.html @@ -0,0 +1,304 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_failed">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 6.20.5</b></span> : Fail - <span style="font-size: 0.9em;"><b>501</b> ms @ 2012-02-04T15:41:21Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>edb080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events differ from any expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'OK': []}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[('message', '?', False)]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>The connection was failed by the wrong endpoint (FAILED)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: kaPj4eOJvtYO/p1+SUWjFA== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: gjAUZ7QN+QSE+ENjYD8MxlmR9fg=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">True</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">True</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1001</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">Going Away</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>3</td><td>1</td><td>3</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>134</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>9</td><td>1</td><td>9</td></tr> + <tr class="stats_row"><td>18</td><td>1</td><td>18</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>228</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a206b61506a</pre> + <pre class="wirelog_tx_octets"> 34654f4a7674594f2f70312b5355576a46413d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a20676a41</pre> + <pre class="wirelog_rx_octets"> 555a37514e2b5153452b454e6a5944384d786c6d523966673d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=a90dd396, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> ���</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 8183a90dd39644bd53</pre> + <pre class="wirelog_kill_after">004 FAIL CONNECTION AFTER 0.500000 sec</pre> + <pre class="wirelog_rx_octets">005 RX OCTETS: 81013f</pre> + <pre class="wirelog_rx_frame">006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_rx_frame"> ?</pre> + <pre class="wirelog_kill_after">007 FAILING CONNECTION</pre> + <pre class="wirelog_tx_frame">008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=e0ae2699, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �Going Away</pre> + <pre class="wirelog_tx_octets">009 TX OCTETS: 888ce0ae2699e34761f689c041b9a1d947e0</pre> + <pre class="wirelog_rx_octets">010 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tcp_closed_by_peer">012 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_6.html b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_6.html new file mode 100644 index 00000000..9b219d47 --- /dev/null +++ b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_6.html @@ -0,0 +1,304 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_failed">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 6.20.6</b></span> : Fail - <span style="font-size: 0.9em;"><b>501</b> ms @ 2012-02-04T15:41:21Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>edbe80</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events differ from any expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'OK': []}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[('message', '?', False)]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>The connection was failed by the wrong endpoint (FAILED)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: D3AO5HGDbIPBkfq4kMmnpA== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: hv3czFZ5FFyETGd0ABBqDf9iiMs=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">True</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">True</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1001</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">Going Away</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>3</td><td>1</td><td>3</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>134</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>9</td><td>1</td><td>9</td></tr> + <tr class="stats_row"><td>18</td><td>1</td><td>18</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>228</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a204433414f</pre> + <pre class="wirelog_tx_octets"> 35484744624950426b6671346b4d6d6e70413d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a20687633</pre> + <pre class="wirelog_rx_octets"> 637a465a3546467945544764304142427144663969694d733d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=5bd63f0e, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> ���</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 81835bd63f0eb668bf</pre> + <pre class="wirelog_kill_after">004 FAIL CONNECTION AFTER 0.500000 sec</pre> + <pre class="wirelog_rx_octets">005 RX OCTETS: 81013f</pre> + <pre class="wirelog_rx_frame">006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_rx_frame"> ?</pre> + <pre class="wirelog_kill_after">007 FAILING CONNECTION</pre> + <pre class="wirelog_tx_frame">008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=022b8ad0, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �Going Away</pre> + <pre class="wirelog_tx_octets">009 TX OCTETS: 888c022b8ad001c2cdbf6b45edf0435ceba9</pre> + <pre class="wirelog_rx_octets">010 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tcp_closed_by_peer">012 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_7.html b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_7.html new file mode 100644 index 00000000..f36f8858 --- /dev/null +++ b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_7.html @@ -0,0 +1,304 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_failed">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 6.20.7</b></span> : Fail - <span style="font-size: 0.9em;"><b>501</b> ms @ 2012-02-04T15:41:22Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>edbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events differ from any expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'OK': []}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[('message', '?', False)]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>The connection was failed by the wrong endpoint (FAILED)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: W6Be3rM7Egj4hNIscD5zEA== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: G36lsKHfg04ueUxN92iO/nJvKO8=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">True</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">True</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1001</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">Going Away</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>3</td><td>1</td><td>3</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>134</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>9</td><td>1</td><td>9</td></tr> + <tr class="stats_row"><td>18</td><td>1</td><td>18</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>228</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a2057364265</pre> + <pre class="wirelog_tx_octets"> 33724d3745676a34684e49736344357a45413d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a20473336</pre> + <pre class="wirelog_rx_octets"> 6c734b4866673034756555784e3932694f2f6e4a764b4f383d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=af65e8a8, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> ���</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 8183af65e8a842da57</pre> + <pre class="wirelog_kill_after">004 FAIL CONNECTION AFTER 0.500000 sec</pre> + <pre class="wirelog_rx_octets">005 RX OCTETS: 81013f</pre> + <pre class="wirelog_rx_frame">006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_rx_frame"> ?</pre> + <pre class="wirelog_kill_after">007 FAILING CONNECTION</pre> + <pre class="wirelog_tx_frame">008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=87dcfe76, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �Going Away</pre> + <pre class="wirelog_tx_octets">009 TX OCTETS: 888c87dcfe768435b919eeb29956c6ab9f0f</pre> + <pre class="wirelog_rx_octets">010 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tcp_closed_by_peer">012 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_1.html b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_1.html new file mode 100644 index 00000000..b23b374b --- /dev/null +++ b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_1.html @@ -0,0 +1,304 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_failed">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 6.21.1</b></span> : Fail - <span style="font-size: 0.9em;"><b>501</b> ms @ 2012-02-04T15:41:22Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>eda080edb080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events differ from any expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'OK': []}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[('message', '\xf0\x90\x80\x80', False)]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>The connection was failed by the wrong endpoint (FAILED)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: 4+A0RTBdoXG1pq8BzP6v7A== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: hOGfIW+mXTQpFxEAAzhJQZBq3D0=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">True</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">True</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1001</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">Going Away</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>6</td><td>1</td><td>6</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>137</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>12</td><td>1</td><td>12</td></tr> + <tr class="stats_row"><td>18</td><td>1</td><td>18</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>231</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a20342b4130</pre> + <pre class="wirelog_tx_octets"> 525442646f584731707138427a50367637413d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a20684f47</pre> + <pre class="wirelog_rx_octets"> 6649572b6d5854517046784541417a684a515a42713344303d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=fa43fe25, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> ������</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 8186fa43fe2517e37ec84ac3</pre> + <pre class="wirelog_kill_after">004 FAIL CONNECTION AFTER 0.500000 sec</pre> + <pre class="wirelog_rx_octets">005 RX OCTETS: 8104f0908080</pre> + <pre class="wirelog_rx_frame">006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_rx_frame"> 𐀀</pre> + <pre class="wirelog_kill_after">007 FAILING CONNECTION</pre> + <pre class="wirelog_tx_frame">008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=9d22e55b, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �Going Away</pre> + <pre class="wirelog_tx_octets">009 TX OCTETS: 888c9d22e55b9ecba234f44c827bdc558422</pre> + <pre class="wirelog_rx_octets">010 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tcp_closed_by_peer">012 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_2.html b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_2.html new file mode 100644 index 00000000..1df9c568 --- /dev/null +++ b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_2.html @@ -0,0 +1,304 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_failed">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 6.21.2</b></span> : Fail - <span style="font-size: 0.9em;"><b>501</b> ms @ 2012-02-04T15:41:23Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>eda080edbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events differ from any expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'OK': []}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[('message', '\xf0\x90\x8f\xbf', False)]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>The connection was failed by the wrong endpoint (FAILED)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: iPuOK+BgWhbmEfm05SYBXA== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: nFtyv6PfaFkNa7HnveKzhe3IZ30=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">True</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">True</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1001</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">Going Away</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>6</td><td>1</td><td>6</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>137</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>12</td><td>1</td><td>12</td></tr> + <tr class="stats_row"><td>18</td><td>1</td><td>18</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>231</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a206950754f</pre> + <pre class="wirelog_tx_octets"> 4b2b42675768626d45666d303553594258413d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a206e4674</pre> + <pre class="wirelog_rx_octets"> 797636506661466b4e6137486e76654b7a686533495a33303d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=3cf02dbc, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> ������</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 81863cf02dbcd150ad51834f</pre> + <pre class="wirelog_kill_after">004 FAIL CONNECTION AFTER 0.500000 sec</pre> + <pre class="wirelog_rx_octets">005 RX OCTETS: 8104f0908fbf</pre> + <pre class="wirelog_rx_frame">006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_rx_frame"> </pre> + <pre class="wirelog_kill_after">007 FAILING CONNECTION</pre> + <pre class="wirelog_tx_frame">008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=28c01a4b, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �Going Away</pre> + <pre class="wirelog_tx_octets">009 TX OCTETS: 888c28c01a4b2b295d2441ae7d6b69b77b32</pre> + <pre class="wirelog_rx_octets">010 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tcp_closed_by_peer">012 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_3.html b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_3.html new file mode 100644 index 00000000..604ab44f --- /dev/null +++ b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_3.html @@ -0,0 +1,304 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_failed">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 6.21.3</b></span> : Fail - <span style="font-size: 0.9em;"><b>501</b> ms @ 2012-02-04T15:41:23Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>edadbfedb080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events differ from any expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'OK': []}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[('message', '\xf3\xaf\xb0\x80', False)]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>The connection was failed by the wrong endpoint (FAILED)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: IxlOTYbZXdgzf0DqE51BTQ== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: /BBFWqSURVgKaSzwpkh/xBflPdw=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">True</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">True</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1001</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">Going Away</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>6</td><td>1</td><td>6</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>137</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>12</td><td>1</td><td>12</td></tr> + <tr class="stats_row"><td>18</td><td>1</td><td>18</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>231</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a2049786c4f</pre> + <pre class="wirelog_tx_octets"> 5459625a5864677a663044714535314254513d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a202f4242</pre> + <pre class="wirelog_rx_octets"> 46577153555256674b61537a77706b682f7842666c5064773d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=826ee366, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> ������</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 8186826ee3666fc35c8b32ee</pre> + <pre class="wirelog_kill_after">004 FAIL CONNECTION AFTER 0.500000 sec</pre> + <pre class="wirelog_rx_octets">005 RX OCTETS: 8104f3afb080</pre> + <pre class="wirelog_rx_frame">006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_rx_frame"> </pre> + <pre class="wirelog_kill_after">007 FAILING CONNECTION</pre> + <pre class="wirelog_tx_frame">008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=bebc7f7d, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �Going Away</pre> + <pre class="wirelog_tx_octets">009 TX OCTETS: 888cbebc7f7dbd553812d7d2185dffcb1e04</pre> + <pre class="wirelog_rx_octets">010 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tcp_closed_by_peer">012 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_4.html b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_4.html new file mode 100644 index 00000000..4e24ef1a --- /dev/null +++ b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_4.html @@ -0,0 +1,304 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_failed">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 6.21.4</b></span> : Fail - <span style="font-size: 0.9em;"><b>501</b> ms @ 2012-02-04T15:41:24Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>edadbfedbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events differ from any expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'OK': []}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[('message', '\xf3\xaf\xbf\xbf', False)]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>The connection was failed by the wrong endpoint (FAILED)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: 2dVkikWX/+l/pkvKSrgYMg== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: FZu8BS6bWe/A9bdToW/oxTah8fI=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">True</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">True</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1001</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">Going Away</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>6</td><td>1</td><td>6</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>137</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>12</td><td>1</td><td>12</td></tr> + <tr class="stats_row"><td>18</td><td>1</td><td>18</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>231</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a203264566b</pre> + <pre class="wirelog_tx_octets"> 696b57582f2b6c2f706b764b537267594d673d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a20465a75</pre> + <pre class="wirelog_rx_octets"> 384253366257652f41396264546f572f6f785461683866493d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=0a2a18c5, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> ������</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 81860a2a18c5e787a728b595</pre> + <pre class="wirelog_kill_after">004 FAIL CONNECTION AFTER 0.500000 sec</pre> + <pre class="wirelog_rx_octets">005 RX OCTETS: 8104f3afbfbf</pre> + <pre class="wirelog_rx_frame">006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_rx_frame"> </pre> + <pre class="wirelog_kill_after">007 FAILING CONNECTION</pre> + <pre class="wirelog_tx_frame">008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=16433ff0, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �Going Away</pre> + <pre class="wirelog_tx_octets">009 TX OCTETS: 888c16433ff015aa789f7f2d58d057345e89</pre> + <pre class="wirelog_rx_octets">010 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tcp_closed_by_peer">012 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_5.html b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_5.html new file mode 100644 index 00000000..1dde15cf --- /dev/null +++ b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_5.html @@ -0,0 +1,304 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_failed">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 6.21.5</b></span> : Fail - <span style="font-size: 0.9em;"><b>502</b> ms @ 2012-02-04T15:41:24Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>edae80edb080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events differ from any expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'OK': []}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[('message', '\xf3\xb0\x80\x80', False)]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>The connection was failed by the wrong endpoint (FAILED)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: PM0Ykka0trj5Ks54acrZRw== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: 0rB4RXL5G/swTTfmBlmWgyUkAZQ=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">True</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">True</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1001</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">Going Away</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>6</td><td>1</td><td>6</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>137</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>12</td><td>1</td><td>12</td></tr> + <tr class="stats_row"><td>18</td><td>1</td><td>18</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>231</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a20504d3059</pre> + <pre class="wirelog_tx_octets"> 6b6b613074726a354b7335346163725a52773d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a20307242</pre> + <pre class="wirelog_rx_octets"> 3452584c35472f73775454666d426c6d576779556b415a513d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=a4035bc2, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> ������</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 8186a4035bc249addb2f1483</pre> + <pre class="wirelog_kill_after">004 FAIL CONNECTION AFTER 0.500000 sec</pre> + <pre class="wirelog_rx_octets">005 RX OCTETS: 8104f3b08080</pre> + <pre class="wirelog_rx_frame">006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_rx_frame"> </pre> + <pre class="wirelog_kill_after">007 FAILING CONNECTION</pre> + <pre class="wirelog_tx_frame">008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=04cc48c4, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �Going Away</pre> + <pre class="wirelog_tx_octets">009 TX OCTETS: 888c04cc48c407250fab6da22fe445bb29bd</pre> + <pre class="wirelog_rx_octets">010 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tcp_closed_by_peer">012 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_6.html b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_6.html new file mode 100644 index 00000000..50ce3e2d --- /dev/null +++ b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_6.html @@ -0,0 +1,304 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_failed">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 6.21.6</b></span> : Fail - <span style="font-size: 0.9em;"><b>501</b> ms @ 2012-02-04T15:41:25Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>edae80edbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events differ from any expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'OK': []}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[('message', '\xf3\xb0\x8f\xbf', False)]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>The connection was failed by the wrong endpoint (FAILED)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: 3HjHLxTtDIUsMmTglsvCEQ== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: jlA618sXEEsPiOOzZdRjjkUAls8=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">True</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">True</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1001</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">Going Away</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>6</td><td>1</td><td>6</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>137</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>12</td><td>1</td><td>12</td></tr> + <tr class="stats_row"><td>18</td><td>1</td><td>18</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>231</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a2033486a48</pre> + <pre class="wirelog_tx_octets"> 4c785474444955734d6d54676c73764345513d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a206a6c41</pre> + <pre class="wirelog_rx_octets"> 363138735845457350694f4f7a5a64526a6a6b55416c73383d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=76c7ad31, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> ������</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 818676c7ad319b692ddcc978</pre> + <pre class="wirelog_kill_after">004 FAIL CONNECTION AFTER 0.500000 sec</pre> + <pre class="wirelog_rx_octets">005 RX OCTETS: 8104f3b08fbf</pre> + <pre class="wirelog_rx_frame">006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_rx_frame"> </pre> + <pre class="wirelog_kill_after">007 FAILING CONNECTION</pre> + <pre class="wirelog_tx_frame">008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=e1eb5d93, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �Going Away</pre> + <pre class="wirelog_tx_octets">009 TX OCTETS: 888ce1eb5d93e2021afc88853ab3a09c3cea</pre> + <pre class="wirelog_rx_octets">010 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tcp_closed_by_peer">012 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_7.html b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_7.html new file mode 100644 index 00000000..ddde6cbf --- /dev/null +++ b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_7.html @@ -0,0 +1,304 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_failed">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 6.21.7</b></span> : Fail - <span style="font-size: 0.9em;"><b>501</b> ms @ 2012-02-04T15:41:25Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>edafbfedb080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events differ from any expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'OK': []}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[('message', '\xf4\x8f\xb0\x80', False)]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>The connection was failed by the wrong endpoint (FAILED)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: ZIp0mbBtxEyXUFyOCJ+3+Q== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: DwOv/co2P4JILRERO9r1utPeUz4=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">True</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">True</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1001</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">Going Away</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>6</td><td>1</td><td>6</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>137</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>12</td><td>1</td><td>12</td></tr> + <tr class="stats_row"><td>18</td><td>1</td><td>18</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>231</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a205a497030</pre> + <pre class="wirelog_tx_octets"> 6d624274784579585546794f434a2b332b513d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a2044774f</pre> + <pre class="wirelog_rx_octets"> 762f636f3250344a494c5245524f39723175745065557a343d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=2ae4c89f, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> ������</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 81862ae4c89fc74b77729a64</pre> + <pre class="wirelog_kill_after">004 FAIL CONNECTION AFTER 0.500000 sec</pre> + <pre class="wirelog_rx_octets">005 RX OCTETS: 8104f48fb080</pre> + <pre class="wirelog_rx_frame">006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_rx_frame"> </pre> + <pre class="wirelog_kill_after">007 FAILING CONNECTION</pre> + <pre class="wirelog_tx_frame">008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=367f674f, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �Going Away</pre> + <pre class="wirelog_tx_octets">009 TX OCTETS: 888c367f674f359620205f11006f77080636</pre> + <pre class="wirelog_rx_octets">010 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tcp_closed_by_peer">012 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_8.html b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_8.html new file mode 100644 index 00000000..dee22d7c --- /dev/null +++ b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_8.html @@ -0,0 +1,304 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_failed">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 6.21.8</b></span> : Fail - <span style="font-size: 0.9em;"><b>501</b> ms @ 2012-02-04T15:41:26Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>edafbfedbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events differ from any expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'OK': []}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[('message', '\xf4\x8f\xbf\xbf', False)]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>The connection was failed by the wrong endpoint (FAILED)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: OaUcFV4MSdP7nxBXK+S38Q== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: j0hDBbytdKrxduaM2lyuEGp5b+M=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">True</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">True</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1001</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">Going Away</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>6</td><td>1</td><td>6</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>137</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>12</td><td>1</td><td>12</td></tr> + <tr class="stats_row"><td>18</td><td>1</td><td>18</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>231</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a204f615563</pre> + <pre class="wirelog_tx_octets"> 4656344d536450376e7842584b2b533338513d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a206a3068</pre> + <pre class="wirelog_rx_octets"> 4442627974644b72786475614d326c797545477035622b4d3d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=7c269b4e, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> ������</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 81867c269b4e918924a3c399</pre> + <pre class="wirelog_kill_after">004 FAIL CONNECTION AFTER 0.500000 sec</pre> + <pre class="wirelog_rx_octets">005 RX OCTETS: 8104f48fbfbf</pre> + <pre class="wirelog_rx_frame">006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_rx_frame"> </pre> + <pre class="wirelog_kill_after">007 FAILING CONNECTION</pre> + <pre class="wirelog_tx_frame">008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=9603c3bf, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �Going Away</pre> + <pre class="wirelog_tx_octets">009 TX OCTETS: 888c9603c3bf95ea84d0ff6da49fd774a2c6</pre> + <pre class="wirelog_rx_octets">010 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tcp_closed_by_peer">012 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_3_1.html b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_3_1.html new file mode 100644 index 00000000..d7a1a806 --- /dev/null +++ b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_3_1.html @@ -0,0 +1,304 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_failed">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 6.3.1</b></span> : Fail - <span style="font-size: 0.9em;"><b>1001</b> ms @ 2012-02-04T15:41:08Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send invalid UTF-8 text message unfragmented.<br><br>MESSAGE:<br>κόσμε���edited<br>cebae1bdb9cf83cebcceb5eda080656469746564</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events differ from any expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'OK': []}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[('message', '\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5?edited', False)]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>The connection was failed by the wrong endpoint (FAILED)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: giRxaADMiudnciYu2X3Ntw== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: VbnTiF6FgXdncLIxXEj+z/bK0y8=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">True</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">True</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1001</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">Going Away</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>20</td><td>1</td><td>20</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>151</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>18</td><td>1</td><td>18</td></tr> + <tr class="stats_row"><td>26</td><td>1</td><td>26</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>245</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a2067695278</pre> + <pre class="wirelog_tx_octets"> 6141444d6975646e636959753258334e74773d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a2056626e</pre> + <pre class="wirelog_rx_octets"> 54694636466758646e634c497858456a2b7a2f624b3079383d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=35445993, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> κόσμε���edited</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 819435445993fbfeb82e8c8bda5d898aec7e95c43cf75c303cf7</pre> + <pre class="wirelog_kill_after">004 FAIL CONNECTION AFTER 1.000000 sec</pre> + <pre class="wirelog_rx_octets">005 RX OCTETS: 8112cebae1bdb9cf83cebcceb53f656469746564</pre> + <pre class="wirelog_rx_frame">006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_rx_frame"> κόσμε?edited</pre> + <pre class="wirelog_kill_after">007 FAILING CONNECTION</pre> + <pre class="wirelog_tx_frame">008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=79a473a8, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �Going Away</pre> + <pre class="wirelog_tx_octets">009 TX OCTETS: 888c79a473a87a4d34c710ca148838d312d1</pre> + <pre class="wirelog_rx_octets">010 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tcp_closed_by_peer">012 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_3_2.html b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_3_2.html new file mode 100644 index 00000000..ff7dfa5f --- /dev/null +++ b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_3_2.html @@ -0,0 +1,365 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_failed">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 6.3.2</b></span> : Fail - <span style="font-size: 0.9em;"><b>1002</b> ms @ 2012-02-04T15:41:09Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send invalid UTF-8 text message in fragments of 1 octet, resulting in frames ending on positions which are not code point ends.<br><br>MESSAGE:<br>κόσμε���edited<br>cebae1bdb9cf83cebcceb5eda080656469746564</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events differ from any expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'OK': []}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[('message', '\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5?edited', False)]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>The connection was failed by the wrong endpoint (FAILED)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: +0fzXkyvkSjPhQlQQX66GA== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: LgO0/Am1g9xEfcTU2myHtnWvixQ=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">True</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">True</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1001</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">Going Away</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>20</td><td>1</td><td>20</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>151</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>6</td><td>1</td><td>6</td></tr> + <tr class="stats_row"><td>7</td><td>20</td><td>140</td></tr> + <tr class="stats_row"><td>18</td><td>1</td><td>18</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>23</td><td>365</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>0</td><td>20</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>22</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a202b30667a</pre> + <pre class="wirelog_tx_octets"> 586b79766b536a5068516c515158363647413d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a204c674f</pre> + <pre class="wirelog_rx_octets"> 302f416d316739784566635455326d7948746e57766978513d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=False, RSV=0, MASK=b2f15302, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 0181b2f153027c</pre> + <pre class="wirelog_tx_frame">004 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=c90d6517, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">005 TX OCTETS: 0081c90d651773</pre> + <pre class="wirelog_tx_frame">006 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=cd667264, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">007 TX OCTETS: 0081cd6672642c</pre> + <pre class="wirelog_tx_frame">008 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=024afe11, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">009 TX OCTETS: 0081024afe11bf</pre> + <pre class="wirelog_tx_frame">010 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=80b6c518, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">011 TX OCTETS: 008180b6c51839</pre> + <pre class="wirelog_tx_frame">012 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=62a7724e, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">013 TX OCTETS: 008162a7724ead</pre> + <pre class="wirelog_tx_frame">014 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=42f77b2b, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">015 TX OCTETS: 008142f77b2bc1</pre> + <pre class="wirelog_tx_frame">016 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=c1f0a0f1, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">017 TX OCTETS: 0081c1f0a0f10f</pre> + <pre class="wirelog_tx_frame">018 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=7f9e2838, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">019 TX OCTETS: 00817f9e2838c3</pre> + <pre class="wirelog_tx_frame">020 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=06f47c2c, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">021 TX OCTETS: 008106f47c2cc8</pre> + <pre class="wirelog_tx_frame">022 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=e139a1e1, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">023 TX OCTETS: 0081e139a1e154</pre> + <pre class="wirelog_tx_frame">024 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=d00820e1, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">025 TX OCTETS: 0081d00820e13d</pre> + <pre class="wirelog_tx_frame">026 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=a93ed31b, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">027 TX OCTETS: 0081a93ed31b09</pre> + <pre class="wirelog_tx_frame">028 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=86fe9fa7, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">029 TX OCTETS: 008186fe9fa706</pre> + <pre class="wirelog_tx_frame">030 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=da4f747e, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> e</pre> + <pre class="wirelog_tx_octets">031 TX OCTETS: 0081da4f747ebf</pre> + <pre class="wirelog_tx_frame">032 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=0906c2df, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> d</pre> + <pre class="wirelog_tx_octets">033 TX OCTETS: 00810906c2df6d</pre> + <pre class="wirelog_tx_frame">034 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=9c8128e5, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> i</pre> + <pre class="wirelog_tx_octets">035 TX OCTETS: 00819c8128e5f5</pre> + <pre class="wirelog_tx_frame">036 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=c0b043ed, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> t</pre> + <pre class="wirelog_tx_octets">037 TX OCTETS: 0081c0b043edb4</pre> + <pre class="wirelog_tx_frame">038 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=1723209b, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> e</pre> + <pre class="wirelog_tx_octets">039 TX OCTETS: 00811723209b72</pre> + <pre class="wirelog_tx_frame">040 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=a1194c70, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> d</pre> + <pre class="wirelog_tx_octets">041 TX OCTETS: 0081a1194c70c5</pre> + <pre class="wirelog_tx_frame">042 TX FRAME : OPCODE=0, FIN=True, RSV=0, MASK=73e98929, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_octets">043 TX OCTETS: 808073e98929</pre> + <pre class="wirelog_kill_after">044 FAIL CONNECTION AFTER 1.000000 sec</pre> + <pre class="wirelog_rx_octets">045 RX OCTETS: 8112cebae1bdb9cf83cebcceb53f656469746564</pre> + <pre class="wirelog_rx_frame">046 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_rx_frame"> κόσμε?edited</pre> + <pre class="wirelog_kill_after">047 FAILING CONNECTION</pre> + <pre class="wirelog_tx_frame">048 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=52a40872, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �Going Away</pre> + <pre class="wirelog_tx_octets">049 TX OCTETS: 888c52a40872514d4f1d3bca6f5213d3690b</pre> + <pre class="wirelog_rx_octets">050 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">051 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tcp_closed_by_peer">052 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_4_1.html b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_4_1.html new file mode 100644 index 00000000..a7168b71 --- /dev/null +++ b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_4_1.html @@ -0,0 +1,318 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_non_strict">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 6.4.1</b></span> : Non-Strict - <span style="font-size: 0.9em;"><b>2004</b> ms @ 2012-02-04T15:41:10Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send invalid UTF-8 text message in 3 fragments (frames). +First frame payload is valid, then wait, then 2nd frame which contains the payload making the sequence invalid, then wait, then 3rd frame with rest. +Note that PART1 and PART3 are valid UTF-8 in themselves, PART2 is a 0x11000 encoded as in the UTF-8 integer encoding scheme, but the codepoint is invalid (out of range). +<br><br>MESSAGE PARTS:<br> +PART1 = κόσμε (cebae1bdb9cf83cebcceb5)<br> +PART2 = ���� (f4908080)<br> +PART3 = edited (656469746564)<br> +</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The first frame is accepted, we expect to timeout on the first wait. The 2nd frame should be rejected immediately (fail fast on UTF-8). If we timeout, we expect the connection is failed at least then, since the complete message payload is not valid UTF-8.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events match at least one expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'NON-STRICT': [('timeout', 'A'), ('timeout', 'B')], 'OK': [('timeout', 'A')]}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[('timeout', 'A'), ('timeout', 'B')]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>Connection was properly closed (OK)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: UpAkZ//RWLrHfexyOMj3mw== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: HUMfuAwHoYZqlf78+bKZQrSw5t0=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">False</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">False</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1000</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">None</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td><td>131</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>8</td><td>1</td><td>8</td></tr> + <tr class="stats_row"><td>10</td><td>1</td><td>10</td></tr> + <tr class="stats_row"><td>12</td><td>1</td><td>12</td></tr> + <tr class="stats_row"><td>17</td><td>1</td><td>17</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>5</td><td>248</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>1</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>0</td><td>2</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>4</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a205570416b</pre> + <pre class="wirelog_tx_octets"> 5a2f2f52574c7248666578794f4d6a336d773d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a2048554d</pre> + <pre class="wirelog_rx_octets"> 66754177486f595a716c6637382b624b5a517253773574303d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=False, RSV=0, MASK=7ae1bfb5, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> κόσμε</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 018b7ae1bfb5b45b5e08c32e3c7bc62f0a</pre> + <pre class="wirelog_delay">004 DELAY 1.000000 sec for TAG A</pre> + <pre class="wirelog_delay">005 DELAY TIMEOUT on TAG A</pre> + <pre class="wirelog_tx_frame">006 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=98cbbec9, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> ����</pre> + <pre class="wirelog_tx_octets">007 TX OCTETS: 008498cbbec96c5b3e49</pre> + <pre class="wirelog_delay">008 DELAY 1.000000 sec for TAG B</pre> + <pre class="wirelog_delay">009 DELAY TIMEOUT on TAG B</pre> + <pre class="wirelog_tx_frame">010 TX FRAME : OPCODE=0, FIN=True, RSV=0, MASK=61ebbee3, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> edited</pre> + <pre class="wirelog_tx_octets">011 TX OCTETS: 808661ebbee3048fd797048f</pre> + <pre class="wirelog_kill_after">012 FAIL CONNECTION AFTER 1.000000 sec</pre> + <pre class="wirelog_rx_octets">013 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">014 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tx_frame">015 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=6bde347c, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">016 TX OCTETS: 88826bde347c6836</pre> + <pre class="wirelog_tcp_closed_by_peer">017 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_4_2.html b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_4_2.html new file mode 100644 index 00000000..8f4a7f7b --- /dev/null +++ b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_4_2.html @@ -0,0 +1,316 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_non_strict">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 6.4.2</b></span> : Non-Strict - <span style="font-size: 0.9em;"><b>2002</b> ms @ 2012-02-04T15:41:12Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Same as Case 6.4.1, but in 2nd frame, we send only up to and including the octet making the complete payload invalid. +<br><br>MESSAGE PARTS:<br> +PART1 = κόσμε� (cebae1bdb9cf83cebcceb5f4)<br> +PART2 = � (90)<br> +PART3 = ��edited (8080656469746564)<br> +</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The first frame is accepted, we expect to timeout on the first wait. The 2nd frame should be rejected immediately (fail fast on UTF-8). If we timeout, we expect the connection is failed at least then, since the complete message payload is not valid UTF-8.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events match at least one expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'NON-STRICT': [('timeout', 'A'), ('timeout', 'B')], 'OK': [('timeout', 'A')]}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[('timeout', 'A'), ('timeout', 'B')]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>Connection was properly closed (OK)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: Otomp9wsvzxBkXQi+T/5PQ== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: ATAg9VNP6aeymyNC0M0XC/eIdaU=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">False</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">False</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1000</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">None</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td><td>131</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>7</td><td>1</td><td>7</td></tr> + <tr class="stats_row"><td>8</td><td>1</td><td>8</td></tr> + <tr class="stats_row"><td>14</td><td>1</td><td>14</td></tr> + <tr class="stats_row"><td>18</td><td>1</td><td>18</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>5</td><td>248</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>1</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>0</td><td>2</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>4</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a204f746f6d</pre> + <pre class="wirelog_tx_octets"> 70397773767a78426b5851692b542f3550513d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a20415441</pre> + <pre class="wirelog_rx_octets"> 6739564e50366165796d794e43304d3058432f65496461553d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=False, RSV=0, MASK=a71060dd, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> κόσμε�</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 018ca71060dd69aa81601edfe3131bded529</pre> + <pre class="wirelog_delay">004 DELAY 1.000000 sec for TAG A</pre> + <pre class="wirelog_delay">005 DELAY TIMEOUT on TAG A</pre> + <pre class="wirelog_tx_frame">006 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=0dd9f64a, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">007 TX OCTETS: 00810dd9f64a9d</pre> + <pre class="wirelog_delay">008 DELAY 1.000000 sec for TAG B</pre> + <pre class="wirelog_delay">009 DELAY TIMEOUT on TAG B</pre> + <pre class="wirelog_tx_frame">010 TX FRAME : OPCODE=0, FIN=True, RSV=0, MASK=ac34c7f6, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> ��edited</pre> + <pre class="wirelog_tx_octets">011 TX OCTETS: 8088ac34c7f62cb4a292c540a292</pre> + <pre class="wirelog_kill_after">012 FAIL CONNECTION AFTER 1.000000 sec</pre> + <pre class="wirelog_rx_octets">013 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">014 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tx_frame">015 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=a38e00e8, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">016 TX OCTETS: 8882a38e00e8a066</pre> + <pre class="wirelog_tcp_closed_by_peer">017 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_4_3.html b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_4_3.html new file mode 100644 index 00000000..9ffeeeb2 --- /dev/null +++ b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_4_3.html @@ -0,0 +1,312 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_non_strict">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 6.4.3</b></span> : Non-Strict - <span style="font-size: 0.9em;"><b>2003</b> ms @ 2012-02-04T15:41:14Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Same as Case 6.4.1, but we send message not in 3 frames, but in 3 chops of the same message frame. +<br><br>MESSAGE PARTS:<br> +PART1 = κόσμε (cebae1bdb9cf83cebcceb5)<br> +PART2 = ���� (f4908080)<br> +PART3 = edited (656469746564)<br> +</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The first chop is accepted, we expect to timeout on the first wait. The 2nd chop should be rejected immediately (fail fast on UTF-8). If we timeout, we expect the connection is failed at least then, since the complete message payload is not valid UTF-8.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events match at least one expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'NON-STRICT': [('timeout', 'A'), ('timeout', 'B')], 'OK': [('timeout', 'A')]}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[('timeout', 'A'), ('timeout', 'B')]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>Connection was properly closed (OK)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: m117Mgw+RaqCkrbu2OKKcg== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: gsC/JxGAgNUyZuw4sE8CAkazaoY=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">False</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">False</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1000</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">None</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td><td>131</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>4</td><td>1</td><td>4</td></tr> + <tr class="stats_row"><td>6</td><td>3</td><td>18</td></tr> + <tr class="stats_row"><td>8</td><td>1</td><td>8</td></tr> + <tr class="stats_row"><td>11</td><td>1</td><td>11</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>7</td><td>242</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>1</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>0</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a206d313137</pre> + <pre class="wirelog_tx_octets"> 4d67772b526171436b726275324f4b4b63673d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a20677343</pre> + <pre class="wirelog_rx_octets"> 2f4a784741674e55795a75773473453843416b617a616f593d0d0a0d0a</pre> + <pre class="wirelog_tx_octets">002 TX OCTETS: 0195fffcbd31</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 31465c8c46333eff433208</pre> + <pre class="wirelog_delay">004 DELAY 1.000000 sec for TAG A</pre> + <pre class="wirelog_delay">005 DELAY TIMEOUT on TAG A</pre> + <pre class="wirelog_tx_octets">006 TX OCTETS: c56f7c3d</pre> + <pre class="wirelog_delay">007 DELAY 1.000000 sec for TAG B</pre> + <pre class="wirelog_delay">008 DELAY TIMEOUT on TAG B</pre> + <pre class="wirelog_tx_octets">009 TX OCTETS: 549b95c9549b</pre> + <pre class="wirelog_tx_frame">010 TX FRAME : OPCODE=0, FIN=True, RSV=0, MASK=3da3495d, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_octets">011 TX OCTETS: 80803da3495d</pre> + <pre class="wirelog_kill_after">012 FAIL CONNECTION AFTER 1.000000 sec</pre> + <pre class="wirelog_rx_octets">013 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">014 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tx_frame">015 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=c041f88e, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">016 TX OCTETS: 8882c041f88ec3a9</pre> + <pre class="wirelog_tcp_closed_by_peer">017 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_4_4.html b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_4_4.html new file mode 100644 index 00000000..e5344a36 --- /dev/null +++ b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_4_4.html @@ -0,0 +1,312 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_non_strict">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 6.4.4</b></span> : Non-Strict - <span style="font-size: 0.9em;"><b>2003</b> ms @ 2012-02-04T15:41:16Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Same as Case 6.4.2, but we send message not in 3 frames, but in 3 chops of the same message frame. +<br><br>MESSAGE PARTS:<br> +PART1 = κόσμε� (cebae1bdb9cf83cebcceb5f4)<br> +PART2 = � (90)<br> +PART3 = ()<br> +</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The first chop is accepted, we expect to timeout on the first wait. The 2nd chop should be rejected immediately (fail fast on UTF-8). If we timeout, we expect the connection is failed at least then, since the complete message payload is not valid UTF-8.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events match at least one expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'NON-STRICT': [('timeout', 'A'), ('timeout', 'B')], 'OK': [('timeout', 'A')]}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[('timeout', 'A'), ('timeout', 'B')]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>Connection was properly closed (OK)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: XEU6Kj9j1CXrVXerZqPZug== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: m+kDAJTxrTpxSNSgA+UFnbv9xr8=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">False</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">False</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1000</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">None</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td><td>131</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>1</td><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>6</td><td>2</td><td>12</td></tr> + <tr class="stats_row"><td>8</td><td>2</td><td>16</td></tr> + <tr class="stats_row"><td>12</td><td>1</td><td>12</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>7</td><td>242</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>1</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>0</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a2058455536</pre> + <pre class="wirelog_tx_octets"> 4b6a396a31435872565865725a71505a75673d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a206d2b6b</pre> + <pre class="wirelog_rx_octets"> 44414a547872547078534e5367412b55466e6276397872383d0d0a0d0a</pre> + <pre class="wirelog_tx_octets">002 TX OCTETS: 019564bd3c28</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: aa07dd95dd72bfe6d87389dc</pre> + <pre class="wirelog_delay">004 DELAY 1.000000 sec for TAG A</pre> + <pre class="wirelog_delay">005 DELAY TIMEOUT on TAG A</pre> + <pre class="wirelog_tx_octets">006 TX OCTETS: f4</pre> + <pre class="wirelog_delay">007 DELAY 1.000000 sec for TAG B</pre> + <pre class="wirelog_delay">008 DELAY TIMEOUT on TAG B</pre> + <pre class="wirelog_tx_octets">009 TX OCTETS: 3dbc4d00d4484d00</pre> + <pre class="wirelog_tx_frame">010 TX FRAME : OPCODE=0, FIN=True, RSV=0, MASK=3a12500f, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_octets">011 TX OCTETS: 80803a12500f</pre> + <pre class="wirelog_kill_after">012 FAIL CONNECTION AFTER 1.000000 sec</pre> + <pre class="wirelog_rx_octets">013 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">014 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tx_frame">015 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=b230610d, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">016 TX OCTETS: 8882b230610db1d8</pre> + <pre class="wirelog_tcp_closed_by_peer">017 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_5_1.html b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_5_1.html new file mode 100644 index 00000000..a75d90b7 --- /dev/null +++ b/cb-tools/java-source/autobahn reports/servers/tootallnate_websocket_case_6_5_1.html @@ -0,0 +1,303 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_ok">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 6.5.1</b></span> : Pass - <span style="font-size: 0.9em;"><b>2</b> ms @ 2012-02-04T15:41:18Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>κόσμε<br>cebae1bdb9cf83cebcceb5</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events match at least one expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'OK': [('message', '\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5', False)]}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[('message', '\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5', False)]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>Connection was properly closed (OK)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: Ntl0ifPncXw3kcnMyqYI6A== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: EOTzOwYD8bhNnxS8IX/Rcx+8Ess=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">True</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">False</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1000</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">None</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>13</td><td>1</td><td>13</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>144</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>8</td><td>1</td><td>8</td></tr> + <tr class="stats_row"><td>17</td><td>1</td><td>17</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>226</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a204e746c30</pre> + <pre class="wirelog_tx_octets"> 6966506e635877336b636e4d7971594936413d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a20454f54</pre> + <pre class="wirelog_rx_octets"> 7a4f7759443862684e6e78533849582f5263782b384573733d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=a3eccd2d, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> κόσμε</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 818ba3eccd2d6d562c901a234ee31f2278</pre> + <pre class="wirelog_kill_after">004 FAIL CONNECTION AFTER 0.500000 sec</pre> + <pre class="wirelog_rx_octets">005 RX OCTETS: 810bcebae1bdb9cf83cebcceb5</pre> + <pre class="wirelog_rx_frame">006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_rx_frame"> κόσμε</pre> + <pre class="wirelog_tx_frame">007 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=77c74274, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">008 TX OCTETS: 888277c74274742f</pre> + <pre class="wirelog_rx_octets">009 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">010 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tcp_closed_by_peer">011 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/build.gradle b/cb-tools/java-source/build.gradle new file mode 100644 index 00000000..a87d144f --- /dev/null +++ b/cb-tools/java-source/build.gradle @@ -0,0 +1,44 @@ +apply plugin: 'java' +apply plugin: 'idea' +apply plugin: 'maven' + + +repositories { + mavenLocal() + mavenCentral() +} + +group = 'org.java_websocket' +version = '1.2.1-SNAPSHOT' +sourceCompatibility = 1.6 +targetCompatibility = 1.6 + +configurations { + deployerJars +} + +configure(install.repositories.mavenInstaller) { + pom.version = project.version + pom.groupId = "org.java_websocket" + pom.artifactId = 'Java-WebSocket' +} + +dependencies { + deployerJars "org.apache.maven.wagon:wagon-webdav:1.0-beta-2" +} + + +//deploy to maven repository +//uploadArchives { +// repositories.mavenDeployer { +// configuration = configurations.deployerJars +// repository(url: repositoryUrl) { +// authentication(userName: repositoryUsername, password: repositoryPassword) +// } +// } +//} + +task wrapper(type: Wrapper) { + gradleVersion = '1.4' +} + diff --git a/cb-tools/java-source/build.xml b/cb-tools/java-source/build.xml new file mode 100644 index 00000000..3d33c274 --- /dev/null +++ b/cb-tools/java-source/build.xml @@ -0,0 +1,29 @@ +<project default="all"> + + <target name="all" depends="doc,jar" /> + + <target name="compile"> + <mkdir dir="build/classes" /> + <mkdir dir="build/examples" /> + <javac includeantruntime="false" debug="on" srcdir="src/main/java" + destdir="build/classes" target="1.5" /> + <javac includeantruntime="false" srcdir="src/main/example/" + classpath="build/classes" destdir="build/examples" /> + </target> + + <target name="jar" depends="compile"> + <jar destfile="dist/java_websocket.jar"> + <fileset dir="build/classes" includes="**/*.class" /> + </jar> + </target> + + <target name="doc"> + <delete dir="doc" /> + <javadoc sourcepath="src/main/java" destdir="doc" /> + </target> + + <target name="clean"> + <delete dir="build" /> + </target> + +</project> diff --git a/cb-tools/java-source/dist/java_websocket.jar b/cb-tools/java-source/dist/java_websocket.jar new file mode 100644 index 0000000000000000000000000000000000000000..bb5caeb4e80a53b60a83868128ac5fde26d7ac64 GIT binary patch literal 88934 zcmafbV{~RwlWuI=9ox2zH@0mXopfy59oshE*zDL&I__AvXTF*5&di;;bJpJfs?Mra ztM+s1IaRxq<iH`|KtNzXKvGeSML_<;0}BEHq9CCr!XN{XWcr){0a5xlQ5cYvzoHT@ z;A}d7#V!ACjKBMzq6#7a8A%CMHAV%=dxfb<c{v8gc|<t|+L@`@CKcu-w%sF_DKRu> zdTIF?X?2h&Pzr`W@?IS|(JJV2DynX|v?Axz4+#5+Q|`$Kh#2he(P+u8DwsL<(QtRs zXmUSZIlMc(I{s-32#D-|F&pBqEsidhO#f~z2#DN&kwE`92^(V%V?$4K6IVx5TXVO6 zD!}~fEB7t1SN<S^fOHUofYAQ;3aYB|s{g7gW@YT)U~b3CXliHd>YA!$ZKSs1{&{7^ zP8Lg)Eo_op&kR<aC=4277fe1EmWYa$)(_`E@Miso97>XH@yP<DBR03jetk$Gd&#+= z;-Hw*25lXW0M=AxLqV6%IjhDx_m}l~^H~z(RZ_FUIwi?Y$Mtlf@kqkA&TGb#ya&FM zy!lBXpGO{$jiq^%tHBbKyn!R)yqY2A+OZpJTz|u`lOQj_5V&sQXd#rr*|D2&l6TZk ztuSmcelHn-5M9%3s8U$E`Qr=iJ8Um85WDJz!cRM_4&t^<T`RP1Vt@8<AwT>Eu2&<H z?HOMC(;TL5|6=f<F6dbj@{>2@!ApU&k7hK0Eapoi%yB%mFS(}=o8ePL`cv6rCv7jk z@@DoKJMp7V-HY&x64(bn9x%4`m9od@^3>J%WGM3P3DXvZZ!r8db|XOY+-r5)H+cDV z`1&<@2hIAfB>p8GR;5Qf_S5WUTxkS<f(hX?R8!5n1g_jjTD@G>MS}#l(u_ICMp>Fc zLoUe1T8RO^(k7JFmNzpCS5Xn5(<(Z>>wI6lh80h_?qi{%5x>1%v)cyHMGaNBa^}U1 zZIM8`m0oYH@v5xJ4?j~IBx?DcJRc$PdY5vR<AjlRtdA4JOGBg{Ct0>{L2IuIw*eo= z7j?grj&&F3v~tDB3L}2--C{zmT)J4b=TV-u1SMvqCv&zS@50qWJTCLL9n*DP5<L-J zqjE(*Nns7vR<fV~rv$T!w8Dzizds9Abfb`h%X)Cp-kEOsn7g5MetzKdS&iKxRtvye zig?i4RZ`!H!Eu(xl@({YM%sTC&3o)Mm8GQgG}jns#h-Pqg5%hkr$h0wqZN{MZ6WWA zj;`iDPF#mrQZ~x)R)1QkB#;r2(a@#9M{Dc2n9qyl4gm`5Td82S={bSt*{mq>3U=*K zmMSvpLO@$pf2^Y~wZ)bc`l={ZUHk|+pVW+wN_5F}fIh==4>6p^5m{-YQt%mDF9PVL zF6twVw<0!QqqN6*UQeSDBvLBZx7w6vH8lc0)R)Z}guEe&{0f-hOoppbkXWqh5MQXc z{iy>`D?Ye-NL47IhMl*jWIEzIv#yF4&SX{nHDt^M`;io*y)!iRthOP|ON8Cz?Wr6x zGk!zr{$2IVONJhSEz>O$IjgLLTzk9mJ%vzVKD$abV*)0Hhf}qgKCg7b9380$OS<%u zMkYuCtqj$6-Hd3c%*zNswcxCxCXu@hQGh7}iz_l<M-xAUh9todZ_^(GXXJyvIG#ZP z2a$sj9fPh$2XjmRQ}Thbg@~A~i;FUI%zOi%%F}e<yN^BYcLCg7lNeu<kltUd6=!+J zN#1nZGnn~CLoJfV_9MzkQoNC!RFqoIIa?;CKLQo30@4Z#7b(vePP81uO#7#0c`2jQ ze>0H8^Mc<xce)ock(AJ)xQ8Wc=)HN3HBydjf@quz6Uj?wB6U!a@kY{;OJ4b_PjDU} z9oP^8sO@No?vl8blt-Aca?`pW6xU$~2hvINcZ+LKg_Wo3rkcsn?xRet*b`gHe)X1{ z*<W(BSXPa(p-9k{+aRb{X*7Bnp71a`eY@f^GTHnQDg*9m8uK}6?$KtS&2}f6*q~#+ zJI!rVzHAeVU>jUHqx4{tTi~FA2(=w-$nJzAg}@&Xj?j&ajPE3#{SelP8;@fXY=cl4 z8@5g!JzW_|>=le>Qf^TIAKXZm0aJ-3)>6q3wjPCy??o2}p`Kl^9pZ_bF#yD^u|Ds2 zYHe+F@cnU6C~`6_F2?JS3pkI7nr4rgWhZBt#JX?Awdq<n-eoWu<O{u?Rc`H+7}~0u zR^dZj1{~Z1GPf!n;43!}TjVY|)xysaDjCHtQFnf)7U(|Uc%@M8g6~Jt5U{D$>4-_6 zOelLEi~Mf846!d4oY4GmIpOyo?XQaje~d))jKH=y+7)ZuRLouaLH(HRHPtV5(;m2! zv`BSR9O&wD3E;p{gGS^i4hud{D7aD~3&T?nM|2n8ng&ItVc#Ch#!@p4^I;>}V@p-_ z^LlTe>3FpTrL=mwqP3$ZHI9)GuvVb|sq1P_I5F%rsi;<r@vFxfnM9qM%+qx-R)aBM z@kck9`aJhH;Yk@pDnk8)%sG57w+{~+b67eUm{Hk>3(Ij|N1r3}h0-@|9U2aH^Abt6 z?-IFXu8}h)w}Zb_e8kHPee|)C%b&cO{LK)U>~JgVwkV=aDe3mCX|$X_v=ck>!lAYb zc@3OXU%xNhfvB*0U`S2Y!tS=3U>TekSK^&tUw2)P&8<p5709z_Y56@|u;sEtws9zU zpU0Ql^_n_3a^pr*-W#IqC7f~6w!moi2j&-_8uo>@x;lQT_?U@MC1Y0)5|!RcW*K<n zE*`vgtj(*%DBipCPO-Y06!x1N3>{S%sKZ6`7t(Bvo6+OVJ9%ue;C&a)!X_eQEg?F* z*2rM-z%1R6Moc+>rA>mRIzNW?C7CocLj+^53(zT+`*7kd+41!rJD{4@auao}UFBkC zXYTCli$`3TyV-kt^hAD$y}A};W_E)nBgs4-U{1(n*T;_6#^KzCAq?JhHQVX^MBQ(x z3ozT+z0o@6I!27p%7F)pu(`+FW~KoOx~+|b+cH%bz#CW}oa9SY_b2jHYW0#1O0q>w z><3*6TR9THXX$ll%^I$G4uqq|6vDk0K<}o<i3F_S3hxoke1E2?-PzCGx%j?w{(a5q zl*t$A<7lzA&raw^N!4NQp%13FgmX?e!07>RcJif%%MX@zlNEUfo;o{NXp=Sk&gp^x zY>&YeCamt~zSS-o?FkB80NfFIHC*p^q7kjC0HSD}lLoJJq3*1E3jW7%CX+J?`fRBO zoEOHvpX%|>cB7<mZAzyA`aK&+hhdw|F!$9^W?t?KQL4wE*uE@3h_bfjT;G^$9IYi# zrNHLcSk%rkGfo%ToON&=Yedk56nI)aKm+>ja~P6sTUljPN0Q-}INfmVPG<|Za#yGk zTJ<Dr2663nLwdI1wcP6;ST!co1{gf>nr;j;dR}Sks&l2bB$o1l4vfF14@y}sK?NL7 zwJv-KqHB#l=FhX4jDh!89z_XuOU3E+*7W@G)80Px)2#Nq;pbqUQFN2dbW*4MyGZ-y zLN;gB*-lhh&LA5s5nDKgY{>J8MS6t4B0BSH8%}c|=G~&Kd?K`bxCkGK_D_n2W>nou z+j-<YIZSyLxMeJtUHsw7&idcx#~f}zFK^%tQ2fIJT8drH0yyQqI8}}a0-D@vh_>mk zWPWlk)LLAE0>BuqX!*liuH=2oUn=MwD!mmLHJN9?4xvQ7*U<qM+VXZo8`d^}XFlqj zk5a1l6e_>)i_bJ920fE~2b3$@6(7rZ!;dgN*Wgsu5)B|TKpjm>F2TOAz9vKikV}}y zi)mF5{!Qy=oFT8_jCk%Fu1tX=2E%c~(dllq7Mbv_VFL5UInccMg_+a$A-e-Zr#jFV zZz0?9=d7y>zCcIn(#6Rwe9C^hAyQtm_Cf^uQ^I{V2Jg#S=^SRQFVw~fPQ1^D{bTRN zUCb=8#wbPE7$qDSIDYXg_TpsApL<@s?np|960l3Pf@nB1VTt)Q?#ubL@zy}xFFCiw zc!Jiwo}5(Y^ghS#2*Rhk=D5hl1A5w$td!gpp80(Xam%fjpt<gJy`53fS7zJkH*IKt z3ER2dyKx((aKm5H+Q6>s6}&@`szb~8vVkW7L?XR%(2|`6ZBM8p_Y)?Q6YZt;PaC1& zO1k!kQ;N;U?k?A^lL39phSl6HLC)w((w3mq!c9-X3rBt25NL_ZnYGp65`u39vEK?~ z;}=Gn<a>AeW&JWz|FHPnlC|f;Zbip8!0-<g3ypSXn$P&<qy+@+zM16-5ly`wh`h?Y zH{@u1*#rvt-SJJ{kERO*5Uf~ewpmTW*=@T+MY~j<yjf~mf8`8RhH`rT5}>?dm|W{k z#R?9sgu>Ro%Co4*6yrM9e$O*5=$cyD*T!m~t47VY`0mAMd%_L&{IgXCCAHzX-O`zn zC-t5F&w@E$pUOEcx#<1rm^JF$TmJkeV*1EI#UWMk36(BcW?fqLX6}{ct+^XbJN;8m zM`>plw67~qYuLgMY~0IqoXpumqI@wr1B=KPzr1%{{+Ab2Uv}GZ<61x7@J&zi`XkcL zeDR3<JXe_4mz1Ly?qcgc>FnMVO`Y>b3VTTZTxs;3o2Ld#+cK&Gl|N+>+huS$56k=- zscRr=jZOr*H!9RGJ^C>;#=kUi0_#$ZUX@7$NK`-lpE9t-Tlx}CR-(l@@)yy)8o31@ zF|*!vl>Kt(qL{!A;zSjC)&G3j(fD1_bbnnIJ_Ydk_HA_aEin9+-%rrpg9F`b^c9HT znxOBBx7_o`?{mR|>w14Y5x`(9?gh^IamCA(NB9aQs$Vn4?^x71<Sa84=~8L53lp_R z@a5ANTs_@E*w0kHjoV>v90k(-g!kUN{B}q}mms0Bo8_~Y{6*mn*EgiDWF%MK>efKw zVEweR@|@pq5jVRl?CY)V`;$^h^^--dEy==(2zqpzP^n4Nu#QHEiBHV*fk$HSPyDG$ z?em-NOTGNdT0oj<Fx=17oy$rvK6RKb^;8WHq0klOqaE{1_#2XP;I*?x?LD=rFqPa1 zs=W~vUprZf+DyCtQoI8u=mWc@f6$TY8j^WgDU=4&=nX%TdH*fDM_PlN7oHrGRo`*Y z<`47E*C}hU;}=Gm>toZMcet4=eT5@KW$4G+PnLftoB!aBmnF-L@PBzI;a^tG_&;#R z|41BV6s67WoXlPR!5?eX6%_F`F$89b*j9<aNMH>k!3zM)t&#Wh?6BnQp&?-@@s!2w z<n~1k-u>jj7!IQJW)-Yd(g$oe5p4A(M10OvTG~#lMGMr5fS+Hbm)@oa#AcXkn8ltu zqdiYKzT3GcLZ7cM*dQmj)L}*3-^38G_uC`dmVc`tT<^LNIO{0`m#Nc%&M~`LaTmOj z%g0?d?FFLSswAf8UV6h@YQAl5@~2*gKhLs`v_8v)xjDF*e#Ul1tNe7Ia5<DWZz1k^ zvluuAyrt-}UwSV#Ep@h5ywGWA6AK*qOm>VOpBxu)Xj69$Q!UI+Lt6L^yAfC`&TOZ7 z$oP=O+!l9G*C%ADfbxvGxSAS1yHqze58X><-Zd&IaCYc4N^OT=R&`8@_#s-E-GIu` zsHH)~aX_HJ)FirmeF}s5Ad39-{H6xVLW9!v&`d4Yd&Fx_*an%UUa?p!RWkLZ=Rk%$ zOP_kUzugFpbV`Rpli}|yv1@T2Ft3XNxlh|(tEoy-9-lPTfmugQMZe4_fatql4aV$* zKnO+YEHw%Z1u&ZEZ`PT@$xx>Pw>c5p<ZwcE<-NjxhWWihhNy<g{hgQu;ZVIdUi}p^ z^%Il`?-}AAN>Cj-zd(%GeeM^Q6@2?@X;V%%1Am%CKseEw!ptw<;5%%4Yt~(98z)W8 z-+wIk4I!~lZ8l|t?S$>eTV=t%zv<Hop`IlfOwrCtcX%;9lfHo5FlKK>1+<aUb=R6< zmhJ0$)SmJVN1e!w&c?1ynet2qV!u3IUrSg}(B#7qY*l!r%gUqH-HrLCDejzRH7;LM zHfn6#A6Aa1PiP}WRO-mpPK##M+_hh(9hnf*QE`Wo`LQhLLlvw?nNn%;=!oFUd_mlT zQ<1ni+re?VyScJgBApI6uTyU@i!fonbJR%kC2WgQ=PNjp5e!me>j<Ly1X@|ZC*D|? z5O%hLGIVPz1|(GsJaG-Kw1<LRau0>nKv<dHJ%VuRD=6%2|9Ap<)e42N`0$F()R8`e zNP;<VtEpux=K7%)yM2$>W0q^63huLJe+uT?;hvyNh1xk*8s2p9t{IxYIn1Dal2+|1 zm$YHjU(*EI#?GqlV3NW)n0&n`ROpSb9GD)VU}<=ucFZ-j!jkA6`8+kpi4f>6&SQ<b zD$FR>87?|z**5o@TWA>Th(2~b{^u$GA$H}|bUA!wKGz`QMW89xBd|adKLXKKtgf@g z5VQCZEEkSV_?@%8;h5P`VS`*1*>E3sO%SLVSVQX`zW#%2eh?42sh80v|L3b^xo}$Q zu@hE0PuxC%!!Fnd)17liNO(IKv}UF|j^YdWJHg6DCFLrl_DFatXJ93ah1d|P_-h!C zZ{(Mxx(LMZ>88+sh81fae^+KO5D>Ay&_ea!!-}T4$v=Kck*he`ng0V%VpXpl7mQH+ z4Roke;%-f6SZ<+_sF)$k(1e7s5C%d@*3fuaqQA*kVDiajr?7r8jG|exXMKN+XWiaN zgxqJfPVO?e&O6Dw@csBWd1C_EC?6&^Q5_M(pfDp(F{fVLKUT(&W2N|ZA0>-Hb5(_w z$1JX6Q`Y0zPwb(;VWz|zXX?YC`z>|~6&QBz6MUZNU)}d+_b63+JN~E2ajF-KACtQj zK(5oZVe5%oEP7q?+F8SbvCSC9|Lc;)v00m*Aq++L%6FloS|_f9i|A3|TKL-aNJY5i zD{xOj`G{U!*Aq&8`ib6BiHSixPeWg<&9~Fid4{Ip^i(YQ<dcNr197t9oiJ>f&c{Pm zK=kb!Ll#r1Hn`N&C=m;+L|z0Zf-hX{w9&?l*Q%EAZzT704$t4X$8^z?@_JZm+$thj zOd^s6(_XZDxMH7u#c;oF^akuVGqWhjF^dz(AH72njDa46HeD+8)8#wY-!j7#7hG02 ziXttAH0$jjs%GqZt$R8Ye5>Xf;iu_Wpb`|F^Yu;+R3ya_=-NSfQKPPsPty58-=gMt zLYOdEHp%f}U0^E>#XNKj#i+gTXNBnl#GVOAp+k;AF3eJ+SvN%)#}-#eyYSw_!M1*= zZBOKqxVRwd4eHktJ_Tj*7Z!5~sW-|c-&Ea!?;Go&&7cTK(8HLq!0hkkW5A;8)QC9; zwR^yMxbWo4$Mmzh%O&JQBjHm+e1i6zU^eGalgv+jLjLEVqmCF^lmQ0;QHKHnA^l&1 z?%(1~&0Y~*1SPK|RFsAk2Bw#+95Pi^Sy`lnDSU~Y!5UujL%6l4gn+>F(BWcomeh}V ze(2k7JgZ=rz<Lzo?d0^#6k8M9?9^r5&Q1W@yqFJunSm^#Qp&=4a-=e+q(#gx90|ER zG8_t*TDtcbV6ZSm%&Tl>%^%85`q6SO+fuHb-S@zG@o{<c9mjOKil~lU8Z0=A(j>Zm zo|M~7VHZsY=Q0w~_B5Q_YjJF@sh~zSN$k&wB&M`P(GDM+*gTI+?W4$_L)9H=>r<Mo zTu~)ajrO}i=o?)wFGDl_KR+=yT8`)>6Aw4BvZlPSvIu52YN^?gDhKOsDs6n(ub11K zhQI5c6|m5H+B-QhT)()R#s}<g<V$ee*u072U5mF=D^Bi~9D9M@ppWYr3?PJexfjuY z->6}_uubNnNK4vcjb!3*rnaxQINVg+Y^b46-{f!LM28q`!HK2nI*D(a6NzCC9!EJ@ z&rAHImFR3}BJ&eAZH{wE{<cVKBTS)x{{kwTf1$IEl+zBJBZgN$+X(aYo|~d}tH{!; z9^#4FO261_3DWP4FP`nB+!6rA!5zhN;3M9uC#0iC-{4fnZX`$Ir^ym8qo2eM%#w0p zv_oEVd44|{<sU(<IPs=sHip&$XfBaw4LYof+$yybgL%j+;lE{Dg0{XTQeNB>j$1)# zdRPyuZ1>L*Z}Jt>9sgQsJXEpLnUngGorX<kHh9GISEydI>6&TLuo5Xzn4{>-`3V|D zWtD&yQ=8M`zWV$~iBaWy8ae+*8?|Zr%eS`$hXY{{6JUhF|6(wffNR|15jj8oNXRpa z>V8%H?sEVlb^*fhu>xo--HeI>96+8AUvI>h&T|65D_zHE@Pd${mQ;m-bF-eOWOGt{ zf8ggvdH*<{q7%MKoF)^#VvuH-;P24Y(~Gi>ZNE4U#jzjK_Bu<8k|>Ah18L?V=G{9* zFd#SF*izUMl*hDdb_zFyw?R4KGMCai<`%+p?h7ujgj#*w3@x>Y#U`P>8a6VC1{$T~ zTTW!C^TWnUlmy|A?v;9fnlosgmQcQ`SDo>7;uj7is`HlSjj>3?{ZZX=_>NH$yZ1*& zwK2+Rf&Q@|XZ3vP+kZZYsm(*QuYZvr5*Y-9=6^YeB4)-;ZvV_DHFZ5V#8EzfIg^dH zV<X7O&_pI1I5F1xo+QEBCzo;@{_1RIx2I`r_DC5^qN9<y@8`KOd<N|gc17X|2eJu8 z0e??u=t(tf4hC~nZDqdnX6ELmetn+A3xVnlIG}wqk{NJ8V<FK@iZE?KM!r#siIU+T zY=a}Bnh!65$b)&w&G5?_!pn<XIf3m)x$}kmn>G9tHb2efqz7i3pN_`~SVYXO@mg|s zSZVorSLy7}khPdC$otIyQoC<B$Jy^;!F~FLn7%hyc&T=Wf8P4)5}vN7yK!jn3Co0e zblO_`i~mZVC8w>1YWm@=vZU+coHAgH!@<t=qgD5ChSL+rf865_7iMcEpUs-<{k>=i zZ8g{Joonep4fuOS!_2v0mGH+*n(;YnR=b5R7EsBu5n+^^D=blZ8zBm4Xz!ff%+#Zg zf-v4y$6;`fu;GH4LYjQZXY_nanDm`JwoUdZg}OZixP|9@oJi89%VstxN{?0$e53KX zas#pF27ij?sox^HR3|YIk=k9Mlj=9`icm$WPqSS5{k0y9a1g6F6|YQ3_f>Ho!uhna z>~G39KngtEz+ua=A>p!IH~Df9ucsqGa#46B_`5WIIvhGJw=nROujggxo3^j1doTcJ zcGezFL((SQgiB76tQVC}k=4D}2CBrv_qO@S+kcm<v(7Lj@Jln$vFZH=<a$Mk^)t_> z`WNW1)ipP@LB645UNK};cJ<n1hqX>RaggRT)Hm90t#!qj@@+Z%a6@+^$qdB_AS~{) z&*asZ-*#c|HHxSz?)&I%Cy!A%9R&fVUdgIjlzK<UxaZ1&7Sim}R0E}r!L#9}Fe>M8 z0Buy|^Iy0Yr5vLkTR9Gz>cEL*vkA*G^~GjSm&(%l`I%J0uB=;33&~L_wmb!&xRaTf zQlkMtL6|074u+nWiAD6@Z!1b6zFg${j}=84JJawzJ?s^R%LCAd)CH&&erGB-W<7Sj zc-_G}n+QB)hjhPzbqxiv{IYk*4Vf3Dn#6iw!H+1DLj0q9I*vZBKb+7NK@u1z<zxB) zL!#T|)FWSnj@qC2+u}zZg+)HSlapJhjylnef*UmPOB(tPXA21#RU9^3yv|c$dSKzD zNP<kSXUvn1wa2aJOXSpt1$Mv*(m61JZ7nJwn^fIGsw(RA;O*fP5yxiJnkW?-?CfJ1 zj^I?7M|6D>Z%WrQ%)*}sS-`0(r$iEfLqKs+H;<gm+s+U9Hy7T}4XS|e(IE{2CxSGn z<qeC>evG2D98O(QOtSoMvPU;$fxgx?WZ5@o!!lzgNim$X3s^H|Q7jYoDA&^gZ@f|K zORx{d{Y5B%$jm<TfGYzCJ%|w~NV0>eOUkeoem^%Q9*E(59TkF+3YgjZFcL_O1686e z`IB@T7wMKkVQyq%w|syAY0k7HZ&#G<!kRnSV=m@VTo`v3Rj&RMwpi}gkY#L)KY-x- z9Meq+LI9eovYjT`8o*StF_(g>w+V3pOP9Ys{&{g+fIxTVBW{~FB~p(t5<H}NNvEh! zYqs1YUp2GOiY6ft#GY6fzn@`~@<NbclyC|a3F!C3*9_%ojM%RY!LtFSn4j{VvpZgp zmwx<@uX{1&Ik&-RBrl*;ao(2jx-Gwe5SC*%8%ha?Nd0Oy_(9z&B-O~UYde00Z`~0A zH%>k+^ds)BW*`9c{{ra0lz^d<og7#H0w>Mi6#9PyP)TD`H%Axme<U<~4~{E>P*6}} zP(&V3j2=)9l2D(Ag-b(8OE*i!yYGj;m(V1`ToCi$3*A1dhZ={T@0N;9>gxsd0!?L2 zL}Y9dIXER~BqFUThF=+pC)T0KEApW+!mOHq4cXRmkcr_Xat0$ZGK^2nKaJB<j*`yo zPW|Z~N?IsXlMDki(zlSDe&=J|9LCHs)Je%XPs;>%%b?a9VPNSX?<-e9E>2Dq!ObU8 zDv3b=Gn#ZoyI*+tNN!X~mRrTa#lg+M5&jVwni!ZF7#avF-vl<iCR~W{TNn#EV;J_I zHFTJNt&m6mfjg!DTkO^p2LYk{U%oFg_D**H@J$OZtd`o!@`)al1rAYF5UGu%L?|f5 zRTLd5?L=vL1my%XzmZ$UK&A*qq(Y2L))uA0hSkD|UDB`?2P%0fi}iX&iS>eo<0Ivn zV{55(6G!$J&r^>#TR*YYMtpqNj_a)dOpjyU>)t>(s8P^!1+J#<o=A0EsHU?5eakNw zX8=K7^u<*BHk?H@OA!#RL2EJ?!1ha9WI%_lQbT&Ta~n=Z%QiH;KJAIqX?`p;IvP>W zj=g9DF1Q6!LC>rG=B{FTbKNYo=ky>=2n&4GWMo<cC$a@=bMQeU&*A8oHx~a+5O#($ zKJrIIG<=QGXf%C$?g>-tK2mpxOlL~;B+g-chdU3pX2z{7o&54mdlZ-9^e~5>-sCWV z(SJ~`E;=;e;KBJ>wR<2+2$+9A#N@Rv34gWk!_{<9%H53{Fg{#Y{lg2sZXH;oJMEPX z>39<A>GTB%YhH=5*U$>{bav)K-T@BxblZHxe#&(2gAA}A0Jq?`8W7|66}tB84+_uh z_JH@e86>m#-5iAe?GQZP<rW3IZx0bU`!)meTO>HPAYL^1ZJUK3JyO;UBjaI3U-a<K z!2!Q#PN2|04OPbj#{H4?)#pIWtkzAu#%k0oYw^kRl92PoES6v1Z9pJb_ZX0gr9FN6 zISSKb7oPvF67p+svCjFH_uC6po%dplqxOwGonGkd-7a7EW8J1ty6$!6?RPr8*{DaZ z?(v|nQ8udm8*n{rxf*0`(r+xw9P$^V7Vkqr0d0fRc=d`e)d5;JY;@HlmJH8|8qoo_ z4hw6unWaoPA57X=hPUf=-oJM?Uq^y=bZ+^&S7UZ`Z=fCWn&D8SlSNT%NTcA<6(X#v zB2X=w>YWH}u`~>DEV1B3mn2&-2Q(T78rBj|oW1XJVv*vd+~4kL)!WVy+Pm{gY%b(O zaQap{lo5%^FKm~E4N3?KWtw{7_o`tMj@zJ?L~F!s8Mf2<8?jm>B*vDo+933<_{&xf z>~3Gw6p0;svv)iB5nV7ZBWm0@ya)oZzS(E4#WOUCa^K9~>z-ez?@aN;luZ_WtFgO% z<*e+*j1^o~w#0&kiBz|#kyJ{}DdZRqX{?*Oym?a>`gZxiamaG|pu40KOX%&t+@}3- zgzHOZF4Tli&&!*MqjcivW~T<<qcH8jAuPR%7lKi?aiNI~O+^=p7eY=tUF6)CP<A1$ zr29U!&64QEj6~=B0$wRagxxlOu(hwf>$(ID^H9uOdrPXrT(L1%EuC<2_{k3|$;zw~ zM;vqN$`Lev;$CHuC|z^3B#PrMd&uUEYLAbil${0NllLPUVs+o})Rn!W2gN|qmHZEX zN9hcz6LUVe!ZZ|@_Pe;hjd&<=D-P>zMYfKb5GtqO0?$Zew`f~5gjv6(8=JO(A3g0C zSo)6gg@yI-6zOkpSCllf2St-D$R*%dqbBM%_#XG6=pnRpDF$2XN{Wx8d+A8oZa}zi zD)3~#SI`QMy))uDu}u7DhFfCnLJgT{`O2BB>3$20wZ3s1C-fdyF0S>5a&yE|-Q~6S z_knmAfl`3G>=n{XR5?Sp<or)-GMHb5HGmdv*=_nHX+}I4F+QJg36F2;8!C#rdY?Xb zOtCil!RKzz<9bP`XrkN1yGBCJ9){%R9$O+{66>5i3!KaSJawy0YkMBc48%YdYGeJI zicgCyDi~FOJz`c9ytZ!iq&^j*90{2nLIcUuk`V$(C+ujLeeS#v9s<f<2r+J<lQ}5@ zg6hEjk8@Xu?illLt=qA@>j_5kuN(sz0_RwL7yKSa!!W2EUGTMltp_P_E*8D6i6w?1 zNri%9U$S5fz(PhtnM(+(*4WYS0FLi*1u!R1KC3d)jO~QY^{l%^55MPVf_$>4Nkg&H zxNP}G9wt{TY$h{jL(-I}P(pkN7tcN#Uh=keC2??($1lw=pN7(7=j5rL2@!uoA1#QI zsgWW?HCxBBJLaz+nkPzFVonB9FUwsC(#pLqSDVz9O1Lq9$Blv5)f6bUtI$qV=7e0T zano|E!HO$v`ZaT6+~6T7>Z5^E2iM~uaF+D8K&nVG70K<4r^guKXVXW*?9m|KOqAC| z)>wN2=%^E1r9kE6ea{-KN~%#IwJkQ(8}o07csD5selS*Ms{Qjv-!$q^`$cw-Lwb{Y z&!lRZ14Ruh>ko)Vdqd)>NX2+*-8vqTIe6BJ+t|I@8pVKhc=Ck|20?7P>mSuu-eCDb zEyX4nfcM{oo%jZh74<!Q<=bg@u(0lx8xk}1>{dQvK#cF+@JBWZ`g)n;8`ViX#}5M? zwQ+`$TIoVovw2XBI_Z6SqERKztR6g8^XoYl!(8QoM=AYn=YdHn;qUdKDV54{sq~<A z7U38Wjg4eH{p_$o--FW-FqW2;-^nzPkZVuG3o@b@@0;0DJtj8GHzeU>ndTM_m9@X0 zQ$cTP1KRQ!b?AB+1j{<-D4IGMwb#9%M6s&{cWG`I{VF3sZ?eGZoNoEMjj+Hm6Qh!4 z0!3MwZe9p#=)KkhG03GJLA^dgth-%ONEe2Fe$oqdeTyztxSTDdf#U}cP}|jJ%sbep z@X`)!+anCAj(8H8NRX-O+=RpQLhvu^cV_&A2+$ZZI-X(i5=TAb)<zZEjkicFh$t|@ zflRu}D%>E2ty(*APs(Y+VA0Ddfx%C6vW1}e?$LYcm!#8w(X!l$X9rqWGjrtV!FL*z zyF{2rDhX#|PI(#TIc&wcZx5Gd+@T$#JfuZXWyJ4S+K+ux0v13KMbnp2y^EH%w~JC8 zw7Xn1c5?rU;f21fB93L6!tgt447DQ)hiZh5G+u`rvW#}$?)euzCACRmh8dRlBZf%u z;*cApCD0P)rx6sA`Vc~*BNiJbOSCqd?v(P79*ZI>B!Y7<b#cy>8$T}1Hdg-1qLD2E zM3(&n0}-x020`4=0A!&wD*P~MNMzYJf6RuAafT5W33^mHOl4BpNQ|5!BUHxX4F9k* zvdAg1JJXLQ4j!->Nu*6O@Yu>};-q3-%PLQAf-8;e(JH2kXqK%cSr<9=L~gPP2vjwa zG{|dHPL0N+Vtvkhot=Hn4-$SYlVaf)%RE2Y!lcahplJDqBptnKG<BHF57-NgIu0UK zAusy;dVPjU%m%1M6v-z#WAz~&=UVM*)gd3{A{gjWnnDe3OyyCARGk~%?t%y(D4q&K zl~gAndDi^CavP_r9SMK#2zYQ9YgcPSl$8-3op@vXo;0j|+UP~1+;@Yv>5GeoNvx33 zjd2^i(CAd35K_*Ftg*_uWS6ZLYA?=jJjH=b4ROK=>7401#|x**Ly~(M;>pB;nDu0j zcO+zDS3fiE+M&X``d8)_#E-EheE<bOAnycCwtaQk@FkJ0>3Bo3%WCHct^|9u0$kjH zo&&0Z=_<>rZ|_B_H~6xRaQuo*u5*s|^GofN6z(|+3WG(1HmWYO6sqo?u0(UxJ$2TD zuz}<q^OSv9XVS=<JrbADYRkHf`Z90{0%q%e7&2;=(qFtAh9Pa~WeG8>-B#BgaF#TZ zEHZ9J8!cghEICxG9Z$+=R?;PHr#Uoi5>Cp9nohgVO4KF(p3&UXK5$^pRJ&-2m7cuo zGaE5x$3xQC;sjR2gLyoTxi8z)E{m$!bl)kdSG7y3(KoD@ViKwSTdiL7og9YOgs5=W z#lZODB324@#rZdmPo=wsvw(j4>~kWzv3UNquZTZ-SM((CneFpWqnFC*VtZlI7+rK) z9WCio<{Zb#5w>xFva0J3QMZ($+BeMF?zCko!IdG5Gz{$28sZ*GhGM^Y5r-d56Q__8 z_nM67c)PkEpe>OgKDuBd8)T?@B6#ROPwRNIo&~n%H|d#T4@mX#d=Wygz#4kz&cx34 zeWFU{k!Eh6{j;Yv73A2==kWTb-5?p#(ziiT=Zan|t~#tshR>`#xb>$<qJavqsb*hB z2fIY^m8CvyRhf0Pd+<TY%F|QHo7-0|J;HV_&zxw05=c5YY;1-If)t-_@T7Vg)bzG7 zIPc}6_x&ZN2-H<TnWe;Pp?)?#p<o{#%!u8ccLPJ<>)C6zs}6ZXBZj=LUz|nnQ(f*3 z{s30`r}VH<Bbksv$sYm=tVEZz@iwSrDGhxlflfiW6M}N1-*P41F+3cG&FS76X;v;g z?}*eCyRKgE4pPML4NW)f4yQdrpNKBpYjik3NdM^#;-#Ezfp_^lDAu05%3T#xmyUC_ zvpZev!NWMgKjqihr(Zb<pWTB=pV&5vX7uKC?y>NGlrKTTp&qHQMQXm!h{`@d>$~?D z=F7UU5NbKMr0nxTzqmyoo74Etr+2eosvI15sI?!4jb{Nr$3(i@asx@fR?2mj86F{v zE@{AYNGqHm^?y8f)T{J%Np<NfFrJ-8kqu`<0F}>>Q%x0deFjSi<t!oZ!@I|>xy!uJ zxv<kxG_led7V;wDy8I4~9IXz;$PfXSZv)g#C%u`8bu-_87ZR9BsC%o6xHB0>y$UX= zk<iE^sJ6morqlC+_e8yG*;P_sJBOsl*JGun*HAO+YWFrKHTNGGp6>JcuCl(64-#C0 zrzW(z`z|ukG0}-eP$l>oY6)v&m)VlNPLOE0OB5-+<*<~*+xPgl5tA23U`!uD_CdzR zB^Xpk{v6g$d=OYz2u$pSAttwlp8X4Ly&79l7Eezded!REW<ye4jI-_`vx7-N6T=sD z6LL}TRVz$PI<1Z6NoY6ijdz$Av1-hfBFlmYN){ZnF7nCOuO#^=g20n^Howj`iMye9 zx_A`Hqc3$PdUsEW`EyJiFugr1cVEKS(Nq!b9+{!@bl&D9G_4no6AVD3txpb`WPYN) z{CVGe=y%HKOhY}5`tHs?PW(Oa@2d>*(Uj}9-S4o9tEu9Q3Bd%aoru<kn6&!uS{jrs z0-G;Z`DC(RWRVZ=zZ0$WZa6^Pj*!1z=RPLIbaKx<U$_%4X8Q;pbz$y*`x|Z>L-4~O zg9@_}ze5qF)tKJEEW^YPI+}m6JQF_nE82s`tA2T<`-Ad>2K;>V^Uq0tX!7~UsaAc? z)UW+cWWfB`BmKoRFxmJ4*G+k`vWKYM2kSWgYVQS2Y|Sj>ry4+h`5k+^==Q1Ni6i3_ zd6(Z8Nf3;?7ChtwMVt^S`2#e<pCDxu5FxS(S@m0LK+pahWYRd|xT6R~cMJ3vAOc;f z1d*B@<(puFFnHu1xD0zjHDYt%aGqQ}(!!bY4HxaDQAuN@EVA>c9KU^-b3N?w59Emf zWDb;{ebaMrx_e)TK9M{*KafGpF2wdxCVoi`k&)<rOU5YNMYo|FA#hbV*x8}r5-dsT zxYgAeXHH+`BOV6m;q(s+GpbyA;ViV{i09zQyA^*6BPp;56F6Z;px8z<-9EZocMdmx z7V(Bt<-**r*p$3U7p0$121#WGnF$=20|D+zB|u^ilDET@0zER|v4LQ#pu<iwI3;oj zvgtS}jEM`_jU~!aH^|RZ3ANfYm*dvqO&)=%Aqk!E^3ceE_Sh+eL8wy3<OM>s6itMZ zi&-p}%$yW*j#N^)?cnq@YxW5q7{8a<Q^dYH_?JNstdu9bXxB;V2xln!!97HB1j@iE zn?=x8&^;4>bOgXqUP5K}5K>-;xmA|_4#PmqZ!cm_jm!6-AEHozuBEv89U|V2q_XFe z^oBT8l%FX14%XTq>jb(9(bhkm#Ac269m?{tUr6Qx^;5zgeK~*e{ci#@5})A}Ve!0= zJLvi9Tz{C-Dnip1Nm+WqIrhtktzFX<;%>6}bE?u=$5aC|+MU<a#Y{2(xRbq&(887O zk~^mD#G^l_(K+1c$^gf>Y@px~kU_NzgFz7ZLa1b=_$p{F!iOPoDJD21uF-}KZQGS^ zWP2783iDFxrb<O`0^gmg8=V*u^D=*C7hLK=UCNfORwAX^!Y)K9aI8$dh8X#HSdOeJ zTeMz4Z)4~)MW&SG2#5!8wTX4aDOZR`jPe!?LcCz1x%gKL2(%KWxhhv&9$_|9ZY?0u zHQ#G^Bv47mTRZ@`JcU!O4e`ZKMw@f>s*G#LimE8|`AKg{@kn>gO}aB;n*W@lSU5xI z52o6#K!Bq9hFyl@d$$0*G*4}V=VoC=468)SzC`>6J6>VY$uHNfsH)jO7l}+2<Biu% z2d%koV6w)2RJ@?pkT_`Ke%@iXi0Ji>=K-XbTLbi&~#a9EQ&D@xVSjio+tWn?&1 z73`VSvc&Nek7aAv5C-yWNK4=~>p2(lpqu^q$}g1+G0$hL!ow=dzcXW?COZ?;7b)G7 zqz%|S!;Ct`nmR{T_hH|XMjm?(h8K!vpJbhw_h4L@)$9*9ZU?rFzj|~l{(8H$*X|>e zbYYN$?W%SauLx<ws>QM$fL%fHtHl*uMiX2?$hQrf<p7>!?YU0hbXYvA$vx|xL@cA_ zxgoWK{z}l^5dmBRT#oITA+AWnow?*ge2pkK;qQ%vI;ITKbD@ne2}rAne&}!`#LfX` zih>EwQQP5oVJE{Q&_~BgiAch4sXqju6qT9%(}D&kF`{+~=iWg>58|!<<lnISfd>eO zShC>eQ85aITi;L-AO?&y+<?u5VK<-{44Qc1rVrW<U1~Of8H~z$fv@yupN6z4hkDM# zU+-ageQN{mI6>$1)9rzG2R^%@`^B^kQhD+DMZN8^Z6bdSlHKqKLK*I|-B9$x81Cub zaP)%j?7}`{^@UvD_&$pa6%j|~9U5juqDGitNJiSRM-s%Kmf%p)GHcLFRcXZnhji42 zcyO3<GKle5l)ac`o>{c@BEW}YGiV)#MfQp0v7h!(Zc!{E%ADKXY8{h&V@nbS8P!qB z7I*1vBv6H<RIOgH3M0_RZwUR68-}v|o<RXYq1@82*tc|HkLpD~4}tOopo4r-BSfLO zR)rk%<aJ%lc&7d0-I;Wr84+}uE>g|X^CY7v*o+I7ia(;jrrEo4h*|j2MSRg1Vxn*Q z>0>15GKP4_FRz*7m?<>BAt3JKs+n17QM8_7ghIl8e*cjM5dpx9rm(+nDoIA<Ogom< z)Z+xW4z*k5T!rhuP~ysjhe|9U9%8LxE?EU8et(p7IXIL;4)kT>PAU@2#7l1cLBQ%= zQ>xFLBA~9?OR=>8n*yg8yBC)3k#Y7M*fmyFGE$rkYoxs~ewlS`EeMgls(c60@l9K5 z9FoH?%8}`aaiM2%?UUMyPkRa&c(aE^lqbD4J!)JTcR;e*h`k>_`0G677ZUe14s!l% zpi}_^p_2$ceaVRN>L%1Zu+5AGhiH|uf7R3!x+~Mec|@EqDQ>XQ=5bc&{0Dq|%g5X{ zRE0Sjp~<m?vJsV~mhTPm3A-2LuLM|Io<0Auf<$?h`uXC&9tB9Yfi(TZXfmc)H%2_} z=bZ}mA%IrkCgL<D(u+XfWzB(DIZ$hdEEB;8go9@wKOM+(M>Xwo<6f$Aha-3~;-2z* zg(EyeFcJsT1tqs9q*$?(CQMHBt4s~D=7bz^<I~l{q|cAR=>@BY9PnrbSIq}5pOz*n zv}1kus%6jinPABxhD2vhoH9nl6+x>oN2n%?NoEP%)T9|X0AbCuDzv6WD)ifekb%ga zCV?YNhzXW?DAIxrK9^%)Xo?(0%wTNVFapp?Il;sdf31tFnilfuSj6&a$9n)%b)s5X z$#~AMeWj~drAt~jWOBff@pRm8a}(rnFXPS;WDOXt!{s)NEU-!Dw^JsID+i=~p*ULG zM0_L{o_vY(7I2e)kFG;1&hdP^Du~mgm^D_&V3gp`>wX|WYe>7R2^I;d4el0aAW!I% zALbn+&bJ2o-~`4p=e>W*|LG^#l=dG)l&go;Q0jxrE`a(@?jyu*3LohYZwIWY2iM(m z;YY3=xZl&+v>=~zrOA8;n9_NF84b3p!a<wc;=oEJNQWJ0zlQEQ<VH3HXR@g#Ra&|V zQRa`;#B3VpI(f5uE)b>5a5mh-RXpGI&M|ZiS4A$__6PBi5P1TLyEEh7DGqn*j7Yb^ z7M+lJ8g+qppKDN!O^9Ze7h^7yl25!Yq3?3AgqJ1F@#Ex=W%M|*cW?zpM+(xB0^2n4 z_15sQWF~(Xg^@;OBDXAbx1j+uh)uH{Qy_;Zy03(E^y9=W`uek*>H9<vGi}Cc*tI-7 zLmYgZhvl07P-EA4@l{~cje!f;TZF=s2&lv)8ozLs6U?sz@Fb$kB+a5lAXH+-fndCo zWtesb%^_FU{UJ>5Jg!GYJq~}vDZjB*k-L=yZ?hL)XvEPULtM~9N8y2RpzsJj@j(eK z>ln-7eDlUSlnSST`7F<j|8wPeN)_L3mZ=ABcnra+JZy1f%=tRfDlRWmC<kOdF@sl9 z%qh*-NEvXZ1^;Ayyr?8(E56neA?9uW%=ji52B%vZr&}DSTOOx-0Ul~?1d$=^xOTyb z*syj_Nw_7UGg}P1#G}2IPt}mhyBigXSI@JFhTt#bdQRu!Jrz)E5^>(eug`9}x}dT6 z`lAJ)+WgUPY+%r=;u?J069Zf*v8PfDA)*OGG3#%W(cBIcf2T`)Ve~G4G6L&$%&_AW zmn$QZz_E}noACm$-{eSN`4H^k`lRe8-VR6+!WobC87y#2*VyXRPH}{V9pMQrI-L7d z8n4`^<}V&Kc4-Oz<Ubc{%#9zL_9GbS(gQEk%aO)u5(Ny7zm7Mx&oaZ@B><iw2OadE z-w>WzSk1o3rXE$?GJ*m~DqMS$t&D_JGl80Bv`b=62f&o^Z$bLdL0cQdx76HK9c!Rn z@e<R&3}C|+`{!hqde5+wT%I+Cvl&rWJ?;oQ=LngVaFLxuCQgRlnW(e;VZm?dNMCG8 zUvx8Kb_V!mbE0pcsZ0Gkgn=Ki!HL|6B`zW*0|~Mdc!P#$IYU{vDH2u?TXnvxDh$<0 z4V!wocauoZ%MCwq9QM)+cNLn@AC*QRVN0Zp-Q6c@2%WN9<4#1eZR28r)O)y&LnACX zk#KAop^!-u1PJJn31m?HUK1bp)KYmQ3qQpXSf$6w@Qp_Yi&OB5Q}*R^FeN8!lBRzf z^}@GZNcI_=uM9GObbh4NiNX$O79h13joQwkG*<AnSPBWKg{dXo6QP(%lY&PLGi#AZ z0(n^|<r!A+%j?oLk<3D9Oc%WZLLEkmT+9r%7ag9GwvtfY8g#9pN%6yA_;M&*jEcuN ze!Ig|OzC+yv4pQ(P=iM*r;zu~4I5ZPz$5tPDt?8U)#3T%pLg~ONX;g0Jc@Q6f+KE* zx<U7Jp<fxhYZ2)=UmH}w4PfTh$Cpvb1m<8;CcLqe{O00j_=Xm<63i*RMC9RO-bvYH zy$S)^i59LM!Hti>5{Sc`xMYQf5kXTgEnhPE$i7N$Au2re11k|3J5oDLr)949np(`F z)sAbmF_+QBF{A>MhT%(A%M!@BWj~#u7K2o@|Km2wrJfKew0UIcx`r3~D*n8H*;U5) zSR{@5hqH;GJa(ut92-#B1}Y1siywsa3u!K5JxgkG*UzDC>X4cXFt;xvzCwu=+R=(Y zR-?7~EuqT?R2vP|tO3*|4$0*w6st%VtYlaVJ;E<x@Ly=)8hJ?3`GYE5js}>mJnO67 z7;=B6v0Y!K9?X3C`0L>diyoB^m^tJ(Gvs5f9OMfmr+RBUsYNZvF#t<R=&qvG<-z0y zqEY4HZu20xg^BF*keMcA@)%dlew6snDR|Ez5obRhuZHZRq~&qbq*lAXgxjXG$sB6? z&0mjcUXQs=4sJY7y&&S;pq>43BDlea?zV;9Bte{&#Zgk*NrRXdI~{x3!C<lUtPig| zc*OFYK=@aYq=H}lh2%}-fGFg|ieXj7mQ_JzM>AGlDnme3_BUF7h=9y=q_5IPuKNXN z;&$fL^BwRg*W;3}|I*EweVYuw1_}a_0r@{(F#cyZr@XbRo4Lb38+z2X0O%qZg2BQq zcXhjcCi|g1#-!qoJ`^dmu*E|P?6+YEU1b}A=3!F^5{!QM`GfF8{o$lJxW8LlQz4tN zKX}<r{Ddq$-Y$Nh(13IY4x{nY4Y)$pHNOURX^g&|3c{tXrmc=D;uP7L-fc@-ty|%| zd-YT(OgxF}{wDA|=W&<Z({x|8HW6RcHn?YQ{<%DVV4Eqvyj-Wj$kgYQnqEgVPA{Tr zu4M3qbx@G3vxe49E$>M(x%-B=@UivY(yd9Ck4ul#go|puVsg-@^QX{NpitMUMmljo zHZewMFH?yoJ|~eDQSG{RsigS0iYl(QYOQ$VE_P9X2rs-G$L>RQhToVp2L_H!0M0hE zHRaYmaA?G6tnNeYV#LaIc{h+IfE7B(F&QivnG0r{eg8+6cWyoY3$nvyN12M-gZ%2R z))_OuL8A`gYF2JAsN^BrVjv#TTn278Jt+y(k@UAroD0^X0j5LC>;+ymt2Do+00pMK zv!w+{Hw6xlC+5->6K`p%<1bJ@u@(u+WV~3){4Z{BD6(B4^dba@0qUAjt0U(e4_T)! z#}lNKH;=192nK!tR8G*@4>o3eC!{OLZsdDVZ2tprFPs}xeo9U##ZzC1ygcy#+pq91 zm)hO^(mNpk2FQ=Uo9=%o$h#OjIhmV@db^qb+a2_OROQW;CS`%b7y@mv3f}}P)iDN2 z<{St*Jv5c6)!?D%+Y_x_bX9@m@+!Ik)1lvXr6RdvA%=&K@_dO1ECl<ug+Tr|9~NGk z><ohzV3vAL4Ge|hqE~KhlNB^f)NQO{O@SA9_dN2953Uw7^15JG`2GSda4^TbO1`ox zFl=Epu7+~NMNVtN-I&~-8!jmB_Okxo3rp5Oq*x6=Lvq<zZsH6pisqN&j=$79y0IQ! zWPdc|R{fd|c`T(}u&5nbgnYx@tG0WB*R1>$=f&4Q+-vtk-D9_0nUGe@lAO4gEYTuE zp=DKn;Ta|2UPTiwAHZzQYih(FWDiKeY7L;_W9<r}TYZE2cW?f`0|4;;dvBWBS(`ij z$2kE1(lwH*&=sBfx9yJL?<@e}|F537xrMR2o!j530RKF<V*mQa|I|z}tYNRZA&KIT zL@+t#AZjK11B#hOsSgrTbceZw1WJbrAWbJKyTF-x1jNE*O+5s;bUY2PwnRH+XS;0k zC>5$)6wn@nu)BE{<0GmAwE509b$ND`^?kiudV(x6n{A2!M$GBQPl1ggn7AbLPjgdc zqJETQ@m;J}1;l&32)M}P)e{kW1t|H^C5A8svRcVE(u1Z@YtHgLHXVMm<~X!=*0U`F z!~3<O_feHtF7EniBtuW_Gt6ge)Eq0!m^$SvcG<eMjq(#&x-IGsLR@Zu$31T#>n3E_ zp1AGmAus4jCB=+Wg^|{~h<(@CB}a|bg1litrR{O21^)h4K0*g3&^Uuh?Xr>7DgW}W zP<6SF%xQ}~gh|d%wJMWwp8r^4`TtP%PC=qX(UxG<E!(zj+qP}nb<4J0w`|+CZQHgz z_2RuZ)7>#K-4l_SKba>o;>0>T_t|Uhl}bn4QxV09ksoUwt8E3fPG;<@x{ZWUr`1(g zhm`3i4v7X;F=gtaDsvZ!G5L;(7u5<)*LU7{Ym1fj`R&aH$qjGDi7bDgb*>8%cA{MI zeQ+5`$`oC+<9n5`3pdKv3ys|0m3YDAM!Kz&EvST3QeK?9iS%(G<jf<TGpuOg>c?-5 z^4EBQL4S9LBS)^yX$oV@tU6*Eh;V!u{h{yu*7wqzpU*L=FwqA2G+?96gY>S|c+jbG zZT+=Nrb*Gi78jYaf#*CBLfs4gH@;oy&wpbe_m?3)ij(g&<Dh`o3NTHioUB+0J{J|- zu5O)E{HK11w}cm){wjr85SXI7{^Sf$L&{VwTW8gv8w!z4U+5|Qk?3DQ@9tk=fX1gC zWP@J8I?85~(OsSt>CoONP8J{-u?MjmnvMO{-Vfc<fR~~N!cxZGk?JNOb%Q;rG7*)I zj=j2nkYBO}laJT~H3&*GOuB40l`&%ORiU<&?nso<Z`#7R|1s}GvG=7Cp_XIV;81eH zmSuS@J^^^z`m~Ib4~b{00oO!rd99Y5N*!+&glQ~1N6{@$>vT3ApwAL#?Xi$uW2Ifa z4r+A;)zr}u=z!(q98k_bs#^F2ZCw`>$=I`hi66Lt=(yS1%^O78Q>g2zsKGG!Xt`Qw zuu@?Lwsldh4DxBwd34f(_b1ul%|$P$Y2cj|5JK}_S16iK)e}di;vq@L>%|`<@X!)w zTLa)Jd#uq9J$(<>ZNFueSW|)~6bxB0U({n8q!$ISW0vfl42=A+-$|5V;}KH|J(c}| zpm;#buYq4@9}MPGJWB;HVWcBq5f6bUb&u}EDz=+IxOYh+KVZgu5I=Bf^>bb%Lm@(t zLyI1<&&m$?2Gh>PtCH;EXJ_*A8`U<aN9N?8p&G?H>w}+?3Jv?lzSVoYC4(O#KDyM2 zb!v`5uphpI9p;&4*O2wF2urjHSQZ`2AXYr|U7uhO3i)0v_zWryLdhp1A!v@D-ee!d zg1N5+OTQCWa%u2mh6G{b@lN7hH{+fs=WvV^2kEQCW8@RDSAciMBl>3>xXY8X>m9ob zggNqOE41)q)XAgzZ+vNkk2M^Gg1)HYpr@Y{8Bm8~6dU5LDE-ZY=xyB$R$svG(=4Aw z%=QMv`=o<2=FT%cYTX`=odVv)E92(F|6qOcA0h7taU}TaS0tzO+rGx|{~7ZBmq16% z@SmTbER~D@NKn2*Qf##(DS+=H0s<cYP=o76f-6gut3;8&7lg0bhPpbe*|@eO@CMiw zgiGd!-?hM*a5vDPNZO#SUtUhPdF^m8olJgyKYWq-xf=_D8`4I>ACqiC4upa*P=`2R z{UzQ^wwLSohJZu=BdgIsc8spbu|PT2F%EXL#Y?wZ{GQEKUv9N&e`upS+7S@NC6~C7 zU}`?iIAHyW>=RA{>N`yB7~-F4dW*Ky^{0h3IY2fcFSCsDJ;l(%a2x(z#0^?M%orw6 z3qz;QMm7QCrV;4@`PR?UVQ5bVWx!`+Gy}9kq-fusYHPaGoPsNch7YyIS(e-+aL{GM zIiHAjs2@2*LhjI5%NYFz6KHKN9XcC4*kHscrP^$1&Y7cR%}j!F8M2Sw(M!xKc|b?U zyiVsleV4H>_+E2NKC@f|9B(Em1uc8QY`evdB3W7^?|l)2^#BHVDru>GHswM4q`V+) zm`T@){xm`%;UT67qsW<cZrH5q_!hK$RWI3~5{vOkYON7xPKu0OT#bQdzK&Xm-A<Jz zxyH(nEkx_xMD<m1gquNmt8wgw<L|hsLcb!gcbM>OY$&pHiOSfuinBorb%+?EDxvxi z=79MK^BFv+h?3ISmy}~ct<*$s?LJPfNq2Q=jKxx^<xh#Y+py{F(2&MUu~TwH!r}1R zG`gVgIZBt69n$L3bkPugS!f0`ik(`ui`Cpts7NG3d$6Fhb7-F-g94{B$J^PNSW+R- zV^bLJJJ6o-IhFc708fs^av*v%U_Br!y|~ff7pxKZ2|jHgLZ%(Qb>7NQ)VJS_%RSWU z<0Rl`j;6eB8~Iqf36O}FPOpd>-VlklD^d79r07{SGParoA^Z3hy9Y?<4fseiSd7&O z`*3<1#1{Kmb%(<a{u(eT7DjhrOD}YBH^D8GqLr;(>@8?Ur@-c5QUx4gOwa@taRu_- zJN=d6r2g_2LfWu_M~FD8@E01X1t<JF)PJWL|EZ#6eL6)0`%TzpeoxW=Uu)-oSxf&@ z(h#j6BlVllWvUr17pq)=eFO5WMn<0K4(bz-Kf?DFSf_|d!*q~@FMqPYeFOL=wMvi( zmhBq2b8*=ncNy^gM@FZ;FRzbZOI9m~P7T!ro|D9oiF7@4b9}BMk*HCo7F!g>VWwlU zyy?iJao5IJ7F9vWbViJ6H><sZ^3y5{-ZzzTEMlYp8HF8lC6(u%ek9HAvrF9ko>oCW zv#N|1ozV-pH;!6nJ$sOlSZ@>Geid((-oV1G(q1nj!~PbDE^6CCIH{uHD1rVpoKE6| zmTKQ_;Bm72hTSBH#*^+s0CC`JcZ~$j^S60(B~I+N_A2fs?xfs)Gp6HZcBHxjF#Q`q zsOJaff1YaN>l!`SU+1Za-xgcC|36dxpFyff6T(Y*X^D?4Z8AezR}eJ;ez{iSA8<eH zp8#TiM2S_B0M$(5SYv%gBr}2_%_`3-E40+vc2yahvIxOHh60fwR#rpHMOB)Ahng&x zmY=Q8el}l@O&Fnxy^?n}J$H1bU1vNFlz!Ze@epK=eJ!~sWhMu<oJnHiidSXS?rxo< zL?xLd)92=+;!P`=OU${aWjIquN8Bv9F;m+qGA0g<0)~;cl12t}v}wPH(=w^2kS&TW zxu=E}wwV8sS#S@?47e3DHvoT>{rTIRA$!6xKbhRNV9YF+{$|UYg(j{<AXAi4;g%PL z3gK5Xc03f2uX%iI?iG?j>d$h?DRV$luU+6!m63YS_@;hceAX!_O2wi}@?{x4v*;F$ zT{Dqe@kSlJQvATlyD@cgy8<%XHeWfj`>>kP_q2&GQ!pIzh_%`HFC%Ey6yt5PGoEd% zM2}YsSb+Z^cd~JO@~xhLxS)189$IriA)3u<lN6aY)ig44W6K%P&gG7cxw}{9LZo~r z>08i()^R~IoA}U2Dm`)m(4!LGCp?N4B`OsnbE2qC5MSqP->&1863tg^TJI|Vg1LKG z#$e8mUhiW6qOgZI&~EpJl9uIL7u4qH#_AL3%nte7F5%eJ-zSn8$I)Vg`8&_+Q#@Tu zM6$)gt0UuPmy4xa>HyEmi%m%&OQDBywRS8_`Q2L-zkYNB9wKBt?eUzy7hL7NYp)i; zl(2Zwd~_s9rD3jPO$(Qv@W`yXGJD|A931qIbb)0+Ua_Nw){g07D9WfKmYa)I1)gV9 zh$&s~dzz@GzE_{46|J`IoPm$wUsbKqjde@r^-xh_4&^yq29KCRTSk6Q2gcoy9rLi) z0DlruF4p$`b1rm4Opnd2K6qD7qA7xbjnO2l>jzIDnWKs&Y}Ex!p=}t1Ju-+7OB!um zk}Uh3C?-+xd2IcF2vM)^drdp4<$1NY^gr9;%*OOGH(i`;us7x6qiE*oPM4G1y|YK8 z{I1b#^Rtu8tgz6knE83|w5^?t=rF?N0XBe5Fe95V-}<YyutkkF`sL!o(t-1__rTs5 ziQ2vZtaeeQvf{$H)%U?&Pd-3`L01V*)pWa&lSbndXI0V%EBslMfzctq@1jo;)jE%! zwdA1Ohhug7vg#zr@zP3+5pRIIr@rFrBeaTZS_20pT1Wy$eD4&^V*us9H0T@qBi`!a zuQr>}^t_DjDf5GM>th)cw`<KvD2cyDa)3m$8QesvG$m%#gco5;L3mjw><3rdP4&47 zn`wy~v8&r!)Mu`OnSaMaPg)c%omPIcb_I?Fcq;2NR<}r%9FDF<*mbp_x%8^bPs7n6 zXk+y#+BtjMK&4L^FaL3_ZxS_$;NVy-YD761FgzTG0Gn|SfkD$3Hg`aia1CsfN1jPw zWYpb;pj+gTHbv%Y{P72Y2sxrz^e{$k{5f2LTfQhPw=TbnvzZHbxYkV-M!lJC9=9hO zdN&O)4*k)N6_dvD04_8AQWfUIwfEe#W@n{!70Vo&&65q|&a5rkLR<01uKi{kcO4Ke zYZp)rBcB=C2os%63#PlP7z#H>ns=K&a4%HOpD(t=)I8H1e>1G62(2#W{WJvT`3PK? zpyp3vh<^HJMrE5f2%<v=Qv&=}O(5o_r8~WGI)wBLhOIfP=3qrrxyG>eG3e{KeM41Q zfz*2d^?Eu5sF7X@z@zA3o83CJlRA2sxZljp71eO5g?#cpR-jDPw!Cruk{qDlu|E3h zLWZZltc>f?vqxLBj^K~+#>=!sNYf4iEfS(uIR1J!<vxnhBn;@eVXIZ%{5B~;<zY~n zIN~f}{T>-_(1v9=!M`~=V2Ji2Zuu@gb-1nA0uPM}Krs0Yw88IiP3%lTWmiV9-GqJc za%P>|M_%%BUe$;T<VYAwuK^v8fj)k_Stucbv6go-UXZpCM-bPDx*^q#Ad^y1_&yj+ z712nwfT2;|T5=|TPK}f!B*Vx=u@*O)Q79~gj}6%rG*S^)M~p=Hq;=E~r*SLE5F>58 z!6#$gY5iX1iZO|?^SMA3%ILbLzDb(^z>S1P{a(e;{)7V^_Gk4M&eFqHHn!gjYrJZ# z9Wd~UcG!e8)K*2noP1#2C>h0UFR+6QGCw)n`>5cuTz27t+?j#}Zh~ov?8*WRr*~?1 z$->JS80VHa+nF=dR=yILO`zG8STi*LX4yjQnQhiJL6Th2SI_g9<0~2GmQ)sgkC@^T zxS@>hPk_8qBglt!U!VLRg`S>OELznll#l+QD$&v*v~cw3A*_JB{ReC0W07n%Z0Oew zF~}CVQ)jBJ;)N^cP+7i0Jc1z((1QCshtpv)d2Q^<9q<WoG*2N>2Yhr2Sd6(^1JdHP z@S-^Szd6&A`<`>RGlgriAI=`y!q2VL*zoPJvx%5L+9w}xfLVBlIIA;o7N50~my7Ob zf6q3jI$Yknu-_oIz@E<fn(<YY)Fo3>iO1&K{Gif6@%R)L++wF=Z*{Y_3tNJm_1|H> zP-7m95gD>@GKZNDf!ov-!$xKXoxeeD7To#xK+P7_c&hFWIDZnhmtOs*<!)=7dBIIz z;f1<;mftA8qlbHz-Yhl&e`0b3!nhoca~}-a8q7?3MzeqTKbU=FPfp%Ivh)rGIDZtb zAHcgtm>+)9=pr~jB~JLD5>j7Hvh?<Pa+R;to=YDp8sTDUbH?<gff9-XID3YF73MG( zpPW4e$#k9a2Kk8<Veg-%YiafQ{xW_1wN<Oq^Ugkr1eYy&LZ2}dDE5!EcnBwddhWZy zZ+SGl_0r)^X%3x^UZ+Ovnlr!QzuyX($g}wJ$>2@Uh;SC(jJ_*|b51`g_%IrRw!@RB zbH;|u$Q+9vc3v^E_)h4Y-xZTc;Znxj9f$Ph3*^@mA+R`4?5n4NIP^|>9%R`Qe7HoS zfASvG9Loyi3a_9ryQ&b{f;M&0FzLPL&VH;;qcuX#u#HS@nTIVBPSmrsFrzA)7D9D1 za7dOA*GRL46>|j*3?!!NKGOskvF{{B;*M<|-e<YI4Ve04PY+OF27#A`=SapPN(a$e zu!Xx}x{J-pt6ym;G*TZ+@3939X6^_aOq>NFx~88;#<JJ!fyT}*+(Vp#qe%7CE&4ux zgZ`?fw-^8Og<dhrwwjilCy>#qC*Sqi9UOsaSF`$(K-Y`b8N8A}0vKcwr0m1JLE&8u zm8&90E+9BjNEMQ_4cW9VP2lN`r#yB&FVbcnndWal1J#OhW*hmjtp}NND@hGrM1f_4 z0LdNOT*bI+dTb4REr+}h7xj(*L99FK=p_pvG;hTqsza=tqQ^{lTexn+{rfo#oeG>t zjNiG3-acdU<z$_%2dmWc1yoHueh9e%aNW)ol+=|4XI28S;kk*-(gZ5jw`zR&z+zp~ znA2`|&eVt7BkF=9E2imI@)?vC?ijI?sUi<2tGrqs{AE^spjKIlG`+>s@rC_T_)>k( zp=l`@JyFy-8+COVA1Rba3a03RB*}!c`ab1F-JnG#-qZU4#7y9_3+0|TE&<72knv6E zh_nWRp0+Hpxvap3O<O?jkzcVX(;*ykbBWH@$$~WF&Fo1I^CDHeM|b?+RTKrGKPd}# z!0E=ezs(GV)Q6!*eZ-aLWt2pl@b^uWM&dTnqJ@Q*GXs(3NuDHV_(O4zf*6IJ*mUtd z?cnb8|H!Y2rG_e6!Ado}mGe!J8{r1g<v}cjDuIEjS*b<+efi;%&*hZuL8C=i(F7%N zsNf4b!$WY0&LrC-{&fqbB@vR~BX}tLmx~dUdMpN%i*b~CC<m2`ofUc@E0v4!fT;g1 zztefmLsUNSRF+fRL@<F%s0Yy_s(UGUWNO^FYl_2Ol}I|H*pljO&QNTpa0%u7c51Y3 zIwi?EKl6!#oSb?D;3?%dn}>y;fjksK9c2PwMETYGYYuN6iU&2vGp`rsqGWtno6~xz z3na$V|B`~&!f>sbAg!>KO&;wOQv)5N6w##7Qt8eXoAbtQ&k5bjb24ci<W#oY00);% z?AZ;Gj#6+6!@A=@6F@JiIQh~TC&z!4`h4WddTBRAg0mqy%;RQr%1`+M9p{?;BFd6v znJ6NU<#Dq_1I>A13M4T_q|6DI=B+hFBgy@~1T-a17BC^_+>r?a^LKriOgE5!a1-$| zepG*tNMDSxr{a0qC>N_MS8b<qmpz?-7sXpCmlI1m@P`eFnQ-d?f6K=rkrk1|(}R;N z;CK=a?G5(#CK7N(Rcvr{;dG5{sA17?kR4!kGq?&fLasw+L&Il7Z}JH>F7pw1C=~$o zg;xYgstd5y`{}{;p{eu0*3_}q=*)A<AZIlb=egz3P=HKT(V~S*T;@uzSWl^obXxqX zVaNT>9{na8_^(IgN`0VvMr>qL$h(LVR|A>l0Rjom92H}o=T31T$ZdsuxsrCL*^xz9 zmwJHp6VKgImpDstf$V|!T?i{X1ZH{gU$)uRJrEt?W|2@AF(32E*GRK4=969LR)t65 z_=n-<0sdLU5}0k2r^@u?2l`<RIEe4L-4C+MN(|PavEzcoc#rhmS$UMB#UQ)y*4kS) zanB*o*kY2Ab|q?;=_o6b0pVlmg@VMQw@B}%#$TNi{x&6tM)b+6_N!RNd9a?+1tdlE ztr_1g3(X;C<WoE{f6>O|m%Z{BQT6MfuAt<Dhwf^+Dy5y;v&p!Hk=hn`5fny_a}CBz z2N0!o>Wy-SWu3O0OOh3>xbeR?;`p8BY1pGF(Ma;xhX7lc$MK^Y$3by&KT`i{vYvTj zvwU;g9<gb1(QL*~`c+|#Vo!yGhlHv-#nT>(myK{fAA)OJ&#Z=koadnBxf(J|T^7o+ zlvNK5;+V|DqDR{M4q!}@sATfS4mx?5cL0aDaA02uG;1>`Ev=7Z?W@M@OtRM0XtHWU zQ5mOPkvX2>=kH<LMMd?HmUOhKgpg@XbexA-7b@G$B~Iqh=oJ<O0;NnqdmguxHw-G) z6)R~ka{tI_u+iKUR<7+AHs&|dYKAAP;E35>b2PUH-UEA#%t?)7>o}Y0oXkBlrgE4k z)5I>M!={rLjF?Yd{yxv6Or{J=!QxLjslhOrCB}Ek%~_i`yfSQFw8t~hsR?H<!_+k} zGS1by=#4@EEzXDdm)0BOTJAj!9Gz~?>XEbxKTypHC#&e)<1(>3cv*Ar7{>gMfgK<Y zU}I|DRt<l~&!4aw!o^O}qDRi$ZUH0!E6^^GQM-dduGzG|d3ZN-ZI8Ty)1lyOcm&|y z(pQga?f-ZMj_)WOqh@wnzCraKIGj(QfV1fkB)<V=J|Q(<==8tXx<Exd6Tg8<#m<am z^XS-7ey}f(nWv4SLmfeL`Z-gbNOQ(DI>W$jAuB77_q0deyin0@J)$n!;;>r7*bIN| z3po4|4RD8d4iO|<Y63x^LrAbgrkU8Cv^04IqO%9gQ!sgA=QhAQL(f1Y4;(MO;=jSO z`0(oEuqi+`af<ooatL}Vb~|(fCb7Lz0zM&&>;o}*A{795&*_QN2$u~t68c2qF06X% zChFOc)-PBa*^)v%kP=wG2E0}C8wQ!1T%1A+Hw7N1VjHX`9B0n*YyR@nxZOP0Z#pf7 zGI_o|cQv|R>1x9HI_OrnM4spIKItsZ%0z#JJ_^?;^^)I_JKmX(A2KA(7+J9A2o|_u zCp$vO7{gVX2V{vIEii+h<T|QLQKx8+N^JWx(woNe6G)4B!ej<rmNU|<(nmuVz_>^r z4Jo=RB-WET02Mi88^f`1nd3NC=^PHc3<IxBB#uofk*AgfE~hc)m?@CtV4iqXSg&Xm zRZlv13C<}06N>1R*Ie5Myak4s^(1|kX)?Xh(**s2jBm0oc--WdAuCE=l4chdq7LuZ zzi<jPC;g{bg-~wBUa1m^M190npSmL(X!J5$Ufh1_g$+2m8Rzcdx~>apBxKy&FbXj{ zM`(6!>cDB^$9#%UlVl5L6Hg5}G;CcZ-hi!J7L_!x!MUMwNbPu)XE(Y6!uVc{2`G6o zXO=tysDMUVnC699?SpIb;Dy;=vLIfaJEtvi&<bg&1OCz;MteAP-@`E9<ORAlD0Qk@ zod={XMfafSk(@ffP$Qqe18{)>yNXu|PN#4u<OYOhFXJ!fhM@|`poPYS={f05YyM3p znOAQBl4F<@(RpE8BoEsTPw{E?PfCbH36AfcgO(vBKOMarAV^(X$A2fac}rQ-T|Va4 zCv0A^C1d+BKWH<0hBSI6&zYBv7Gt~)d@8SJKqn0V?H~y~UrGC}1@Ro_xlhA7s)8-E z8J08N+@~m)^DHMq#s9s=Se{Nc2oqpYKS%xt4pt=h8_RYvsC_sf-KMxI)omQNK&s=k zK1eyD#qY%xvsdfDey~K50ecPpTcHmytv;9tGEYiV2Yn#VaY~9?h%78dZ#$4u4HCW^ zxs<kmx;qo{6*Aa1-F;J3&Afn07~4%P&5@vlGAnMWB$;k9o{xtFX}eK)PX}&qYc}<E zbu8U*a_9_Ixd)^P0J}{^n2jlh>YQ^-p<_TX&OfZwVF_dh9Hkw?p3@wE2U@J1X4)bF z18Z>`9R>syCQ3XtzL0eu(UPc}MZzX2u=k$@PPQ3If@~SJ6H>4PS&)NTW^5D^sO7e7 z@2G(Ol&qk*Eg``Rwp3W)5gkGKj1VjJ7yq=HHz1lJ-b9W%_3i=+_vDp0fR`_}xAnpZ zcp4(T4)>~TIu9g=GF5E#Wxkyq(UgCOg!ezvYk4zI%JFdUK_Or`I1xZ{1mfiC5U#f2 zG}tQEpAATsx6!n3z5gyP{vVUY#*PN2&QAXWbMhaQeaivjr`9iXa`a0Wv;V)BB>!ui z@P7;^7P7W;G7&ehHFh#Lurg70xBoB8uB=}Rr3HCpAKHuMD&&xjOjvoZw00O1SOI1t z-~f1W)Mo=HV}{|)XhujKt996eo`m?YIrG=cSjKEm8)#rchn6vpm&Z=-nKqslK40%I zz}%oz2J%rWa(zt^%^@V1f`174=px!gCrStk5rh<v*2oljIc5Kh<eSU2tf0eHChnqO zY;W8=KYL4kz3k{uGn#E$D^Op!2aNtzu-lfj&cMBOz==*ct%HT=Z92SF-=OG0BzLJ7 zPQnxTT_uy+oNuVKDKtbUk6$!l4t!<+TH!iYkg?Mo-UFq`t7hSxGw3qd7*(v;VAy80 zP<s@NuP^C@L~DyA(P(a7BU^P_t!Fv<&^HPXSvXbn=_vUDi$_d#gxu=z=fR@MY&@o- z&!@(%PO_12>@b}H8?rPQIrW?<7Rk%9=SSL5iR<zuc9_1jM+VmF5prryoweB*Z6SBE zU~m$RpSIG9(pDMa&Ztn+Pk!?S#6Wb;I)MEJLJ$)aQw&2!gf2MA&<Ag!86hPs)BctY zTw3m)dk()`L_s#cp2jW5zP&fedFQruZxdcIO}fsk&qTlmoFC&a3BtufJ0T#1AAs2e z77D5KEF7AD*Uw>aj+iB@M<13DExZ97lGA4l%NdFD;>rwCd=0pzK#r$(rrkr8_>x1n zHIjTkdlWZ4v?CCP%SA0FlY{4Y7i*XN4CA_<_(z?8I0Y)btw?5A<rO$DU7Cj^9E*?` zQY>zyj_IL}Nfdu$9Q@Aw>#82Rr^&QAB7TK_d1^O+=??&R7p5-=SXvu2y_X)9dp2wo zMPugkzi3R{fiU-kegRwf3s~m=1Hk^@SXFX1aQ<(wD(%Rv%Oii&(x6JJ@ShB}F_3Nm zu+4I0<AZ|HrqjET6ES$wqgATY*eFJPv=oy%t<?{md?`#!R}G4rHrH+(W@dW&{IY#K zFDErMd;paN=pj*Nl-WcD`mzGD!67OHx<T|+2@geKqoMFf?Ya8V0`4tK<=56L=%%i% z-t_GIWFglMGy1cxHXHU8sCdv(fCHLa4xYhAKI1QZm|6Y{JPe)v{u6Lr0(qp$9-aK* z1oP(2&vpPw&Yl(DK3Zn#KlJWZWlFX%AhFTX&wTSo<xc!OQ_H_b70@3z12%UtB5V<Q zE$*Z86N@5a0Yg{10x#MN*U9yXL_-&{saiJI>3{Tt2sb+Iz<2P#7d6%zW$Y3S_F(^X ztg)JSijlhDE>C4ob>&GV7e?6!jq6w?eIBb`H}hPkl9_Z%C@XJawCfyHQLtC|sOu)p z!lPlal+2}fDc1JI<y5wZh;%OB!pDjlVk*Ee2wU6?yE6fIq!=N6dy#rpl$f5Zo_Y*2 zyulfV>|b%be`vK|%)}g|P+mNeGkZuVk`SB%78(hPr3!x>#v=@KU;F!@Xvm327Gwu{ zi|#_s|AO;6G4tMAB0^8t5Lr*Sw<mg1VA@;Y7P4L(Jc@)mezAJs7GR<~2UOIt&-W0~ z+b=;&r+aX5O>Z8hK8y?ZgMlI_%+7MKP#`NT@dwnMu39`5TnGOWtNaoxe)n&Eq0@RB z&l@(v*PqMF;tq@);fUf!!7z#XY6aD}=O?Afh&K7gC~8y6@SXpTzW-^TnkIjYwDv2v z`9lBzK=Qu_<$oEGs6(i0|NeCY4*DTv&wmzt43mbn-sIQi8(bsBgufoZpBW+&Slrwp z3U>e~n<X7%rZZkxtwdt8SZb40RuhrTHG$PqlP$R_h{Q5zv)whZ*f!%@tG!Sv;acXU zQQ_yq6kT-R0AKHG>SgP1s?!X|OXg3e=e5_KR!$dSuEOvadvKc~^nNiO-2s})gH*Y@ z2un|8x|+&Ex}}c@OU_H;D^&SQMfqDkUQfu%(q@g-D-6p+hL7^T6z^vWp4jaMrq4+1 zljI<{kJ`Y^TOEQErq50+UoT#Y0n)cPi*Mzg+-ED6lgTUNx3bMA6W;e`Xpg*S&;Eek z`>mSgbIrsp+iN)@{mH<}_^r!l6`XI0>i2f2FZmGPe$L`<j`u6t(^ts+g&xai2A<~~ z3Cz#+fN$xwZ;Aol_eAK=@~sRDX~PeN5Yx9BOn1><irnr9fh1}kJOyQ<;azF5Fj9>i zGp?qWWl<6tYLDgkXTB5%h-5ISm6zg~SWRV-2}y3c3vZcX$8tp<sLb`eaRI4-KrT;; zc!pT?tGLG|yB(}Fv5Y@%_{fy9=cIVaR3m8yFp7JG&ql3;;`Ww@WobiGhfyh?W3doE zf`CaO86&*KmE&>4f=ZT7)t6Xk%#*9gx*Xh-3pU@}93MV8=LYF*AtHlPBF*Jf9a(D) zRXnDZ+h(gdv<_qFLinlsf-O38=ttJJtH>htZL_+=1TQ%`>{908_VXb4OZ8d(zNy@C ze{C8jeF#bmzZ#+Cpe!bZRJ2nfuIHvCRCAjIrUXWTYx`d9yL=3t2_{iQMK0}PkvZ1B zYJQ$gKkEi#L4j#`+ptSY1dN#|$VIC9Q(=~g9HWVD;g)&WzB#D~5|#Ux>v%-0Nzrm= z-r_$5Jgd=!#4>0o)>R6k?3g(ocQc%(tHNGKv&KJGE3I=;GfsA+6BZNdbqFWUR`aIk z`Dx<|;J98!xL>ecP$lz!aA7J|h%g%_Te}>2)^R-0WU~tm=6Eiq+t;>`oU~EwwX~Ql zi{_i}kNEdHF~}}IaZ(FR=bI5N?Nbp{0y_tnomD~lJOwKhmzLtCFju!qf>(EZJS*8c z%F<0J*;^!4dSLcU21M5*nmIAyAtt~l?eG|^3)+sMnU>u4kOSRWpn<IlqNIkSP}mfa zvM3JzY<c&pge%a_Kfu5$sD2_+@Xy>kgNFyjd3t4!+03k}R_0AeRun3)+eHt-a3$uW zj;OX)nV5{@5`TOMMfzVBi@r#nsuHvin-t4viBuW=D}Yk4svO^;lC+4FEg+#CQksTw zew#{J-altF3E2Cwo2$OXXqsFV-vq4NIn!e{#DE>A)p&MeMrg?bCN|5InnVOfdvhmX zK-D0wqJ&4Rn@TbuTTJ)OWSe29V@@V+<Tm=DhvT`#PJdatLSv?i-6X+r+*_+hw;#7e zvTI*Td0zD%6JF;{krbYC=A<i`@gg)Tz6v(R)zS&cWV>NxyGdwSa{XI0FR{%5;{K7w z_7I`5hoRkAGnz`@Cf)G>W0fmt{!$&~h$3O}_0OV1W#mI}V#hG@CEh^a!JpJzPx3}( zOY!@(6@zEzx92FIT2e3`Gv&9FVKqNodUM&BiVnwg)ku`m#+3`?0aguoGp$=)|3gz< z(uB&^*OxFcB0Vc5Lb1nbUMIpoh9T-A%uqS8!=+8E++~gQ<=ipHdgaJxEpI{=;!Lps z)sm#r!vM#D*tsmxgGH@sj8+HJxFsgCmDt=-VR}+c4kq4>*?|Cq2}(Mxrpno(5IbrN z*OU?Rx0VZus~Llbl*h6^d?`??X{VjWMz~n!z&nil%4#T@=-tju{NduR{#$HINm&}3 z9<Cw<bLJqYY3C+fVirWILz7!w67AGHIy$#DR3#<+*dHk$_;czguE}DdRBdTC%^Qy` z6)%}M(C#cwwwIJzJ%PX>3D8LPDjh{hWq~+%M02mI(_Wa7!L&Bj{wQ7(lhfN!Ns!tq zt{zuKiiEHw#tC}5WaOxw%6nE~xFiM!8pFbTZK)lzg2QTN!`{=-?(ml=4OYB|mJE48 z2I~BoA)pcpDsI;%Ej%Zdj8?@gG9=&e_oj(_qEdUNY8W4p*&Al}LvNvVew5geO*?n7 ze2-FyGY44+=|H=%iSP(;w@|H_h&<0^FP(f@{s=ac^+mqMm2l^MRoMXp^cj07LEmtS zF`=ml*|``-=Tq4w&654?abkOvA_4j?)P1(e{JKr(IgU@}^k22VY1-tgP3>lWq>bCn zCJ)J;lPIgz6YctIIH^+z3)mF&+nlM;(%MCnP@qgX_u3BPCdxT^h?NgNBcjKvMdcq3 zL|cD1R8?f7%idGGpSI;pt%ve#C}4SY_f=J!68<Tsxh!AZA5%z0DUbh?r|1#=DnfMR z=yrN)imjNmi#6S!+k9xCzVfSQ!D*r{np|rZAAG6-(OVS9RG7#f)urx4kena;H&1bR z4;i-D+MU>Sr4t{M4XFAG5_u4wR1T3S*|e$O1vP;gV#JZG=4(pMY&<WS=u;%IWV5hU z=^n-41VKxdIx%|9g3_h{PDOrl&ShO(KtVMYkYpB-cE{kE2`iU2_Al>Rr27TrL#MFg z^uV<ET?IuN>S&HJ-|cZdITfjqwQs#l7mTc-CJ@K=*?FNS!F?#Kh{)(pc-)$8Ug~Xs z(u;I#+2kCDU$Ls-L|m=#$Nc*zDL~~(%rEOyQkzjpqnk@G(jujoP7e=SG~d)IkCu}7 zT?IEvOh$~TCSE%I4#C4fWJIS_Qzh>dG_E|ZLQuu1I`?TU{LRy_>ZsErv*!kzPyPia z4>Q#)zMK+mW<F_BfalmGe-7y{Z_7o()bFonn3X>(4iCcYIa(yCd{iK2diDaOQKU#{ zXUr!h8FNB59hcrgL^hD>{&D1lk1rq(D$*jTfpF%~=MAN|BY$EEBK$2)HNBksY9q37 zFW?$zfC*#==?zqLQK?rex0Uh<JQs9URbRaCz|FVRZblXRP#DDBt;qtlTA9f`6^V(s zZB0KwTOH4WVV}v)E)s@D!~UlBaMI?DHB<x~u_}@029i@dx&}(_fOAx43=gRSTfM`D zw#y1&Z7r(*eACj~&^+_~URLYhqj;4REtcm*r_B!VrT1z|Ddc70$scxPshQ+WYS;q8 z!{x_#&i?~<PyS(#hTD&dd!Wl5rDJZm5hXSop!)4GcTBFEp#m$x-B$yLP`GM@o{wa) znk4V<YP?EyT&Lc660<Z?$JkfAj*;XNtQSbCRvWT@He@|+ChzbBYvhs-EQXziRnO(b zC{=o4B4%1v9@Q%PegEgAOWQl^bLDZdC&Xn%!95*Sy#rM)J3~~znzweEE4Z9gZq{kw z1ADyM=<CeF`X5wJPzSI7`^D<0L2bu1DJx4duv7r<Og4sf_D~98slr&chBZw;igkNT zhlKkK_Z)ROipXEp+Y0lloc`w6+%jH!yYK|DdMuKeJnb>4O}6MOCzrm2eV#tVne5&# ze;lP%j!1ci%qun9F6pI-r1OQ+v<j`-u>)?t*(s&x+s&?^eTVllIF?f}BH4MX5_|9x zizX)YUXA`I1;HnVb-OTKb}=cDJ0A4D4ukzJ;2UE94+p`&A<G}wZ9BwAR6z%55aK2e zViZM*irq*%B59%I7aeUqVOA)wy4+)JFN9Go2Op~iOHSPiJSdog?qrT!lHg~5ce5UU zy4(P7_F>_&35!y#E)>oXy!d?lxUwDpL_+v8-3-H_PS<`BeA>fGoV3dA<<Vl7W{I?d z`?pgp${AX**G^#c1cgvs!ZOPm%n&orptwcojc&|c0P7Y5sJXP^D|g}bdEzJiF#9Cs zgYXS2L2jR1`V3?A@bP^EjPS`qA$oaDNSiu{XSlm5ii+B_PBR6ARc{l#UBw)pQ_JYq z(^0LRdB;6djgP&?4ScLo-{jNCN(+=%8{M3Z$6Ii(9zyg)c_+5=okDOk2Y&?$$i0$2 zPwg8BxT2dY?oh4D^&d-ILVGM8TQma0+WQYqHL#z;%k9NeQ^b3mI_J7z+Bj|QRFe)g zuurbA<#9*C?ipw)$IkR*u+JaDfOW;}Wi;lJolcGMq<?`nZLaP)9W772*~Trq8-vD^ z-YiR;BHyAK*t-WwGn<C)Q{93#VF}zsJ4pw~bmz)B_+04Ad!m?Nn@{($1=BV&ovOOh zx?W<Bm^R-Sih4i-8d@REsR#*Qkrbzg7Z9wk=ZEIx#*&@R{hb|=Ti8x?1H~)y<qL<~ z74dLQN%;Yp?iP&gl*RMHz#C?}MestJnNygFgu_dQ^qRagOPJ3#wf&@bxIW6ZobN(P zzhCN%U7L9O*Hsy-a>Kzb*ELa?RL>`RDLs3FHzLPqsY{mP6Txs<bm;vG?1_0~R}i0# zFX_YiH{AG)PvV{0&?ltyQ)77T>h96$brFY&FSepEJLhcb$|s(RZZAFXOzwb*LmF>X zMBAeEd4W7#U?wZ~TUmfT`B*^boH)3jUX?v#n&IjwGheUeO+#5%?B=X#NDmG>WB9IZ z3TjSsZ*WYCxGSV-(Oh7%z4M>Tq=66z17*o%Lep+TYxV>hnA5ta)c~#9Jv0rQR_{v2 z$;FfjBc1}i@6e4)43u&e&G$LWqY7?&$ailqw5;~P7#dCmjk%5xY}23!&YRD4yhotB z#+K30<U%036Zt4LS6Is(7h#40F61}f|Fbvz4{fr@g_T3cueS2_w`jrrzuz0`G0+oB z*c#dW`c;}Zn~1m>nb<p9*xCM<Oj%LJ4v7KYd)gEUlU2t2t(f1!NxY?WLl+(qB^9|a z7~FsTSd$~eqP@0>Xd^GIXCN605d~5F6Tk=AuoEUTu9V$v_~Geudz8<|=L=RJDvLw8 zZjnE1<J+|)=7{qeqLC&|8x@F=b;Xiz3juxxIGv}eGI1Qnn4u`H+s0QNMlbl!+~6z$ z2Ikr)kAoehypvell{KXE`+8L3%6%0#dtTns8kXbO=%|ocBF+tLGb^6Mk88E#i+b(` z&kTP%D}3qmV_1&WlamNt@rV$kr=z;PX<iLeF`WkRNQ3bb6Qa7bN^vY12#3c#Z9%wE za3#J<gmg{ZG9nufH(2Pjo}aq7mGt$IHBQG7XTUWkU142C328w|+sXVVO#R+Y*zBNJ z5UMJnwy^R@d2iqkQSbYwyrMXZ>~l;MF9*^LwwnRf59ko1Z%~}Cm|sR|9;`nUwhH}7 z=osKtbBWfmI?cK@ox@}1fuA>(E?;n1Pc9-|7U8QhD{XBS-2?+r)x#GrDo6V`3vQo@ zHqdy6Z$FUUNHm6hziutu!3;R>w|_<QSTrIjr`5kc<$rB0E$F!B_EixbtpVfzL)rB| zKmlzQ0TYS_08sg>Rww^I0>yvT3^X9zl#iBp&6>F;K>B+D1~9>;#Ki^ufWae;4IqMo z68HhC=p}3u{KqEgnG6&(D%Go8C|rUyi}GhR*9k5E3`15`HD60rxi&2?+phm=k}QLN zcwS^p6X@dxPu}*@?DVGJvmds&ou2E+!pL_DInP9KFLSY0t8U@R(xRv3xjePGqe8jd zfjv=hDW1s5INTbMjgWDzP8?`uPwrf^clV)jZ*uK!g)M&lulHm#86{k0Qo<?br;-fI zBOBPYuKykHVo$p$#yw05pG-?wwND;Tv}4StQy4WAYT<K1qGVqcj&ZApoRD*`OpC~- zQ!=NyXG2e?8u}wc6}1XSh$tdYbR$|MQ)(X<{=vQ6AL<m|D}tnRn%AaH{dNTF8l@^! zAsCV*Q*%sCRJ$`XzOYQIO34?EU{S10&R5-Yk<}azyz10FhmnQWrL2;=@=D9%yEvS1 zFX+z*T6c_(kP|)6)SiKHUB28r-6yqeZrGX52+p9P{ms)w&U5<-xI(j>Ibv1aDzb<i zofjo|&qgu}F*L1E2wNoTbRuvRFOgBJ<Cc>l^+@tJkM_4Z#rC>n*T>Z+Mo*02F)gkV zmzxy5bWLj-9-bIKLAZAGPv|JU3KCuKWE7*~_f4sy!k#m^{>tuYU4D{~=@5;B-}?mj zTozvNqjtQi2HQE^UwMW0|7C=sfLjjL><6SNrrB1g2{!tckCUK*18oww=tYaRR45ks z9?TyOs}V7}SGIn(c{l?3%Ii$XVyG%?s#A!Z9FUx3YclWqw+yex(ME`DF`l@D)ner` zC;x>?lS1b7t*^9=sIjmc`DSb-Y!z+U-R{@Cj%E$sMBf%IrD^odpUn_rB18(QfRM+e zc^o_b4obS+Yn*Wd_ihB0Hkqch!l)rB1k>s(AIw*((%igg5s#@sJrZ*6?cdZLruTj) zAXT{rCrehG-%PM?6(#a8Uby~`4@bC_Fulg03N`$Eetbf@sR9F8EW=x?vC|}rCTpSW z2p^3thOHIq6(20Hn<P&>k}gFa=z7oQ)!@tTxLS-}=3*vT-xf<;W>l%CjuX}Y4?Wwa z8r=&?GqRWzkPo^1UdKOB(vS+HZuGG3HZ;}fu5g_P+U|DENWc2S^Rg}pU&Fs1{N0Fi z&CQl`=t{Of<VN*kjppd8g-`7*WLfai$^+?HSl)f7t~3Ei^ZP(gQ{DXhP^<={X|u-6 z$^W8-(paCA3UdU-11}6}ZWVoJ(bn1qZzn(~YIt@SN_2Y{*5LM9Uh}njSrQBCoPaiK zMCb<yo&JTr*UX;RA_yI~FN<131{*!KTRB1u3ko=l)!LciA6mhW4W%DU$fO$TvWTyo z^{JbhN;<MiU;RNr`8J0Jo|YWSNs*V(b+^x;+qEMHn$ppkRxOc&I9Axf3^!7jjQ@+I zB2mFNn>#V2aAgPiZdhfAT=6b`_1Rr!NDz~g<lc=`vcHYE;Mz-}`fTFPSN)e&RFFo+ zU@oU044<Z8in+_~n0Zc}&yG^r3To)(_|b~Wt4c!18L`5z{14UOH)+YPlO<(qWKCd8 zv*>4CmV0vE`SSbmKED$9)^SR8ivB&&hE0gxfc#!BdBN11w2=pPy<()Ow~S_5kHkW7 z4g#!*Q3YY1M-*A6LldBK*6TR(Y0kmlbahFiOBXbyzLHI|$iacT!9{Hh^z^}ojc-07 zVZ7j3-)A~|I6-e};%Y7I@K=41=E<WYC6Lrf066?rNA}KM74K3NWy^{M9&G%TOFPTu zfdn2@$#-#y8}a$AMX+J~FY{a^z_jARkR)XA-620d{I#iJ{965jxHbma6L1?b^$M{B z*Y6}mNM>+TqO;y4RBLf#N2m8VEE(x|SxC%aBFx;KW%SUy#)mPMfz*E?E;CcEAzTj) z967iR<?p2RKAG&ydC8X2ppC}jGTsV!O!`*bw`)=K;2ZJim`i&jm}>ogs5>#WaxwHt z%<y{*vo5{LV1A5r6g9#a;kJqX81lX2`{yXDTQt*xYj?cUVvL0XU)iKIs+iy+O+i#) zh{(KQ_}<^_N(QDkZ=-J?#2@K;cwr|O)93P5g}COl5xBD!l5LKKl7HuQD6dNB2FyLW zL6zH+ZGpy?G`(f3RMo2VvXJB^P`)F^|1MZT`l=Mkz2X`RjM>Y_fJr8zD!l3P22s6Q zQSOjQ4L>C$9K>Cx61<XosUQ7nEB}eSv>EaoD7}V_i@jY?eg)|&y<vUkj_)L0^ElUx zkGyI@`f45B+&U!V?T4cHC?B!<s2{m@iyb9;Ngj=~3KgN9nk3_$+?0O;=cu09yk|+a zEa2E55swXJ!4(X}i<1*l&Co|nPuGM5{!#<o5A_uWOlJr*)a!7BT7%{u^vF`N%^?GM z-~fblkh!;P?@ge|J!Hh*dg2}lzPa~sMIXp~G()k6&3KXIISOwYKCm20dQsij^6@pd z50pK<bc|D{zQfBG#pCjonB=7739H1;-G%oPpAq9V^A(goL3!yp_f(KQ)%RGROu@d@ z_jIqRls^$OrJq(B55>qm@%uCsK2kM=6*p-Hg9o6qE!4=Fh*CR<&^B^*!*r;UsrpE> z^C~_Wz9siJZ}5WWQ2`v0k*UaNht$~_d{k!3m-YLZZ6HNM97P~QBnuOgC&)>M16edn z6A=W(f0e(1eh|iFcNEc+e~2yLnSMm}{;K%I{y>K6A=yT}5~Fh<&MUs!<#+WAAV>+h zy{uq49&u3q1n#K4=D0bl*}aUMXY}cFBKoP9@*;<of6{$pE?n}SQSW`}AfMRWi4`~= zy3-CZH^`NoyDlCL3RDT`?B1b;(4NEXrt0|fhN;!{c1gC(rYe8O<tV-F7>>!^`W8Ov zCG+jas(j<&f$dP-Y8S<x8U^zyyixOk1AICQC>%MpE((*X%8<_T-^_cKk5R*3$6aTU zLebYxuf)N^%c-5sJfa;YKjrCDBSIEpIWTt(wIFS$>y{Le@pbrZrN|3Q#rAE^KbRpG zhQgm1&B;;#Or<|ae#&HlgUh+;v1rIN>ze59+1&v_G=a)kof1AER7AlO&XH~5p$OGH zupJ#ALsA3y7V7|mJ@(E%eHEr7CuUg)-Y}|2!!8{j-u!9s)QDQ~{=uE}8}cExC<81_ zeH}?!dVymP3kM&U`qa!dsG^%KcM<KUtu63<piEWM8;q6qHa8t_i6vVivY?rq#ETk| zlP#)(D!zXITV}4-uee>C*J9*(xINlNRbk0mO<hf))7%Pe;q=4jwtBmK50cLVUS7Td zZNC7ah*;8b?xo<{g;4SFMFB@9a~WBMFBb@TkgZq@2R+j{n~DO(t=%nNy<8Ec3`{cX zm`|Nv@=sg(z*7gTElD+ruDTNNU91rmZ=Q|;r-Kfjf0-tPkFeoWP?@8bzhvm7NcNrf zKBn&7fJ|UZnt;ivRRx#e9$V7;x7<4qHBE7EOtGS;#V%^6?mc`lnNZd<RSD=%sl7mq zK~qf~U*=d=tn)}?-%VL7#Y@5l`e`5>XiIsa%~TY-69TVW0D%R6t)x`X%29-6BjZHe zs8f&TJ&uY(3=V06_2?tvZgO}2G`yygg}40v!2k-@R@Wn{%YrYMXcj7rD931@67mO- zh{;b4ChUABTb-Od9+@|8d}7*n-=r9SoV{}mt?w(ks&5(#yQ*%OgJjsrT%A%Lfk&%Z z%u7D9$n>rqQ$n|#(rWxyU|Urd5}%~1tdgomJ9Js@rb3cDg$7H6Wjubi09J5l2Ecv5 zmFK!B!ut&%r|*2!XFiEm8A^Ij=QRb=gS+M+nCYS=^r@V|9(2q7EXYWGC(_if(W(a( zp@>v3#jg0G9!I(Q^2|W~_LO&_QDzh&)X6Lxq!OmSv5{5Q(~JMFk1=J)(4g;m*<4#) zx&TQ&Sy3+qRnHj87z0-1OEjMmQaMQvemlfftQ+{;f@W`3c3^xfjlz0;ZWa^v73L9p z)myb2gSB@N6yi~g*`YGT@+<nf(r)>@1sUoR6J3cmXRYzdr%oD$!NmnJi%IF-afVOg zjQ$8bC_v^;9C}eJM###t!ZLD2Z`a7}CiYQ40`S5YI!J)UT!9Z=qLT%ti60LQijO(l zHWw>=RFHJ6V~%mG0JljC_fFV?=#Zo0MPvJu94@ydY0@0Qy?#Bs;Z=dr#3jz%n@a<i z<U;730ZajQkTKtZz55@wvZS7X91H9G)}zWIth?O^To*<KO(~gKn@x#0gMx@F*4j<p z9?qe9Y;i-DwoM@|x_9Ng^Q1F$$2>U>HFVexYYK7_lfyCf<m7Od6FpZdsXT&AA;|lc zMytV}WxV{Fy=DjFqK{Mh_txa=5-{h8;aLREh=sIn*`2}6*=KOeH>T?Z>z7_7W^qt3 zS*Mb${65DP$AHa|r)zj5m2;_(^wQCUqo&5hlF|L9!^XK<pnBU^2aSxlS>XDMeQePQ zb7GnjXiWhQWdRUn38=CNN%Lgc7wUuO*2O}XsO};uM+onX?!X1c+aLB*R->L&T)_!J zGUp{motT{gPPqOF)y4c&iZwEe!GJl7@vcMkEK@#U8^Q5yphJK(u`$vhSEA*r3Nsq< zvZB65ShVVua4EJJDR?%JeB}K~{TX;WH9_1WR-wJ(j=Fx4!N0~i`SH?J4Y+v<Waf_u ziqyH=o%Who(GnXj>No902e^?jPvja4m%*r!BZh4`ndEE+gGOu2pnp`24@fmS&WNL_ z>P`1for_&D^~{Bw0<^8=HmE9|(I|0;j5Z?It)9qjk`yvXuxg(8Es!EF$;{^kF!!7I z_UbmJs$tWiAF>@WS8(#@bSqYDvpGl|mbbs^zsRc|)GI^H16I<TJ5CC%EefQm)6eR{ z8cmk6p=vN^1(a4g_Vm_D>db=)FWEa`M@QF1bD6}JIoHY6QZk^jaLGU)t_$jPsW2D1 zU}KdaS`@xd9-Uq|-cH-tViS~dvJ@(q*!m+2?A_AoOIkK8jx8)E`}ZWeZbAy+ol9m# zF=aFKGkMqfT^7Zk8Ur!S=`5UiJX(~Hu!rFeHl$}}Q9Z){JaSp)^(e&g7Q%B;OyWk| zvPU?8Hr*?)4+l49MOuK>6lQ6Mwx?Ae(m11tnX~N(d%mi+Yqr$Ux3xmsZo+yx8lI3i zBf1^IUz{~^HOc~Z#F^aEJffS*FlWBVgWXO@gIpGj)U60&x$%0VHK<x1o;?C*kz!r{ z+TqSBp1PX8<9g%Sjy)28;qQH$ZUIJemI9c{tET}OrP~A~!cf*b&u%{LpNf<?QJ0u! zhdav7ekwHrM9-C&6O<=9k2z$Tl?dGCaX6I8qMn~Upm*UO=nYPynUmkTQNz0<ZV9&m z4F1V=Ect*VA=AGIZHGf8@u~{T{zF&U5~PGQogpB%5X*lT>jJ}D;B;@DnK-haNiFH< zn1Gs9xF7kTl#q@G7k%0{V}srWbO1*Ge=+urftiI(mYt+y+qP}nwr!go+qP{d9ox2T zr^7euWHb17XTIIpnP2zU{d1pltLmv!b&fMR!8P646@h6`lxDssUir<Dxe2;28tIFs z$n5zZb&Z_wiOb=s=Z9W?+M!^PaGb74%Q|`aBZTJk=p2RMAW_V#g+_^@Td<9#n<Z^( zs<4)H@i7mX?~0%_VYKPZJ)0IBj7~sh4Al?-Nlqc0LpU4@ieMT@SPEpU9g(ps9IZ&; z{-;Qk<RfgR7`zhRRz3m*FFFIo6&$mK41=b0-CUJH9|?ZmVRZJ-Ahx9k(uJC6<e&Pw zH+q%|%ICE_lB%L;^9icq&sv-3%w<n&jU6$S@=7rj!_XWv0!wFD$oPS6@&+f0Q#nI6 z*c7(lDP=AfXa_rz9ZbHJStB1ma~{rJburaM7TM>R_M>6mGVn|nELr|$`xXqo(Vwe3 z(%JP#{|sthzhS*;DJx%*S-)|;VZBS8?$)y}DdDw4Z$oF;@b}<^9m1AQEH{q-{0YGg zyU8E)32=N(sxQI!&o1i_$!&)WJQX_Y1*617c+JwV7kX!ZRNH!v3(#n2HC==`pkrNR z0dUQgg<X1lH7Y-9BD;?KnaC~!jiA@R-C2{%k|Q25KPD)~w)PIaGZ(ycuD?8;_XBe2 zfk5p2nW-lh?@OBCyZ8CCdlJde2Xo-X-cgHfA&%Vzh}^hT^0O8Jy&fZfjMQ)H@R?{R zfpOaMIaH{seeR?oraex~ixzK_10J||bY-jl^6f9{yN$r7dsiw+1F<VNvTkoI%r&gQ z+bjjLD_!%tC47)zs6b8ilze=ad~cb%i_^n7d^##chL~S)J}n1j&}T3swcRW8UsAez zW=8-_oR<QBL1wg{+{ekjJEU*P-Cb^P$xBUN9aL_NNb%3!*O!$2-x$55qugU@f3~<d z#>=%g%k==8p~bscm7d70A7})}wp1YSlLF45<vOCbO0WaU3*v*2dV>n<mVY)X!0>&) ztHnJ{r8Kf=L0F!WAeOCNijl2Hly0z7+gee^(m5jHg?g{}>@E4E;<yZ*j}JU)Y}?vt zw#=*z3+Zb~D4g5YflM94Wpxuw@YeKsPT_twC{?*hPEkYTWht;7mt?D`gR~P=tpu-D z#p0O4ME12u2mS@?K^33jeG@6;=pn0awj$Odwn^*3fJE3<Jn@F>AGF`<i3vwF3{^zX zVy&we?)x(|1zL8=+hEMh@e}F$lx9P1sNq)HgfG~SZ@h2mGlguIRvnlp!jC}7Px#dz z_&lG;7#Ero{w<~r5hqO<sSP1iYhr0lLRssAo$-n~lXM*csvQyFbF@6+HwP?S13~AK zZi`Jk!Fq=UkI;Bx_IDI-#Brqng!8Wmm?Ps{A#=<MLU{vf^ZE(o^~t2UtZ1iiN*V~O z@KK%DeoR2^fqlF0w$SOVbB7C<_!In~4z}+%){i$nO@XMiPs*lzpF~Eo(8MvN(!LiG zqfNo#-oAziH$oGi@Tp;$FS5e`lc~StxtyB{`au*HL%m-{pTuKw?hH}TDlkKTWEeX@ zS3-(_fPYliTsFl|U30|DpNF7Ha@27k$X6N0r#AC;rcggao8ja;>TX+*Z;csCcO>-S zdpd3GY)AZca{*lK=%0-Z2nM;`+LuvBX5(E7W*)f?u;L3WM0<^c93^5UxchJI^(f6e zaP$a)@W72<*os#i7Gl>ju@ztQi99@K$ntVD0e$J{rD5znV%1GnuHrUZcdstXb#7Y7 z7aUU9u9>$2*3!(<VorpXi%QpTiGQyiD?W9<tNb|j)|Ff1*2XP`E2*;Y6o1xl+Uc&# zspaLB>F$|h95I&XVMB|(<6wJA_D&r|1n{QO&?-1oWL~Roo;^scfO`-Gz5j`J7@yW# zW)^^)jV8YU5g6VSCzMtvNAbA3n3m2D*jWT3q^lOhb-{IU|CQjCJTlVML4+En^>j2{ z<(1=gPllbaTBVnkd*QikBJzSzXMBK@ZbO%N%A=Hk0e|R&HO)+sWlrRoD=MlZV4YB_ zKhk6gJzGFAE9%7;_e_}6lZD=Q9(k@vS8|$A+B+kCw%Uav_d9yWkOP{&4Bi9u*8r+R zYYI;=wI_yp|GwIt#wU#Vz17UIRqi0GPP7+g-7%}qWZDCl-3jd0K-LRX?IG`0-@C?P zR(k}z!3o~;gW=8;{5_g)FvthX#VO|)y<_<92}AEJ<7c?GEZdG9))2Q0u}7IDkG^H* zfO<yh1Ksv`9?+7H|IvY?2qU@&Ug;<=YGT_@1`=cI_zB;@wzB)*@eX5GKlq{*e9@XJ zCGT^bCFE5d2js}bCmo!sDA5@R<PC;dVCA7BGKypefh@{^px4Q`4X)wZ9Dwpb+s2iT zn0#|;-jq$CzaR|U!3h{A%zt=>CAi=0i^n5%N0QG0agDic?K8m&McOLNL855`p~3iH zedsVt%Lg-*fkz$zFw2H`K5-s>Wz+1LTRG6D<oqigg=#-}e|k6v_QW@1XGPz+>ON~7 zcwh4{IC`h@2_NAJu{Y17!;d%~8D^3sJKU4-Fw6QoWWyo!YVKNs(j(Eyj8)qggQI(% zi_R3Lp?Lzmvo}U^foJkJjJ^B)XNE`Ot^p?ao81}z*2ViDHS^j3oy}3X{uj|1-bFOD zR_(}RX%tbQl9GYI@N!jRG#C=p7}7tJdoJ7q6CF{^n-_;K5M%gD_<a8Grd^8zg$c!} zvL`zkS)8Xkod6Tx&$myIJ{-Js21dI=Y~<~;L0DV6i!~GUM%^(|#*nT6ZLm^IF8q!j zY(BGabLT9W2qOhlnDfNTsHcth$aw6o14R;LD4ZKxr}K*kM+U^3j!45olrh|Q+`5PB zxWYnUbNOI5)%)KCjcF~Lx!N=gFpi^$@B37MDyy{H2vSwFM!I64tO9bVG+{iX-z_Ka zLTJ3T$@w_;jpn55yg=)sv#6hx?u4tAr4hZzO1~<?YbJze7#8)!z|_iGMht3w7GgRA z>R_grqHn>ed?-Q0DNwilNcon6q0&^6!uqZ22FqAitDrpHif9#KLT%9c>K)|3nsAni ziF4bvpem9CXDx`JB{k42?^;7Vw;Jm6r%`oGF({zTy`)tpVt5v_gs9#WVH(;!LELF+ z4)6vwtOfthmS}<iEMu=J0}StA{KLhp>Rw%M+Ls|vWwQt=LF>e5*J;T*-pY2MIF(}W zd@PIFIvS@KHhX-NF6r4?i34AqVN&!0{$f!YF=;PIY#x5b>cW*=-lBZQoB@oQMN``3 zX5%6M4)-@xjg_96B$*#^m&W$>%#nrSHRi<R2e4>Xc>;B}3FPJyz%0<h`pWXj_0WHz zHi#2<SIjcNA@XyG91s*v5D2c21ey@rXXa-<Yax5dRJjYcNj%~PV~vms>6cmr5)n5` zG>xLxA=8z5mfee=txcG7F^{mj3@7kUuP^S+C}3yy_9ocZM?~J6GDxw;@15^jY+-<5 zxiSq*<$sO;vH#CVrcliqg8iOj9l-maA(@@^e~+?ype*Blbx$X*+l2l^6M_i)2|>7u zBtQpgV23;<m?ZGC#W7K4V8YBjCkJ(LV{qrWzO|)gZl#I{uURx%KtNR6-uqkR@UiuG zOAFwag`d6IGlY%A0f2Qkjs3RU^Xuy!zxRvr{i=!_52#*B=W3V@XF`aZk34La`XvzE z&ik}J@V#5%Y8Vk7Z)_xp<Dy@JEq1>geU$OyI15=5V!s#%FA*GXW`vj{B}Du#J&@k( z4=MfqpSCTZfhh93WSrd8L1rfpHMrUF{Yj^nNN|1CKdkg<hw<pMhnG$d*y}H3dJ2DN zL3f{xID3d!%Z~r(a{A0h-5!GDear>nn;wpO%m?L-oeo0zUJP5|-0zv+zdBb;(jQ%= zIlZKS)0-ZydT7EGzs7*~rrh@)^xq!U^rj8s7hm`#A9N9fEh^RUk7L?@GJg^+e^udp zl7;npD@Oa7?7Mv^C;pXYdifprMe$PSUsWqCummMj2OU;eipJ#r&Eez<(#xD~riyVs zKpnZbExWipHM2jnxGgoajp;&^!`eQLcdx#T@(QoMffV%1DKop??~vyI6aool8&Ylc z;k2PG1ZLK%U8-H7GBzX7SVs!aR0Y<|3FwbE8-c;waTvLjr8P9JSGsw+TJ|p5@8Rvx zr1ZRNoe&#u(r0d|q%|pe9)=Ny&xm<$@nkDgAkfj|wz+6&2mspNYtRvn6Lqa&FL$>n z`1}*^Pk$`u9XHCv4k`rX93>zoc@78UuG^L;<E#x#*iFl}RE4dZ7(xOEu<XapM2xpK zU$nh}Q&=r@8315!_+!>or^V$yS$nGXtgM!aPm$^2Zr5DTTNk?a|3Fpz^S%GG<`!MW z7ADl8h`v;)XgP~5wV>O`EvI7HB*jp{1vr=6Zqs5<ER!Z*$hWBWSQ2A=tTOUiLeXGk z9skJ_iWh^B_!iu3K9MmK{)`zwU20LVTJZ4rlRZK#Ami5%4<kB+<w$+d=(Oc92lhnC z;U1ecfv-ruhf_7gPB`0?$5qO_VZ_o|u%V>nhP7KtE^cl{cPf_1Q#{%>alLI~@i!x4 z3OAWQqMlRQk8CN5*nF7jV)JBO`~Wf}XX<=rMT>D(M}8anVwVCV`4@{B@v;IPQpTA{ zRckgbp^Xg<;#vE~A}4xGnW29{g2T#=dEl29OJ<*X7_QV^FabjL_hjZlsj7_-RX|H! z(o9ypj|$MGu%g%TmBBZiDf>06!zQUMvki5)HRyt7Vi_-Hz{mtkivr8^gZr^xLxDNF zxSr=1O-Vpc*z$CM<!iCRG7k!5ZDJ3^xpxHNxb8@!D-ije7mvBkU#!?6YmN6Kku2aL zXCyI)=DZP~I^<=~pbkxR81biI#1b5+L)@Mw0O(4JOm@+1X_XX7zt$+kE9oKWX10I? zUJO~RTJ|EA`Ftn1Q9pq*n=StDw%^P+%x{k){wz0-9<Ta@=c+IU3d1FBGQ-DhG6rsy zF~ZeU3ky^`;dpEc=4{kdZ-S}J9Zx%QlL((~#0(e^ltSeLq)ca5FQM9qiz*3}D5@SJ zsZ?D=+Q)=&i=weaPO6N;FDQ)Cs7<1|eO)G@m*F8(eet>B);%y`frBATRcIR;RM8_u z3H62-f0x9k6h-;qD2!^UpoVx<r_iBQ8Wgh2B1H@1nQ>GctFmd)LV%lu44WJ@*r7p0 zYWRjIR^Kz?$YY#EX%vT*6pJHG3pEfYRlhB|K-J{CRb(WV(Ac=26nbG2e<rB3$)Fw= z3ZeQo2%)AF3L#RfHb_;Qi8u2ySy(jwnv!~vKs=Rpt|p{g3l4kad%yW$wvJ4aJrf>u zYt^7$sW9qhIoE0sL`4se0frv`J!hsf#$p}%KF~k#^95D0BvRH04#dvh#n_Z|e|^ZO zI&wO3C2dGHDydGTK~$$a(n_(Y5d9ZbFX}ytVp*gY5xDz-e`IIz7hkNr#5A_bJO!PS zXp)86uo|Ph!T8Xxy1qh4xCY2<En>2IU!_ku1=+nv$a~170hLPBpj^vZ#(ePinps*m zM@y~n1((0$?YEpopF_+dP|$^8F~Mji9kbHiP2B(~;JV7>JnQvtEtWZT<EJ^`6l9cx z(j~XmLUk?cj*CpJ3s5z=?q4%@*0&du3<k`&vaHH_T+DY9h2tjRBRDlXc;`)gyG(~k z6OX7zH1|YV{!FRz`y3$ERr`r_Kwl{Il2uf_e9q;AY_=R2E7c~$J{hG~3aY~R3oq+b zyfd>aW^S-rb5=qW3tNdNca^GQBxVyJEr+R1gwqD6{MItvGAkVh1OxPuER|ZSm3F_) z>Zjm`gwzs=A)1U%`oKH`uJ{)!d@O0dy9g|ev=r?wIt{7vjk@NEF=Q|lsIvN#?j-t} z3oh4e1Ky^&zx7PTOdpw4+oavbTTIsR7;XLPvbRQcw-yt3#NX)F5$^1CeKp4;)6!^e zT=xwW;<RnNl6?X@FyY4K{MY2sN6gt{PJ3gYdDoLmJ4krxm|G-*TjB-sRH&p|N2J1k z2{(NdYR3DUZUo^m<IEa1NiUF%I$x&uaFR1?^^UU;Gl32l)_l;-gSmDhAG=2^S1e!7 z^ARI34)WA8k-J7i(vjsy3$AxL=}}jCcx$UWZ$3X?=fJEs#O`6%^y&kbt-n!L)O6Pv zX5X$wXs=;T+fM6Fv(oa$u-3rlOm~h+jtSf@5^3r8;fxqH8)N&^pzOh(8NhFkr$FoK z*vo6)?sagR{(@ZPR$l4cOU12V?cMVF*0-AgsB3s*t=hnG0LH<RXK{Lri6<S|)p^JE zU^LX+U<zptDRi91D7Ya3Gsfv|C>hyQ*<P_km%dS^{)ad+ha72BlNCas8E=$pR666j z%Vy!c5L*1AaQjrJjX-*2et9&M08jCRYoQQ%++i1VvG^u<=1D5C_)d6DafU+zrZ|I> z{h?nOXtS<pUqV@7gZeWtA~gU~21MeJzU8&ct@Y`asM*)!d%8U6_jqkJJKf?xg@eYm z;|&_>hG=Yr_}bN%Cu>I$mnVl<ms|mcUE}RXKhL*(>}c_aX#$ATfBjgLC$cZIpJ6=8 zJGjvaP^(mDxQ;wbUp<+{=>M>*MR$4Uq)T3W==e+5eu>{-Xy>O?iaK_pA>Op2oElY+ zAgd8;hUG)8c4%btJy1}TLcZ}*x}DfQl}bkVpw@-Cs<aYP>;*l)M-*1#L2bV9Za=u$ zPW)uygmctlLZ952w;x!Gg6@I*(cKo?70l`lu6X7&2at<;@5H%TkV0ZxCT~a&*IGEC z=Z`HJRb+`YrNa@q5d%ol>}u}ug(fQd0w|<(|172!Q5>4|Q%t;a#6}JtT?pdst6z<& z$shze?i<n^A$~bi4)Tl$Bi`xCX|6E9RbqfEJ+meo;vM_2?-*-Va21HM?nD7JwN$vZ zJS?^^?|dh0FCTz<{u1yVoe`gtvRO_3^4*P8R58on`n_=KZWg>rh5lR=zL=yT8#4>2 z!R+5?1W#ZMbS2i{4}^VQFaCYqhgIy;$(Y3*Md8dUOc61(5!}`C^Ak4tY*1|pdTZ<} zZm%fTCC*X)qD=LL4r5ph(<U`p6*If0IA852mvtSfoeyrB8*&LPz_wLy?3|(%g0o=D z7PSZZFPo#%%R{~ci$yENVoU^S^vH7bFmCh+UZ^xurj`&SEn+toC7)SIm(7-lxJfa^ zNvb~MR)JCNvRi!wl9=NYA5Xvd^a<!YV^*l^^&pF5+tf6V8-hE%!*(c8d*|-PFN7BP zLQOuCa8dya`=!@y;_|d87-~>f4N8k=2(0!LNmY8Mf=(HnlHaClWXi{aDx2taY0<et za+>Q9nI1O;P&O;e?-@=LSDwuJW9z_}7vLot;QE^ui>us7EhD7W4kT3ut>twR<Qdwf zR*~xa;iHz;Ru`!UTA$<fAI{*+B<Vj;LVG6qvitjH&Ck#fZlW-&hhbFtL5dPr^u_n= z2_LZ`-`E0slIrnEwqU$W>3N-K1Ke=`y7K;VCGK`+{_6}rD*Qqci~JOKxafiOdk=Ff z^3)Mq?$E7k44Hnv$ph3KDgcBC**zkoO?;}q+lcW7dAK+gV|~D6Q3BGAtPV!XD^RjC zXpAEUx|%ob(HBQB`#NnrOvmR1#qs1|S`F37jpb%2LFMY_y*WoOX)MLTtep32BZG}? zO+K=Gp~vY|bXwg0fifl7qvl?B-T|&*FkhdJGYcvH3AsRyebkUM^JwJJRW$iRO+-i6 zI+d|ND=f?Tn8{Pi<Pv0QWD?3@L2_kQPI<@g`5Cq<RJDc!Q}H<`GtplxAuBog-N@iL z8>(=$(x%kVs~JaeW%1OI_nqmJwoVmgrWsBV=5V%)Zb`D%qV1}WiB~p*e6lgQf^@kh zhI!bd_3qlC)26ZQqf{nzTh@;4@Q8e;cRt}Us%;(q9-E=uFFq)7;Tg-IU;klW^<O6a z#;p7o4G@3)2>Ny?|DQAm*xCO5N~HzuqjH?`E%un<ZGIv&lJHX;F_4HX338BG0vILH zU*utqgaoY8f!SFSjEwn^2UxMn7PX~iL#jp9XIZ>tLy1@l_+X-=bGdeV!|wV<?S0Gk z((U&);9d2dA21_B#y}X+yE_s0-aW&)>*;q2)AO{cu18#m`Vkowdag@udd3^#dse7q zeMgMW$FFuyPWMLnSEis><*ZKUs4#Ex4c#q&0sKXN&#z+co$h{4fAUnF?hX4*JLB($ zXm3HU^4YDfir^PPo?r4Tf5qc0(`S^_SLH%4<XkWGi{IE4Kk6HO%IBtN6zHJvSJB*8 z<)fdRSFCT<0=ONSS&H<Q&IF7{Yk+j?WUH{@u&AJyx~ZyZnE=Pp+_4e6H9?=VN-i{H zn~Z7P#A(G9D%9zud9Ht2XM5l&^b9ByH9DE#bXWp?aJEmv)VJ`G5s*gtJrTS5z0vEG z`YTcjJt~*9N{`HONn<BM=Fwz`NlI2#`_{N`ts*CZaZOa3qr(0%Q|*t-E+qrB2`5yW zfhyBhF^Z*e^BSMp_-<FI)lLeT+lfL!zRTk#K|XBI3t}jHr+GfYwhU}kJ152H9am>& zd&hgV9;F|@$5CnJyu#{~UaUg2GjO~gC6*wBGq}@FLJRMb8OuipDg=M!^{X^CB~;K@ znka?t;Inc$BH<g|x85v=JsFVUMu63HgDGg?M~E(9XGm35dZ*jS-Z4-lw}=^ud7G8* zO1MEo3dV*#TEyi>k8yQcO2dmO$w+G*jtAr;?6&Nl$qx&^N4qd&F9C{``^_-jYH9{m zO$&+(%3?|dd9hfHK>7gcnlz$MYXbGuInhI4STj58>l(Dcj0lZ?D2bCL#TJPBpFPyc zSlT$b5JsIGEG7fu6C}hB7`Mze&7-Twpmil?xwI`|tJ1F^c{B79a2u{+WF0iPQ1k{} z?l4zdF)Nb!3|Pd$qE)aSbzY+iU;+M!-<p4qR>r}WdaACBa7!XQsE9wumIF<Zp8bsy zHQ4s;fBG@H!<~cEP#gS0jZTp|BP1rZIR~$gHl0ltVZxgUUf@_GPpjlWu<};{i+*H{ zkE$pPZ<HP83P|b{9qy8*3vfds+c*HGfwr>d;VdqLcIDNFA2-sT0c9`CEJP$uRcph& z$=JP-5~h~Xl7@k@ioB>u9&^%HWdw|OI?oZMkS{pcozW3<+K`avSCu_XlP0YPu}D$i zNgqm;x(>(3bkzjWHW>0^L7q~gT!=_wv77DT$xND&=XnGrNcs0L(%xh&pfQJl4mNwI zdVL~fW+j!Ott@gami)PYWF6=5VM_tOX?{$6KrB7G!myqaDKsS3%<i>pNMG@h=pr2s zX`o>(I066U-}Ve`oYlLaya}KeO90e}9Lo|*mlQI%;>A`_W~mw_c+@+Rz)tacoAu|v zK-#r8`-e*!c*CsTMvMT^LscyO%;1$h=b@*Si>LRhS((}CS5}JoekRc6Nfk$&HnAF= z{1Wf=B)M(KC(7`g0jo^^aEc{ysyjtB@2jOB=|B)Gh0E;9*w|RhcK~m6Q#T<fEn}8c zxJbtwW$y7m=eex0iynGBl2D_^j`Vt7e_2+>7;7g7A82+lD>kt0(w9}*J)$MYQhC98 zIb462aaCPBt!cU&nqLhoTvr-fMx7-Q9$5+3u#~F})FdNeeZ$zUocA4gxa4fgUN|%B z5G^V@v-B|5e!vQ;TW(A0B)FNHKr4kN$J?Bdp~1z>^iXzSb+_)y@n*R>zT9g=uCKXU z^UnrYQb4#<eJIA%oqh|i2DnppX&dLbA6Rlcyv~mJ6Fghv955i9Jy5VxnS!q4a@%X@ zb>4W9EqK7gTkwn_#sKd5N?Y*nO-(vTjB-@nFbg*Hih3tSYDHUAG*OcWXf$dZlNIaf zGE46bpk#!tMeCVztEZG>9C1+BP1a&fObC#aISTG#l$<%8uqk5}v*$h10oFeLifYU9 z<jxd)$>CU_uMPgHx0*Dy6s_$_Mcilk1MK71?AAU}<4(PJwNvB`Him>|?-&=+*AW55 zbfTTi@(^@eDB=mIOn*b|kH6JnIsAkU<?pYdb_?zVdz6cWjjY8NoOFAlZqa%-D4XOh zKP-F14`=`I%0S<nP~W|k92R&qGtuV{0IYoQ0YzRrd6n>Bifnd+%oTU$As)jfcUlkf z5gQNz2B>E*&OQ={vkxFp{N;bpS$)Id2`|WrpE1UhDaQwWB=!ff_y^(&nR1?|=Ux~g ze)96x-XVXG#^e>-TYMx><(Ar8eZb((y-YrKjLhC6mZ~z}TQYN_>fE8uuM7Ih9iEB~ z>mMV=h@FvfoN_8<X9UVftrMucSYzQI!$j+*Hye8pk-AcUxW?%3p`-GZ-bsHnj*Px4 z#_W{dTJVe<hF6|z*Ju17G|v%O=Ioq^vpkMwS&dHCLqHskFCAZ$LC+~dX7;Xgk)}ym zF<$7erdYX?S)9;S2w&t)kG@SC?GW@b7U%GeDx%s<q+&e$aWj}&Q(4j>8d~*JDQ0Ma z*NeqC-PU!oSY^Ak47{NRP^6`>=om%o!NU;68kx%saOi$46)NsZ_ZhJGiCXb!hpyhj zk|XO{_^7G2%v2H@Nz|4oMihDD;LH;FW-7MrtyqzLMynj!rKKzpV78hoB|>~2;bh~q ztCehp@{{}#4(sMQ_D0t>tMgl_#L`TQ?>)9u14dqxwMfJN%QgmM-XS%rMA>`KWM%f( zEG91qO*FYcT<MI0p7@tu@ki(dE<%^Syr@vtv>t-!9SiUHk(16JT_v~U4Fm0z^J~zj znG+Kw`~-b;1juku0t!!B`G=yJqjaT`)fM9V&T-KYkNZo}v@T}|lO#gY#0{ooiMaIC zZNt{g3L#nar$XFe=Qo#Wyc-FV(m!34L~`;Z(HiL$Zen105apm_*ryzBQh^CXSZ@u| zEB(5)Dgl-=w0d{7aPlw`HqvFt2geyIY@k{#Y*<=NFScw-1tug3u^?Ni(OJFEu9<@+ z!#<Q>YV?Q%=Uy{*Xvbou5_fv0U?S$bk1I1!g8NAnRHertD|S}4>kB(ElB=mRxwJVl zr|5`~dMpGb>GD@>qSD2okHL76;o6hXMErel=aR{*r|PJMM}3nIQr9eCOp3f2*Hc%L z#H8x&^UWdRPTl2X)NRT)=ImV_k{q&V;>$&5*7jprt4yY|sVt!gxhI{-9Oh}BXqBN5 z*V%oomx;_y@`gR+DP;`NeVR;3z6c)zCAbmsuf#3H?2u@;uQNjWMuh&qGd2B*0W2+U zE-lUakYSm}ysdvFc?q*y?a#NjMw{OTpGu=j`-Sj0(>523CgvCrQ<Nf_YtwZpNU6l? znyWER5v0wdZf!MmAthLTsoda`Kd0f9A$?I&U1O7PIyUx(2O+(2^f&{FHT&DRFmhHT z@&%-r5iXFQyG6@cn*D$+^x@u9x$55I7mR(z^0~0Zo2|0y-ZrPNq19c!ZrZf6Wm>bo zX5z5IWuoBA@+^O|IjI=kZ^}5u%T7nYV>J-+$OeKQK&}6=1$3zcO>x3Oo}YQR<v+?r z0qx;%peh%yb;}`S`ZPi5^z@qS=#=-&>GRCEAVxU%_Yr{?*pQ~-{T$dQEk1{in4Pxd z;mqkuWuB;VWJ110McA4`0GnY`YO*{ia7lX761^rLNK4XD3zWJ#RB2^yTLt{$QoxVb zuDootJRtUmvdTK7E3X(`N74<EC<^$Q{CMw);j`t`TJ|;wcdCEd={a+HN??)snckGm z-lby)JTrIsY}xF>=~zG~1i6HBe$?k|Bl{N&xwGTF@5+SwY!PSH2cB*8giR+rTP~6O zZsD--q00`2=jGM$GqQ_s&bGcC`|Ksoacoc&v2_Ymxuae;bC~)mRgH66=cKN@V%hE~ zPLY$%s^xK}fwk`}9Sv`kJAP%BGT&&^hvdDH27slVF*&5cJCf?nXT~v8zcytSvj}=* zO#QZNa9W&?!wQM7?Il3mvPE<qt+jSak-qOqk3$SycCY8yIraqi1?()ug86X^V$`+} z$|_X$cb}WKVH&kznC)77<i@#GN30g5h;1`^m0<^1>@QlD9Z~s%D+t>%<0av`!I|Bk zM{y0GoYxFB9^gjQBG~6ZUI^|mgX}#5`a?S{{(cbNb_TR#xqS9sfmaoe$X;vFGBG<v zgMVOoDjs#cP@0tfym0Xx3Tm{*iWwe*8}@#WDDnm<gW~Xu>IeF9^q&jw-3li9!TLt# zwgM;kf>_*@8TPsd01g<nJOa^d@1t_5^qWH9+34K(9MbbYhkxxzkDM<ohMed(uGDr_ zKBuUy@uF|}0RC?OVw+k@xEhEJA^+_FA>+olVU?&&btKcfE&WaqVj3bn)m#sqcoWL& zLXzYwqJq(g>AxYYC=z4g#Co*+0}x#i^SUIf&(@+C#^Hs=%uPV56`Rfulf{(}9B2Lp z^^5lM&UCG5-(={EDTPO7g0aJgWv_-ya+y@+@F}l26A7!RZ}8|De5Cx@0quZ<*4V1< zSJ2YNDqPY#3MeAor2OiNhk7~%>rTpwN1_^IPX02vMT}&(oywn0(oT=nmDokpvS|g_ zIRuQV5<%x^fghN(E!DyG!7W0J0%Qp~O8!#<u!9WGzHR3aC@#qLc0fxt{vEV+rOOV* zMP^(GTioZDW~F%JW~Le40!5{nVrQalc919Z^ONh3e()=#7~ED}mokoBPo;EhD<0U@ zPaTX}UktR95it-x*;LL0ar2>rN31Pv4Z%=D{D=D-;Sin3^Ui7GZXvFwxx;W=c8z2C z?75~_wresBMFvyaeu;caP%-QfNhdbLkQ>Q4wb|)`G1H_F1*GXxdKhJ5_KO&-gZB~! z+9MX!jX{u?iu$UXCfH=XjbI}?VA669*J196Mmr%NT<{sLh)$LGpDkb+&ESfy$O*VY zHn*Zn5h5vQb)FH03U81SjmR166B{z*=P6eYYd_aXsN%r7Xvxnjk)Sk~^mHRNh2<XB zT~%kqr(NYL)g)98Z6~n`Fy+?hHmpN^u7T{uYap|mG`nfd>%2OTYo9zccJrO`{=(kB z;Mm7+o_b6d@g>NDu1Z{i`LN|}pZXi;6<hZo1Up^CFF;gCh$7t+J<<stXhsw-1~n5C zVhh%nB9yWd^S~1#;EOiCQ+<XPwrU2RqPnu&=7{g-lsO2)BVATOoGQ&(vgHY4moI0{ z=Hs%cXF|EkKt1mktu{j5@?MR9fi-o4ja>5x>e>P+3jOvN##!eQ+srE|_5V%Y=^4_p z{!UYY_XM$CB4b%sfm?Lx6}K1BB)PR3Jr5UW4&b=v!Sy%ex3UUC%#iiqd27_tj3kkX z?~gdE6!Wq5=gc%{u5mcGbqLi<{{nN!jM}tS361A~^<@_b^`3$N+x48jD%}<T)?|R0 z;?L(Fq76a%Li`2t=Rn4~Dc~NWt*d8MpIMH*8FZ)EyJ2e?df)B$tK!6Y`cto@^IwlS z;n9k(aFb>RopBD>K3=ED{fr3R_5N{zI#(8t^i<8JF~$w+(vHYJ%8gZbz4N*eO0UFF zsd9FP%uzm3Mwx?AOLv1MmmDQ!$((SqKn^#Eqfi7>1Y^8zK|Ze}v;*Gm=ueF0#ouR8 zZ)_+4HocZxp^w}B5|1#u;#*-(zfkpO@bD>oqBq~*=!eC?7jKffE^z_n<Wn)yN3ye2 z|60|0EpB_%@(1ll@=|L*?1$gy-0L4ryjd8(ATW}jM$bmf$4w)1aZpLiW@H?Df4u51 zygvF0Mm`*4-V4U_vnp=^2m)P)nVezCVimmtGY7ZcT?IpICzTY>XCF-8k57`sRLs)I zJATGFTLPq;)uG(z2$~Q?z6MDCg8a<y>Sgna-8_4h<?{kR1hBuZ&Fb&HGuj=S;cXwi z<NkyA{=en(zPaxo_D<&XR)%hd`tGJi&i2OErY`iR|5(NSj}m(S(&2e~6edmoUhqx- z9sv>fZ{PhtyS@Kgne3mJd=qBnkQGrzdN<{?xn;h7C>5A4;=1$?)v(w_!m`FN=5J?i zo)_q(?^v4I_5Z=K@d0Ne!6<^p?@Mzq9?(XZ)v<Ck)QKPe>)i8@v&)(Bet5I#{$sY` ze4yV8QPT!Rfj*y5L6$;|2}dEjT%z~(8ZvwpxJ0fPnYaaW%t5Sud^PX)P1&SAFsSTl zL<d3Z)6aX`k`zzMJ)5Uia37aK#4@U1CFj#NOvNubIHA?mk465S5;wzlIxbTUQa?BT zQsBLl-^=)Nlo=v{mYMjxVazT4k%-t`6lBQ=HL=jYP;{b^KVQzfsuggfzShA^^2A<5 z9A@EaerT9M?g*Qwfh(_C2%Y{aa9e=n@z8kMGm_jJJ{(0VFAMRdcv|C6w=5!qmt=Vb z!tsrVxfmma|Eyy38i?Y4;GN72L%KDM?Wi_?anaT%(2AKI)6w<|0Wf*@r@%#iw3j=n zbh;a3Qu}w8s2FBcIh$qi8ud?o2AwlD4NWzgdI4@YmG>xDPDpkW19UFIGO9pH?1ztg zBb}oTGF^G!|H;b*BZT@E_xr?czIEvS3*p57fEY<TH$xjs6A?of!+$#Ml*C!PMFE77 zUfJwm61k*O65B*zTH3ufpeX*|M0AuXfkY(V#-T_XZB<JKHYVJ(^8HB!gd_?ia5$Wg zh#nw%z>J-4AQ0l=LFVSYIj_@K9<#napTK);UZg1Xnj(1WCBI3hTUl12+VuKhL$xS? z!#ar(gJW-bajzKb#bHu<ZbJ|Og^2EDMDBoZ>#EP|-*qsen@_@q@8W^L;4rW6v8N*N z7%awfxbq9@;>!hx5u=Oh#)vrg4boD-&NfxTlvy{(3(;`72?x6i@MWseKZ5!BQ4i;S z>qm|u+1uQ46_yHF@8vkGa&8<V`gzpeFtNn+E0Af@0mxPaQ8;!?)Y<xlG;iyOLs#%I z!$t%AsIViFh(g6KC<o=VN<v6}5f%2}DDy;B&zA_Mz<pR6MEw%q=}d7e(oQTZf)W-7 zm0q}%GL~u@bU6JA8>+vr8sAm1ig>fZI6u^8M$?;?k<>(pmi2N2^$Nvob_5cggITJT z!QYPiz_C<a8lzjZ%X@^WA$0~bbK@g?5FB0|rz4bZg@NTtpwcMNWsV&YW%+R|Nu@kx zHsB+~NVRx19WNWQQXXR}RZD#)rgVm*C6~XTJ&a{4<wFfyS8{+IY>(vol=s5M1?kJ% z&tGH_qqcpuolTfFVZWHno!njfVur(idg<Jg+f2`^Nhy6nsv}3GX=V2)J{33Cm6bL| zeHSTo=_n9rRy<K2llPAx^>0p17c+IOOW%QJ59G%W&i_H6`B%t^IT_miUory!z+B9B zX^YNAF$qhFz$AYNnjuu6G$N8!1&WEIg><b&Qtd)XZOn??WCr_zKarzAwkHT3&ky-N zNX8~cRcstc$5%HV?p=FxvCp^5Me`q*ZP$iK(lqKCwT(K|#_iFl0qrW7v_-8<6><ma zHMBr?LI!5c5sNY-n%ZK&SnodHXLI88v+aSC<Xx(p%7En_<h1*?Wu8;|Y|4$F8vT|O z&E(yPCR7HCr8OiS-e!{Q-13zJ#0`zS`msG}^z+K)GF{+R(aPO*pV*4<7V=T?Cnav} zu|11(za;G;mNE&m0wqSe4cM8vZfw`ciqE%C$-g>KH8?KUxf#YGe;e7h3}6Q=2bzxv zy<EEj*|VQ#)o!ftoEYu@0ATD7j=H8|)39sWLGpaW{es|mAzw|MI>Joav~k>gGOO}P zi*ZIjHP@wPim4cZB0J*;vB0G;j-0W4qv{WmZivHNfKK+5;S@YwvKoG6^nWp^GLz)x z5_C1K%qkkgUnvNzJbp``#hl$++psjNhE>1{Q5jQfiGQnS2y<{jWF26r<pVp7KClX| ziW}CQaD?WhD~`Z@*4o+z+EUaD@`+0Q%~Q>D?CI%^!y2yB_-+C?{ttqL|B{oe4NqPc zzoByV4He%1DOCQ;gp@dMha`v+`bD{^#HOvSU?DiPqSn+p(voQbGcweS#fWXM)y@hc zV{p}B?Uts7KS_;5@ACtH7z1CB*IWaZh~#3LpV|ELi`$&-{d_Yl?#Hv^^a#=gO_~N4 zP1?iSl(Dn{tztu}p!RS?>;QU=FwmWh!7%|;l1iwhHkfeB%kj+W?bAqSN8bVn?!{w) zz)bfJT0LB0E|SKRYk4lbzPpMK4L6Xgl<yL0H3>&fQ(+363z5Ta+(8dODt98yyfR5- z8#%@6!dAUo(lT_&I;4zguDgusiWJ1N5bvwY3hJtZ54>9-^w%v#jO&`coJuUXqsz#2 zrWqF|!2XWLPh`aX9r!rIuyXcna17Snj{ThiYee@+<UtwR8Q(GK0S-sUaNTU(u${`` zmcKWu@48nCZ$d>bnCADe^KE;L-%DwVrd?0@ryC>Y02<nP9>`CtG2@IC;2boA!NTq! z)CFgic4=0@%X>RUfalM<oi;T|TP?vJp4SzN)97Cmg(huqGv+XP>6~&5#<wF7(2L1L zuJo7_Cu<ps9_~P<!mWz^9T_A2Vq(4`b~ETOp21rzZ89LLFl|h1pTOu$42PA@4l69R zQf(VLTOIw&GA9S{|2eQ2JhaL=e51wwJD>gcfknpB*3w1P!_>so)a0L%Sx&+(9f;s} z=`!Eg^4Z$_xznai!Un-ELS##t5|gz6&Caxq%W7k8>K-Zn2Me!1MPvxX19El`bSOhL zVy-QIJU7Gn*{AnG_T>2Q9cCZZiLHquli|cba|mYWoybsP7%U<=lahD~6v(DI2BkRF zyv{ioJa^&O(bLQ0^LV>4##*Y>1#W0`)0)9pYLNg#w5L);kX$F_f$$DYh0-6Qy!!0D zWcMh|Uu4Lku=J7IH=Ml(<g0}lBA+31wQYC9C1mgsm*cHV%h-qzh$_7k+AyM{%tY)* zmB%ZtG^*B<=>`!QfJ^is-8ilW`sZRyzD?v#Ba^{;M(Y8b!8iUc#_GQ{l@-)@6Zr$I zUt|+ptj4`ZOJ3X1&|`DbKqk0<G4i(ytKm7{&g?J>Ib>3%S-_p7U#B)f)_!fU3eA_| zx->FKK`-Sz8^Rn?u8Q?cu9aw(y3aAB;?Dt)6Uj9RLz4NhI}x0z3<4Qok}67Hfd2C_ z7GLwA{{B8j<?o;Vzb{GT>|K;i?Mw`fZ2mbtq$Et+Av2<c#67UH6w69x&HdSIq@pxZ zF$jw6pd>&Q)s0W2bt+Y}lCZ(>rhs3-^?t4<YS;tvK*<F{0C0hb66)qB%yd6zKfK1C zUe3<y|FAh!Lq*jwZl7@qIyMV_g1`ozlq=DSeWP;S$V7jS107-x80NLkP8$InQz#$D z!SjE-d_Heh2plJ{ZQMeku-gMbrM#adAeTBf%7bip6wV89qAkkiTObWd`z>?qQ$HHq zfZ;pXa7XHnxL@x@V6$cQsPU@+5nhLuVCeW7yUHiw%<{gsHxMlBo!JrM3owVd@-3J* ze_-Y{ILX8I=lo!Rt^G#7SK7P>_Tp*CnVgfYXIvp%PO?4dsyCW6Gt0{|dz*Q0v2UE3 zL$S~_)iktdYy-T%{D^_oXT&^nG3!Rn=^Fx={+jvgggXRW0M|g37mD!42y}>W11g{@ zk7s@!DAzAZR;eIPp*P6r+FA{evH&NK%mQ^Dnb?;!u?iZd){A6#5j2<9X7_DVW$rt4 zgm_jT&{PRlr#Ne*HKaCea9ZPwdij?(xta$q&{U%e!D#1geD)RkFx+5hL%5HfSM~n* z2N?eE8x8jVI|G<~mj;%0=Kmy3!<-&u@$)+n6n>-VzcBIs$BX|e2L7)F#=mjaF^Q7) zivl>qUrE8~rQE?KSK5Sz6_)J;j!xN<^+#Srn~>AvPDzAPkc?7W1LlW);Co5+;eY!g z2AV*|aS#Sp#vZ&McKpz0F9*}=!Yz_oSvrd5L&dm?x`?-I2hE0P#G=eta7OnT>_UQm zWspwv7^;**Pga1B5Etc8*kjVN>g+=+uYHe|?vq_`t2lYqoZkiSl)1_(%ASKDiJ3CV z8*CUIypBmYi_AS*1CT$4ELsR{CT+lJ2U{yc?IFoAU!t{o5Kyaf=g`HReMV1X0p2gv zMMjJJ+^}|UU0CS@6<U6Uy9Up;_N^1Q8WDrqTOreuAnSP^^`lZvdiL@wVEgg&KGSSn zVM(Z@3-DslyqH-*7Bti4)R?ByrZ}1by=P<Qj`-kZ^STT&n@&7%-t{Qbrrz{C5Kn>} zSLb>>7x3QjEE~boQP1MInwRhf+HzGZV*ASN32T@&;zhZ+E<vYG?l`WwV;X+Zjz>=! z8S@j>9c$Z3IqCB)ej<x1?9q&a-Mwo^F4P^YJP_c2?Xc2s;^$ZVN-EP0u_g|!6v1ev zC!57MxdMIh01U!dFXaQydw~NFd!Qlc{nAk6PiiQ7p!mu!9Bt))Apb~)$Op(%cZC9? z2h96VQTX6{^8bkbAq}Aqs-y5pVdDHi&<b<Hm4e&FR*+IrOU|<Rt@L%R{|^-YTgmFc z36kpx{_S=N|KkVK{~v|_2TVe)mNq7)PXC6f6g6vi<Wof7m5y~IMh&n{s1d{w$zU37 zAgx4T#(p?-MPmtBhk9W{)4FTeh1y1~^|?~O$%6F4T=DNmV9AXI=?$aAv1})~mo?`% z>bHin)496WmFJA@78>k~MEFd=_PS?YhNsz%=HI{ket$9g<b2kG9c4u3<4z0D2G_-W z6jz)Tv%}B?YY~$aMHo;+V$Uhe7^nvj?z+OEiSCJ@oMvna>wnf#bfAl@5lIo@ARa@U z5T(Z*0O{Bak!tpm9841FMH9u=g|Dj*AFlqqxx9agf#{*U?IB&GXFJ$z%sjX!MWm0~ zr$+3;>USr4sSSknDmc@b&~<v$@$}OAjcSAEtCzLNRBC7nR)OcT{QgZJ)lljNB&s5` zy4ILvKpExQ>M@%$6~)@7Gqb?lvdau&Dj-PQa7;T3-?YOHhE~KSLvwEPl^ox9;Ixvp zd{|Di9j&W_<!BZa=liWSnll0W+Ud=vRF6Y<BxSU=0PC8B-m6LSPl1D(AC+~TC2BgN z@wzxF(Q}2GA{EnSbaV4HC_zDKNp%n>;Ifp(UX~=X37^8YOQRqM!Q|K|ps`Y|NORT7 z(<g=M$O#75-KVmZ2;|IL?=o33HOtMYIHG=fSqY7wJiJAx@>|R%LwB637@OfiH3Wf9 z_w?yN=!C2#Zz;xIijCesvzQB!rv+X}tke{e?D8nLcuP66fL=?_xu<kFewpmlnYt`d zZ6>`L3P&!vse`_O5(M5>y#VbfG=Qh6ejwOW`9}tEAoOcf`3B=tuc?;RdbOBkRCAUc z!?M>?QA0iB$jC&F2D|v@QY1^u8L4G0B4?JR&B$OZLJBXDT6KRWbDX`AQ~5^biT-Jg z%sWJ<@})dbZ!0{Y+CjXljRgF$MlIqfF+z8%@&GGS%|_U^caRyjca$Bz{o{@5*T)Fj zc}oqRmYYekp`JEdCRgYq(;s{M?ZhrKDS7lL67e^o{=|iQf9fK_b8rRuyLAZ={g50+ z|I?iP)fCw)(Y)G_f57}weKac2`Q<k`evaMMbl^c>0Ds7cnumTz;YKClmuWpla$|U- zCD%~DF2pE*7Ip*86$p>=xC3u98&Sw!Fd&W1YMt4^o4hJTn)5vj$>L+g!tEp?xZ7Pi zX(g}|B%YLa;lI_$V1H;hnB31oH;b*Vo_B{)BFyBooxxa3pgAhO&Ul|upgB#p<+7gr zNUp8Cp5d!gTgiQV0mGSLSmR2^dfS{j>wLr20TXeh#m=uo!>zek;5ko)sRv@OW!Ynb zl~0Ln8WYe2=F7v$*j8$<oyRT%*RW#3>-R0~F~^Y-JRmmHIIlVBrUNHVx^<seNX*$$ z3XbR81Z_QBNLTxIk>HVL_3FH+d0cpNIsLS^21=-HPH3vu*<r3Bs+Z}l^xJ92eF-=( zv!%xK)uv;O7qD2kHgfC7k79x#5Vqy`*=ZSHa9WFl8=p`!CR6B>Ee~FPiZlAQ1WOTR zApuGaLDP1US7bwmFgNhU$B1V$tcULpUJw7?4z-5dj8?DQY`51@fkx9pil!rCfyo?7 zV2aJS8L=$5)E2dQlJn$fN*+LsvBx*Ydho`z_s%7)kEDGmrGL&<%jM}pt?PRCD!8gT zz^&!Fe9@V>on%r`Fk%_xbHGz3MeS<X)3D$t%z?(!Q$qS@TvbFH*!|gC-o3VvSXc1H z)}||DEF-nA*C-~+qN6*$C*n-vh-~E=b|grfN@4V%Ujg)TROlCe9=a<9UL<qKNrOju zT_CIu)fz`?Vu>kd0U{WL3v=`}%(dalDI!_QkXdoM8GVaoYIk19u4JtnWA`?O(S96b zg`yRi#tV*e25F`wRd6&AeF+t&S_;f0xiNeVcm%S;T`rMZyhRSjkgf%&>PO$p(<{BJ z(ETxr<|{<Q5|LJzH}x8eSOe!H+!qk@^`T{2FpH}#YI9r($bC9d2(HtK@Q@bpS5Kgx zJzyNw=E2RqHh+7&rRN^cdv*EmgRV_y$V-N{$KDiC>kt?98kIfOS>;~8D2d(TWNU*d z6QDk#|0IS1G=<g9u)|TUGiv;qVVkeCBTMpT5D~=zW%sb`0TxD`yF6-xxL9E0xlukZ zGR>mBsnquH!&LLfZI5XiR10Cm)n6+LLv_!_6W<V@KCoB4_w3IgIbo++AEeqsQqry+ z5rFubFD!^O34mvwn#pCyv}x6Qcau_nb&ipU=85)Tf73fCmkF<{Tj0u0&HD#@`=#P< z=GgWs^g}u^GIyQ{eg#~N!xXR7)Wc%Sz~@tpThCmNSdrZ03g@f%>@4|6790WO2yQui zZOQj=X$AIUNTjX3La+6eou?#^4miurx;i4;?`PzP>peXF6S+>>F8a|KqMH~^dm*n{ zkzTprb!Fi_f3bcfVl9$~bEP{XcQ|FpnJ%wqWb_$msa?~e6svG;yVrWAx@#24%vYK3 zRoKUxb`@ycmcUy_vL~m3z1VC6-NTlCHKdPaA8VtQGzkBGPXS%uB>-pv+RkV)qK^2Y ziS8M*Ylc<onnU}mh5YcJ#~6p;Yx!xv?-ronl_Bl_Kau}yLHM6@xT385_e9w*RTEcH z(2t&^a7M@FD)b;JA)~;Eksx$sH*h5ltp&8?-jja(y*Q>Bx`}ZS-e&s^z>Z7l!{^-_ z*d9tJP$du>hz%qP?rZ4+aLC)?evrV}>GRkqS;3FZso&z7GzwOhvd>H@GYQBX6}uRl zQEyH}dASvbGCTzMt~EGR+fkX($rAL|%<r%67uuY%F)}}&FW58K7bZkvD#}LMHc7b` zb$f?c?k6nJ4aD;&dc3JyjDcWFuo2z|xtUuTSyd-mRPO;;#wlbbaZO#o-<w<jQgn*b zkmAG$w=($#^zy$Y>;Xl7J^_8}@K%|>v05HQN~zk7CO<$Tfq|jB&^#SNfaM{jasFr^ zoU}zEHCba$w#~3(X9V&d`_vhPFq+Wq;Y^~x|A(I6zxeYEilpQee1CJ<@7>aGt;qiu zG4YRT{9nJj$#;jDMBc&J-sIoCPE!82G0#SAG5~%3`{wBiVwiuhNoF%;m`an?5Y+b4 z?^0sm=u*1*Cy0Njmyj5-X#USSz9|o20Rb(-QP(M#hu40Vv-5YauirbQ0q83N$=0$1 zJOFiy$|K(p2fp32*hq-@)c9L+Ml{`6N;KG5YnWxUjH(c4Rb|RSM?Kzf&guBu?Pi0@ z9uvmtwR?H30rN5Nazn4yjxEbA$*^JV&vWAYNx|z$kghdbwYwX2`(*}rY-idQNwfP@ zDkk@&9HT_aM-^1cCPi{1ebtaYiqtG;GABj5(K-Ts@|s&~TT0oCbe*>iA%5l)+iEM0 z{9`8d@ld?5=xB@db<p=}_mo!74n?7bXBr_k{k_+Cha_oqs`&Rj2Hs{g8g{DYE*o!{ zIzU^3ZrE{G|Jbs$`H-uIHFR9M1088IdE4u9Zi;Yl-FaGVvq&m{!@b<H>V~{de{8z* zI><%7aaAJBjAbL&)SgS{&z|QPQeD9z6~A{ASL2#Oys^evL02qB?zx1)4m)!C0^$kT zz%&cHqMaFoXm=;MA%xabsC41TVFa{ts`Y#8GSPm+SHeKOmt*%qd>P&Q@6N0;CTbVU zk#jPt42D%$gwX#*+B*kH9xiLbGi}?pZQHhO+xE0=+qP}nwmt3c**^E+-gEYz-Milx zQ56;S$FCyldGpQ8C+Qve!ggi2ID#HM0N>3to(R{^tOYuNRmU#yWrkf#d&()i%Sa!B zX=J>oNX`?I;qyW9Hn{OcFZQ4d&lNm~_|}eK6Sp+eZ-;5WS0bH|g@_yX<I^nYHTV?# zu)w#FQf+tLUUZNu-ZtzqEnb!h+?U7amlC!z0I6IIJc3XH&>5-_ECd7IXfD-gEZ#sk z9$`Rac|aue1aBw^hf{Zj0_dj*^J<@;d=e)bL%9Sfp@{O8-Xt3cCx>p5$UR>KMckkt zEo+#uKV>Tc!Rhhr8wukc?|%}8zsS41-kBEdx1NFfmU)W*p}hZ%D8x?5^z$JF&m7eR zgog4CazkPuk`5AMeyf@MKB0sDDD-tm@<sCna<ZD^e$d;z$dvL*Exm*>R!7h2F4N<K zhbwG8P-)P27#J)p1c`yw2rm*P&|y0VcnixlgmC{W(2!W$j+mg~poMoKOwQvV+s3Rq zSu)AHm8$h?=R~PRCECXY$QY}`DK_N;Azo0E{5OFo=W@-R5Sl!aYt1ew!YkczSOG<i z%FA(}xN~a7>!}e2nL=Xh5i6tojcqfr6>mJHY8~$A1w4l3zPQYreD{5OUNB-C>nd(- zX2M36Gs#lZpMum&duHQ05^S#{P2i@!rS8~D(31kc{BvHHUnPMAU@B_VVpseC!CHLO zM2HVq%F$ir5I?I+A^Y3eo{e$@0fqI2E!@?E9}fP7^rP_c)3Abks~DJXuJ%8KUHp4Q zG5z1LS3UclHi`77i)F0vObnkoxE`f6wisF;z6?oSqZDW&pL$V2z4)vUn<H&-EU1}+ z!lpI1H6old6q2Gehytg_Bic7{#M6{S98-Y`*I~!o_4>)@d7CxC_v`b3_Q%DY;ty&z zonSf{I(Yyzq0F{Ci&8N<*2zsU#Hcax<f!`WX`-<~y?BFolY1b<1o4otA)<-H#Gtw# zD7&q=2c0rNqbp#JvTt`twg{c>>oE(RJO?8U2-$`cj6W;;AAWZFp?-%UY2WRWnRViB z%Y#;kI%(kK63i_H=%tMXat(3?a~bl`0ro@v5+yY67Go*)GW~A&(%pZuo1m4il~JZe zro)Fil}zrPiXSC9cu8ST7Va06BTJ`BC@_W;Xg8J%h8HohFJA#Ls}q(Pi7e$cGUSyc z;`_gDSz$9bDm5DRD{$6_1zFS=8&imcxGmA=Vl`WYm{=EJ2~Pb2=E`yrVBSj4yPhEd zRc=s4Uld2Ulo>+)tTnLcbPdL_6e{r2G)c0hN?Tr0gt-uKb>^BbCrCmu%g)NO#zO$8 zFfF0k{ho29g)87kp~PIN@!BpVIOVDyj;H>7Ouv0%!1XH#G;98nz}g@9u*J*Y3w^0R z;VvlpmLC9pde~upZ>rBL3^ioDU5~JA7Sw<1z@FLKoScww4_bnjk+@3$lI*O}0#4DK z82p4n5WD3i6iFUR!cDQ)w#b7jA(~1qMOIrC?5C)N`+Li<b4wfmcUdL6Y6sJG?E2Y} z?nE&IPA^KDiBCc#*s=s0?E!Ts^?{F7@@;hF)@eNa5C=M}eoSPSo0>?Zu{D(^>dlxN zF7D_H9IBC5P>iux|E93dEjdrZ9lIlH+Is-5R~TfFyZJJ<x-_oBR2e(Z(rGklkt>)k z{cHNIxpm=dlW+>qaWhN%?Rh!9Rn+SJgwth!7bJNSNWvX-^=)5I(rs4c&0Szn1w-_% zD8MTdBjDm*15SZCJPcH}(m`@>`X5Hx%+@2CbPYX0bnB*B)wxuAi8<zu2FNXvT6+}{ zms)a_CS#3s6a7i0QhH1QKy1b-=>;npp{^-yS?}>IsRNvXVP&T*8y3l<u<)tF?F6Ms zc?sL~&0}1Z;)Wof3#bRSK+&4Ad8+wxOYtZbUyh_XEV%RHlem`Kk)%e16)KN1H-vT( zzZWTIse`_#Ae)@AjgduaU(c}a1s>N$U4fK2Y}}FS$JL8&2<|)+A9RnlNGHkQ@kA<1 zbB{(xA$sr(q2<txDFNdNjWCvwn3p$)@73hfLaR6~4t$y|v-FBfbpblJ)Vb+>knb@0 zvT(3FY)=xXh*ij5*lX`BnmoTA&$bEHWjdl7AvC)7GT>IMe^&_sHUd11l7)hXC14X< z4Y+8m^}($0)0)@YU~_`CZ1M@epeo7qI|6R!_u#n`VCO%`2YW2emw>!ixms!WOkNch z24m+9M8!H23T1-z43OFZA~^aSQ>0cHOiyU}wVfNB3g5zOOXQ`@t)&>^W7NgRS`uqW z;VX#Y2gqF3E9PEoNaJhpow16kLRNb>+c~QToet9)YS~u}wiZ>|S6UsO&yQY=V=Pz} zN7l74-51vnZ>^qS*gEPz2csgc7gf~-TdkMuo>1<dG`z<E-q13M&!Mu4mDOAwT8=<< zFLzwo{Qx~%v+nLJ>UN2%*<yV02s-B5mG?ve@;F`5{Aye((lI=AzJb!&*j&KL4{2gV z@-#bg={bHp;t}~{%M`5X7)xpoYb?E)$%BHZxFBj|vx6*!RQNHWuLILZzOjyVuBPnL zd{1mec5NXWd+-Uv+e5og$LtNi;mam~U&B@&-Rq-u14JXEbhr4+T#Em90@yQqkQbnS zOJuC3V5#s~;d7h_>6_Sm4C3Rotl^IXH!M>-BvX5+6EP1Q2pT*YT^hmGNFbl#+9<u$ zT5&^g_ti)Vkutv{=KIkEQC$?b$ZQoPizQj!w|YBE=k?^D+%2`ae@IY3Fdfv8ruFS% zBm*R|ebiwl0;48R>a^gs#5Xk6mTp4RZNA5GRb~%jAb<)AIhfI4$Ip=q&L5DRmjoWy z%zW2c@|Axq{Gp_%*<e&+sv$8IwN4oUW4tDLgUIXg!gOW&0R6k^|KB6l*#Er^GW+}D z$$v3lR?v^172l8i{(k?PFyntdDrjYHY~%EgWanQkn0E{}L_Z%~@XlbWnHAL|48^{I zby;XIUMk=y3HrSxK}W^*R6n2_89lsGZEflv@($}j2X8g&564F>CoLy!w;=sVlbLpo zvRKth!6Y2%BCx<Yw~Vm{q+lw;J<O!!yMWzST8AtYEyf9gqj@VM^fRGjE=!>PMuj@F z{k?ww>i*0qvF*qgd$7-&4DDEZGq8jnR300mC?_zWifk3_D64=dmxSHLjevKczoYio z2rK-SyixP_-{$;wn*L4h?%)3Pe<o>i6A=9JNZK10ZpmSBxVd)mdFb)}3W;UWmaf<~ z&*zzezKpj&c_glt8SsjfJC<y?Ew-*xDE(+C0D%4T`NVUjO4VH)o9E`-xC}1Pz3t+{ z{zRS0WF<l=olXQZay-6I5$CnKNsMsl33#KK!E6ZyV|FNe;$S(~>Mw|A2I&}irOhs- zt+3obT^>FWC+$=Pop-o2Z`YlGUFGoSz*qT(I|2z%h+w3-;f=%9j}k)>bdw+A{xcX6 zu0kZO-(VnrgTeUy`QMar{U;dzkbr-JA?ojfkZQ(6vFZj=kQ>pI6`%%&5)c(^6^}n0 z6h(yDl@SbjE4N<^$B!AIZ^E;kku&n(0!k;m;(w8Ql`Cimxf~&7u97bI{PeIFt(PUa z0NwdpT&Ga1D11=mq%Pc~r*OVwqjC~ZXP6xEjk2tNpA0cO#U8_fr;IDeK3O!M0#@}Y zf=Wy+;y{z4TRLdlbZMB^UXKRjla(M*g9R)o9ekows{x$l+;0q;Ed(wdS@Z(<pMh9Z zAW%&H1_A9miNpFo2I4<Fy1!nJzxj0v|B?CU287f^5z_oAd=coKZ#@rvRuD3nFM_Zi zh^CXF?^-W~?lN|1wJ~Fnz#{E+3;02{zzLZzZ}6ypoZZHLGTD~!`t&)W`a`?(c6V1C zSeVOs&gE`$$T0+)dY)*(A50j-tc@<L`1m$5$5Ho=?Os%5g3V({&WGC2+yiQ8`YW~X zj5JTrjV-b6C3T8a!1oxDxRMRL6~mGG%6-XcQmGhfWI#x?o0+!@E`bT$r2j<`=hs={ zOwgY_P>e~`@(opm!4Mmp;(jlUA+LAINt2VAOqTTLYX&$-!UQ(wI>}D6hVHqw9i~#& z@wA&F=<V&qv(a*kRe_=q2Ca~5-<YA@KN%#4AtxLjT|E@Z-IiP8B+6uF<6Sv~Fo_88 zs<uvvQ(+xGB%Ou2dwzLB6?&rixQawli1XKTXQ>ng8XT>F9VBvmNiH!IV!N@0BBjkl zdq2;W&OCSF=_~nTET*~HI=@FfgT^o|ZRRyOXmO|+9ne`JHp9D`af=utPw0O4uIyP@ zYfNYSj*3fXuW*L_=c9&_dj;+MKI;De_oG%cc5wMep5=SB$O0cyru1fNI}9-scp<Qa z8LW7mg)n=7!w>K#g<XS1%5@^jk-<7b&#kXAoYF;zFhO5FU!?u3V?bhi`M4tQi;^^V zMp@72_Z4bC6c}o&-6ayJ%Zh`hu=X%k5*W0~yu<_{hcptXNn^54>qy~vJ6SreE1#2- zO>nnU_Ug#bS<ewUx0_$xmvo1iQ;Z-7hD>;;E-SD@DNdV+cdFZvOzh_myOAT^Z#m4+ z?2qPp9hgJe1%o}>p*je^MFltnabV){alucFd^oXX1ClBC9pM)a>q!s6+nkyvW*k$F zL>MPb?ZK<A6eb<w%jC@~B}<)-4N!-S)Thl_A>>kTW*rKct|Mf-!__Aw>?%oCR|BjK z5tE@2W6(7Ibb*7M=x6>+(Rwi3`(q^KjM@oXh6^PW$<dVtEhqBb=ic&I>M+isyi`Ku zgmP9Rom6%`CtFJi?I~3vj4Z?y+AhuN3DJ+3SUQi`H!anx4yHx;Y=|#6E6tgpB|;pS z*`tM@!Z4=Hl@Z6uft44ty^Ys$-X)M(u2L!^&j{@7DTBt`8nVS))rEPe+GJ7K<QcP! zV=ZRhHJh2@`;W};Uo8R=Vu;A|_nnXU77Dum@!kJR;OMWH-a2+hV&E?oiB)xTZ#Bq| z@I-gJ`)&LDc}irDaJRXjYb1j8)~ll?`8UUX-#mIyTu5oX>(Xb(s|VeLmuF}Dr<MWI zKG~q0zEN7IemVoMELD<vl^w=VLy<duyT0am2aVioUB8WiqiO41p<Oq<)9<UK;Uf-x zTWy|hlv_N~>-L?DdiWPAjhUf~WWV6DKLMl}y`aJVP_0#135LOV;kdSglI6oCOm#(m zBZ>$0?R1pmP#F=;VV82RS}ijrsv3*R?F7r;W4r$lB8Bt>o<tBAZK}$h=5h1q)&}!E zHkabO<qKYl@$*0jUT+U{FB%om*uVUX0>%Ei%k})eQ3>D4OXmM45dSMJR^D(x6oun1 z*wC1VP={fGLjVAZB!WZ{1OVc6iR~T5!wl`+#8EIbC!p53Dp#Q-`yz)WLYAUYCf_dI zFCg;}v2-V<Rz5W5UhsZ%%DG;C`TXVk1y~y@N`7c3Kj2C(WJe!n?nNt`qR<*iY5w6? zAx@>w9{jxtD;gQPLh(f$pk~Zd-G>6B+OjoIKZ@Uyt(nv+2LHZs67K4jW*}~z@hcP4 zd!{im-TWrhB<Xn_v$JC`wlae{!_!-puF?mhh09KD(+1snhWToFzR);$6=9;-+JiEY zO?TBGR5_F6&Lm9A>WOosziXz2x_)vo6QrfN)pESUHqza?VWd{gaWfhJ%-0>i*;B?) z)J-H~V@fCE2CSgn)xsgB#<hK!;Ako>+pdkYJbOi(F?fk6yUhzf*Hrr_R!Z-XoM@;> z8QB6tw!!AUN=|s@jO;!{0*Qj*L$m_MzM+y%cqpakK==`)gI1YNFUkxr^#qAXhj)7x z_!y=F_};+`Xp+f#Cu*e^OXWRjV&nDWR)`wo5fqGc^K)k8ygiU^{5v9Kz9EVuhfRLD zzV-<9=Ca&Eo&Wb*CFjjPZS3dUhG>p!MyZ&Uz8~1SySdQ_IH#-qf*=RJHho}MSy!g{ z{MwxF?nFo3=FGv8?;V6X%fP_LA}-&1x|-I!1&8XWB=k5~q!~6^IAs<-l&|W;d9raK zvxz1m(a?&ed|{uRpwg*)NtjTshL5M#J-RqHOZ-ws=d%?Zb--HsJ^hiP4Fpt~*5lJe zSv+Q2;ahDDS`h#~CTAPHl3UH6Z6Z<(Gg?uffJjf3C;W-#Gf?6#_+*WQXi(N*${zI1 zoQkCjZ{+I};foOIw7mdTK2YOEY~j44R&O9ak-cHz1ZY&D(2dTXn%c|Ium)HkQ;LPt z2*YnEwm`gm37R8bbj3cPqxP0!Y4bit1l&N7vxzoZ*p606$vP%|mF}rO{L8e7T;n_< z9!1Wu5g6Yp=6QZYA%jC?=iRbrlBAg`r5}7Zkw$T44R=~7#=|`zO~s1#$(p|)>?blX z>bR22xq6lMgaY%r2|H)c4h64_Ub@m3RYo8=^3zX3%MHXMSZFZVgPVF+x7To^&g!fW z`Tsg-Oeqac0hDkQT+ggg%AS6efX;kIR05+uXds-L`eG?|CzrE#5zG2rcD57MuloY_ z&o(kmj_Knsej&oQO8md_gTGqEe|U#~=LeiN0Q^uPz7HDN3gK7=sQ4m_kD&zcLs)nW z5zL*95hhTZQ2~H<rDP4?4VMaP%{-qu)H^=3sy`5+5S$Pn5gx$-NjX%x)r`481h}WD z_8x+LB*89;_L<(-@583DAx$gar1y%0s;OgmQr1j#jVoUgUf5A9YLBxbDgEgvx)_&U z9FvX79<oZ+cNmYS@V&TZsFF3ulIT8M2L`n393uEihssBMEujSfrSYCn;>EzO!V4b$ zL9za78X`y9K_<U(Q2)l^|4J7A1&4p~l9OVmECBf7f-1hp*8hP$K7_Qwvj@ZJa)*Nj zfdy<EZ{4V@n6ODrTXKDf1sxR(YXFUd0}tYHQuI;={`BVY{^=&Dn@c?le+uykrafV^ zw^`z_o|Ft1bp9Gh_krKj(8QtNuh_bI7MPqijaZf`{$QlBN3EPlvhSYmZdSO8wMylL zvr)DEP$|`rI@RicKoTr&<Xj!oSts0RO4_2TwB3RL(z3o}G7?AF`Gx+-xqrkvBn^;v zToT>eQ-uPDVFus>$>kP&t9E9l{SWK;zq3$`|6cJs{)gN2FBH^t67tUEyBDAt>fd&O z{Ovg*V-tO6D<?%oDOF<w#s6q7`76{S`i4%n4(@*yxFHp7#SIZ8?g;FOQ5n2h1c0AX z5Q}I5evgcD2qS!w1_ByK1w_`0>N=67v1LS{sb3*qDK5qSGK>q|O^l!NS=-u>{Jq0C zyp6Ek8dureC)*cYuD_pod%oAcfoKf@*i^Jea#FO%D%+_FBdxilkAXLo8h}qw@C=zk zs3?`Tmh5(i6W#Y$hF8G`sVm;f^_@UCfh;{(HC(=UeUFxzutleh;-1>;7I{doK5<-X zVh<tPIUpHG9W+DUsb^j{Q_)J{>~0o{K<>Fb-}E+5%e<{~pQ!r)4)J&LUcVz1%A`bP z7ZlxgcnKVL@$ZO<43K8jeyulOm4#YCxal)m+-c+rdG%VOHV=3nqM>y&e&FPn+2343 zc@2@T^Nx2PhiuoHKN_#skB@kFT&i^x)jyfGuWd6EMhnwkSsk;+h#s&Gu}V^OtgJUt zA?21nVPW;IxZFH>cRXPJ;ri^2BNv+xIiF=ABuF<tzSICu#ZD|y8tQC5bI1vBLyjlP zDV2iB1M4_&E7-2WBFNWlcKNcIRRK)VJ{)pm9<dKkB>5t@B}Sh;(ox?<IKYG^lHuT2 zD;%;H;zOs!a{w`*k|2-s=fHi!lDTwxf$5MFdn21Q=J9KKuCCi1LifR|2mTO1zWG9B zjQ%5B5{kDONr#c6%2@A!SDY2R=}H=sk#Le~;QEhas&L-fQMl#3vE)ZyK~(!}qMpJn zQy6Vswh*mCKbK)EO|bO<xjAL?lw?Sjvt*L>Ij258=^e_pOg^Y85|`}$_sXW-3?iLf z&5$?@op?lL*nndO%EHo1g{Uu4e@ZYz68dO!eFmr&jhikd^pU+P<mS>i^4$Dx#hiPj zIWg}2Y!ApW<nXnrwU870@;;lhAIytMO>}usfhQ~;>&Xr;)i&(J6dTeOxx!nUheP30 z5u#`zX^(DK@B2_sFX@qmLq~I(HvkYD&Z~3iZ-8m*S6>oY#N(YKpCdRkYbdXc6tr7r zZ)vf#Or2Hd5G=m@JiI-0!oM9$n#PHqJg4eeFIk0Hwja$0>AdBf{bi8XPc0+I<9U|z z^BK;{X1!>u<F5)@s0f07t}`O`1YqZ1nR=KD@~SJ-JQB$4S~8uN0nq#L^F@b*ri!;K z@jY|v6Fg##V-YaiktUAy#JWYDzTwk+#HG0ftC`#*CmP1_430h}xUQT!+WC256Sd*j zlar^H&j;rV`j?`GxviUVMP582-Gk7GdHEir4#L3cW<_G<eljoq4iisTfQfq{PwXbj zBShLEqcOM)(()x+iE#z%16z<TrC)!omLe8R_gQoeXmv)2o=-54j$e)v)Z#)37xyJC z<ST}N1V&)uC0{{o^(Rl;2MV(|424UDAviDvq5=0G1@}KA0WtybS0}ei7#_2`;@0rg z<@n?`O)lhxGVm0O3}DR0-vV^XgsFTFvf)~cIw;^{jgA4_VzU9)9^+BX=xRWIDoeOz z%1*(`*{$Om%7^OiEl_*!`A;+Xm*iWsn1qkcx6_*f@o$sOf74<AqPd9vmIoSAu~z(U zX7Y(%E-vJ!An1O`$14H`=%x(i0~Us0Qv@Bk3oJhS*_Y}RkH_!@@+ZPa&ctc>u7P*l z{sx0$QNohfjB%~w@``)rdV5>D=g-RraxYu5W?U~cGKLLBYY{=fhJaLQ>L50-A_epg zvVle@I&cF-J;XKrU;y;Q4X)wnp_Z-o4wDY%OpAM;>r@KYtoL=-arRdb>b*i-$_^rv z^}Iu+UbsHfZ`6kn74&@dmQnO|$K-*O79Hf)=T)-R!n4t~V*MJ7Y~pS~W2M%SA0XC! z_N^&R&^srE=;oQtzL{7ytlTz0oeYDw&BZ6kfob{j5Thp=BEmCjkY1nJW+V;<a;{?9 z*=+PqWY_%_@zP&(f%>`lprg&Bv#Kkw^#z?+?^D<zQy_#%j4c2Y%Z|xAR(fL=Xd%^s zlpTuenp>30%?ZwdnFOuNolVzheaWI=H|Y>n@Y%=tvR0lV<Q@8QRqIe|N}&dA?dmFT z9@!h4P*f}Ghnzqf1AG7>cn0dyA=@5&P>j-XQ`bNrsLB(V9N>#>tR%Ey;@tu6Ax*vt zJn`OIp%YLWe@t!TVJ)_|?xlEa!`?0{ImAVVgg7LRu@+psC)9QK`$9C8>~sg@8!g55 ze^x1XalpGk3UDdamg4^+4$0<l&eew_hc1~T9Yp7-`Lq8~piRDKJ)pj#3X!3N0JRVW zsN_9^X|n6V*;x07$xdrto(_^S$-n^3A~g;_9wB9@Vc>#q5#2SN5=g*zmCR#noTT=8 zfE72;;2Ik;uq=H;M<XK8-j+8%g~l@Kx|^leNI?lr`w5~;9E;Ez^mlcBFb<!zDNjI= z>)gG6w}xG&QKXe07l*h-ea$|Q4F6`ak^sH{jwoCa%N=lBN!nhVw$P3$f2BmI(;8I} zTvr1d=?EfQjLn${0QaGj5W-`e2;A)sSTtC`C{KVXG@KS2pIse70@_7M-OsZ};`uod zaKns3mC0~Z9{eY031>Z8B3sq9;(p8#xYz!8#-K#-z*g*I_GFS$H)a_hT$#!M#DUu0 zKCs^fi_r-C=BIhKF=C|qQ6=Q0IkOs=4Vej_-O#_-!wjh=#WTNtD98}K9j%{QFrIF` z&z-dq&{yxLN1kwuB|N&s()i!kL^V3rE&oephzjb+vDUXAXZ?L)MgEVD%D-OLfBF7T zZbz2lU$W?M*}#SO1^IY9emJ}P!;WF_@(2Xrzi_zHiz3x3ts6T022F2+xn6$oB-*#f z;=sk(tx6qd*mkGc#(%zjy#V<^veo4p@vj9y1Olq&&$`8UlRD+dGrCnc3$h;)(!;|l zUQz-+1kGZFiWM}f#(^ksSq$0k>$x5fz=CD7uK#Xo&q^6>L62m~v>6;(e{f$>+<=*2 z-=>4}po;5zR|&-YlfCCKu?3+>p1+&`2Jnc^WC@8(dw7kdw8@XFeHGk?JuUXgy5~`B zkIx_ex}l0%kXl2<R=f#5GC$OOlJF_Pu*1*J?~YTUE@y1lWT0-$=2doLMQ#hJ`paQB z_f0i#HE+vk0#zc<K#l>$3m(e7hL^w=N@gXCVvamRq<;I1m;`;1b(?F#k3U=|okxsg z01uG!2Z;%I6PR=#%@O;kqE0TCV4c`$cG=Yc{7|ciE(A<N+kc5w|3%q;p5FnJd{;RT zd{_4H|9?9_Ra*y3V~4*h-y)@rzjTsvUjT91L)lAGO0*>z;Mvc&{?sYwb0-tVE4q!H z=gE(1V6%yevLZcdaVu@*y8Y>eWUw4nY8SQ`L0n_%VltiG=H~nPb%)4@DLXD#N0s8q z!KB7<eZUV)%(W=DAh)8&cxs5Fb~Y<psT?B08qRGR6Z85GI44Wss#_>SuwNINr9F3J zjM*i1LszP<ra1s1_`AlPHT+q4Qm+$65Z)GuOI)abmYusXX&_#d`?nykw&|@UYA?H> z%Wv$EeGw+OM2+u|U&h=>cW8?{Z*JC_&EdV?uaWLW(hOmBXb?IsPF~>(>9OfZfs~j| z{r1;2<QxTF2=q%0RRE5ku-vAeXplUoA03F;VmRpb$r90|A7gx6Cx0+S0%s#c>L_*I z7}DCXW|AZG6s$sQ;D~)rt;3wqprKOD&`WbIa-p=#qeGMfLvG^yfwfAyb#qamj_Frg zKDqZ441Rm*EO&Ud>D<}DjiPI%n#|v(>R)XxqG_0>4pfo`r)y1dxrZ3b7Saxa@<7a= zg;g$|lfJPt+gg}>E*{njJqL;?Y6cjj*GK&DE-P~SMfRAUihn^RZdr(Ws@a73OV8y2 zF8mWYm^SS|Swq<&Xuc0?EF=-!2xkS)7Xl*$d=kZ9EOnd}33KYERjHRgh-OA<IgfIN zF|~+r1w1kl#hx&*dY8n;IMT*+&UF-(Y`)LcKDbc09?fi@!M%dbL&6D=W%2%Bgs_v} zJ=@}20Bycm3-A9QA^a<8sZu#pMG}Sk<J>?;Cl%yJfI}~mVx;dcToBeO#6D<8g{BSD zN7>K-9ZAJ_fwi0T-u<v9`#NljsLkAs`apei6eFR)-H}-h&|Mo-R$YBwe*UJc`n>b! z;|tVJ@TCm^8cptw0|c2!DN#>u8j2iEMLwj@Av5lgJBegwe2)E9T*5`DuQc2mEn(7) zc-m0FhXh%xl#iN5;y6BFVtyML_@<(CBv%4Kdx*af2`#=D8BDwcNs2O6{tiP5SXFtF zpg>Q?e@Kp*MnQZd6?wb$Y7*iX|3wSc-lLJz3Nj5K+W4ep_bsp>o4s<*iea~@i9v`p zdiMd?M1-^HsGqrXPLhUwNO&Q;^;!1KY<#yg)tT7pw3F1iwmKX+YbZ%Gmg*lZ7DQft z6UzYxq7g>x32x(d4+_G0D64pYM-wU5&0#odld$DUNYf!bhTSne^<U;`+u$L~qbSSF z(eyzcmrT~x6#5zlt2vV3ra~bDatXE2+>FJX#fOjy_~<x-UJo+;7jk)elJ(sqvvN%H zO>OZ%?|~iM^bEB@nx5P7>-iPN!VVOr6NL)FMdEHo3ryGRz8EJw7*Z2?o2Z(^HoXTB zx4ssY;Lf){yhK`x4Ntr#7*7Vzhu&4g-l=8TqH}%=c{GhjD^n0MMt9*Mia#7-ixm5% zr8jzyz?Dd+?Ok2muTZ2|nhoGX|FVzAS7?L)?^ch~QH;PyhcjMn+vh!FC%Sx^lG4D5 zId`6IG^>qUvV)GsYQx}q!8oqt3+<uX-NU=Rz_8hE4uEB9JP?G&-7}Qf5y`c90g{K# zkvLi>05dlej?D8hj?k7oDPOU-iuVnfVfON;O<ArP(j8EJN!NdF;9tIl?lxY1_R37D ztPxfpa7CycmM)R$d&cnTje)wN&9n({mL?M@nN}z^ZRpSEmn5p|#@dgPl}4rrDsOa3 z_Y(s>G4qoVG}?6^Kg!O^$)||ssuIHpr|i`Cmcbb@@Sl}uZkg@LwxHIu^7bT4m!FvZ zJVUodALrSUFeO6bmDlE$#CTl$Z4SdL$!X`JcQ9e7tukF$4KXmmYA&0De9@p0tEeJf zQOlFq#E2fiNgbJ6u_7A&#B8px1TENV^CE|-&BCqO#>~RBMk0N#$?)1K0!O>vrpn1h zg+2YT0~5jOebz!D`TLxhdn*;&-bqj=hmtbj1Vm6<qqUfDegZmdk$VR^8e!`pDx(_5 z<D6-B0P^v4zrJ9BvN(+`I{DmV?aS}n!kv3oE7a)M9f@}g9BYiiJOD>}{&QbCjvp(x z$k-rY&~-s#h%u9r#kgas**O1H9s?-#uN1L*>wNOJAIj`XJJ4L%MYh=LfV?3P+tF7a zY#Q!Z_S?ZNV#RTy_kk_;Q1?ko(FctZ!?S`)!*Xj*utjsCZfS-?g%rXcNJ`t7sbv!t zD3hb|nUM^gimU5BZ@m5quUGUD2m<(fERwbO=3np@XkS9s1Rk&(xca}0&O>~Lra4P3 zz95rg<YhUxe-o`|4%le)rsEpS5MYEHv8~_3)l^EU;KEYVcL>O${P{E{lOy<A5;hZz zAj2h~1ico>Tf`M6eFAYx+&>%S;}fOFk%Qsy@&4&-1{*((So(@3HDBHL-scX0&m#nm zG0fuwV*NoCeNW#xFfuomaJo;3hWCYaS0JSL!3C7li<A>IMLV#Ex{Hu?f?C&0yNA+) zaM&0_a47&Ryd?YzvB%I3C|4ZNrpV+FnCfZX$agQ32_x64&F6n$@-x_>vm?=yLJ$Q{ zNiiajJ?MrHoSH!C9@AwT8iMb-QtJGc&?t|#b|D6AZVO2mko#~4?~LW}kBx%+4n@Vw zf5`#<#g_mncPd^+`|*RA<KLF={rAM}Z)#h^T~BFgiTA|BM2Ep$AKdZ>K2WStJRA!$ zp`{uT*w3)o0F^8$9Q{!vl4&Wm&5Gu06^jaiD$T0ArE)TKu_lSR_9YF?=jG)hm8vR9 z&87&H?wz(9V}>*-f!ZBhcURt%&4wIK_qSbH9FlzV@^wj8$0RS5anrcIh_1tZVs^Al zGDgWRCrDMy&PhpjE$RhQ^v+F5lA2ilC@&R^&T+}WOGQ%5&UHz_RuzyEH+SfoB%Q1u z@8NwHol1HG>bAFKIv9qqGK?=3IvA#bY}!1gXkNuBBzD#`?`=9BKhtdJRvFv^Q@$IZ zC!#v%Ciu6B28yj<#5XRB3}7&^?&g_n;$-<oxhYc$uqKY!TE<Nq`ZPP4B3i~ntnb-1 zc}~EZQ}sACYqKtn9$vigKLOBn$hvC418L%eBf7l6eYLA!p!@91S#(M0XizDtW4Dv) zFDPEMvH-PKM!#&wPc&$4Pi(<QFN}U~+`mTmtw4TY9v(*3>;HmsmAL6hcMG(=pI3eL zyl+!qwu#%Wa(H2(-I-RMIC*b#!rZ~{!ae-XO(yDRJbsUr_az(gAsV@kpV<QWqh@k_ zci-iebGe>1@%#YD&@!_7q!sSizuQ&snsa#;yDb|e4m*B=pY~qX2aEX;YS!zM_L24o z0~RXU<%G^lqUTEk83U=C0|z9F@sZf*aj`J}9(?I5!qYQ(Y+BSMw)#?G<s$>qlRRS^ z1o<0;mo^afP^pXVL^#by{8%S>hEm5T$(J%`2mK?xuZQ&o7NlpIsdKu#CJHFM(ya=4 z%St%q1@KPH2{S<*8%A?37~hx5WaxVj`}2};zO+hWSZX~gwN9(U-2PINl-5)tX6UXI z9!_M?(cr!G*VI?|vKyU}yycLr-gp@_OUUQla_NxLoVWASy;x(NK=Jgd!O{p*emK!M z;_-gto~>D^20b5+VGgvRBs0%bHt-V#i-*A*iBv+Ccb}NBUIA`lumSQD^%WuHl{nT| zv^u^d%{Fl7JHDr~AFI$O=02W*+&FMDsG&m-y161ct#M9`8c|gDil5BpU6lO9PH&=Z zAo%M*6mrQadmbGcWDo&8<V6v=S??X6C6h()0rHnyAv;>BT5d+U7?IKIfJ1E`gb+q0 znV8NT?)T(9<-)R|L8d#)aqhSVjVcu|M+1}aasf7YDAsJMj#^)H-say`U`tcf_?XVK z{M-$^uIKLCrM6qO{ESCIKJZD4Vdd8L6%sj1c2d$Jbe-(;L@MzGzv_0^RQ?PqVMl95 zC7&uUC$EAOm(vo)*@RGbreQ;Y9ytDNXf>ivu*4svJw}7lWUiX}t@4QE>phCV9Rc)_ z*Yss3H#}WP*-ejzxc@E<M(2TQ3#~@IehuvtXs%_ii$WoU{M1&U(X8~?M!fZQICy)F zy1qQ`UbZ9(P|mADxmx_>9V`VzVOc@8nbN}XvWAy9O@GE$$c$xL&P^cj=E?0!bsYoH zPKgrJm`lDaAg`H=W%%y0yo#JT%ESb0@4OlQW(_*7spoyC*MMve{Ma`{%-uiO_}JzH z1Zqp=ZU}{;2x7>Y_Zx)<>QV<euA*NDZb*yTBR0x>3KmU*H2}-hS1M-gtL?qg%u>xD zQO5*hPFTyrtbbVl3BpU}_jsW<c$bWt0Y(nFQju_T{=mE7=%B<fH@35iU=3cLQl+|G zs(AW57XA+)qIHvgq~9%#YS8s<5hSV!P0;b=V|-r%zHEBI0xARI%ikCTlP`uXM2cu? z?yCs0Li}WI``#-uO!EWzF}|2@YCh?b4pi7}#ANo&2OH}Y7C-leQ$1uOhW1*sVrU&i z=;sLT1&0wxEQ}L?|2<O5BLQ3sA`a>ADB%fRI`@OGVj?wLNlPok6Fnn)5hMpPS_@`@ zn<_JHCbX1QebDZ{T>-&Si8&3rYr2-H(u)AX6_DmZ+-JdqlSfD?6YOT}Tc#jzwI_tb zCQM6epd&r6jdC$EG{0-8E29z+Im{S>ufUYoI6H09YUEq=FA*&Vkw4`2Kfrwo+8(@o zNTfaw6hOpV&jsT=%+w)@C^e~Wy@&SjOw^F0M_2@KDUw~DFnWEW$Vl5pm0XB^#nF|( zwa6)`-{pBLs%|BSU;K6k<S4r9MKj3PK4lUW0(AFw<%%efA3J*=QH?6@=!TkT1(*Vb znR^vhVYC@z0@@=5r%@N6{@%1RI?tYGAZAE%nvhrJy;V+$KN%K;x}Jo-5VSwgRuTU@ ze(jg_r}``J*-XD29J#FmycOaP`sPP|mHAA%i_!55SRgu1`}4&zsV~A^JrRw!J^jVy zvimfWoHOm067a8J(V!mIi%TGOZ9bciKivv9+ccgoc!!Aq+v{SMOnTJd5*%Di&{IhM zh7LTi^lOJ)37~g+#qwRI(j?scJ*;Ok775!C_wEQjJ(MgoOWVvg^E7Yx0Ny<p%6$8* z((97%QXG8NpB(3lawOamF?n`pJwwunR<>!6jvkx4yy+h--g@(&kAC7evyM_j=<)^O z0iqWw*y)dW+(6aECV}<h4gv`{#h`k9+LqttQh!)H%LMJNQW{2l&*FP`dDA{%dMxK} zPQ??}$^GvsiB$z?mQG=phU1tEgAU-&e45Ic(JbK1ZqhzXeRg(xQa@C2Z0<on@RjoD zc41LP`tz)Vf2-utKvjZ70xlyZ@b<IN-5a`XKe|Gu{mB^fJ@G~R2ph|}HwF2#aOiv2 zBmD*I8(r8y`^oYOX@>qM1vcFaEJcxswe#0CGtvYGNKF{VwlpuxnYPDFA@9OFiFajU zUcJ;&KVMtYh!V&j%=I)Vh^!j+;&B!B*1Y?4iWFX!q_TS{W<F@9B|Nic45(cSU1<76 zIdx2vdGE#zr2*h;Fy?y2@-6M~v4pl&CE(V4jNmLTJ~CWs+X95Mjk|P<NGuMCRK-3; zX=W9fXvN?V4B8e7h9w|oc{y2zGdKr+=f-rad0wR1Bt6QGRcnfzUNYou6dam6M)s5j zXl0Xf2j(Fq<j2@NKbQ1$8wVKAIIbtkJk}+sy_?eQthh202<4GdLuu#CbmQ^^W3_w= zY3G3SD6!=kj@*RcE|s5fO|@RRVrJ2gP}3|+TFf&_4`;@e5aZ@h?zC_)&||x`(#pgo zk}r-4G5$n(26ontCZPFoYL%V%^Ovw86Pix_Y_82jzct6LZ*;oxG0bTiStrTwk&%Z9 zj}*j*kCDQnseyilLr@-1SC0aX9j0+9@D#+UV2g_}yJN_*E)kfKn)$38qC+|1xL{h; zl_i%VN;f5L|H8d$3FHr-P1&b1yA3>AP1=#()_>14{`!-CLx-IN#cA0uy*HYCJ1<6+ zoexlLkOd&a9#2=U*hYieNrwPIoKxmld7RwpGFsZwj`~<i{s}#L9fDjUw4U@k!%oFi zJaFFhN)_<a1m@~oMfy~n9B85V^7$8rZ^L1OfkwPcGO8&Vu7?ymkLI@90lX(g)N<mu zq0rrZHb6r~D_>#T5N;Dn5T?bEaKCn^PuC_*jix&l?W{~+1Vs79hUoZ#jL(d^mR$j_ zilKEAuG)`P<mJHYrQ`vXT-en${cJ>gS$mzK4qMsoh!Y^r&+qchaG*T=1cui&oSH8w z;4)glvlv=*)Lkchs?Tw=9yDkRXjV~-0L;h0f+_~#A#R;74;~z2W(%1E&8^(EdZ@Zu zH+5YG7K1E{{qWPp&O){YK#AiWRuIC#H8?A)D2=Q%$a_pQg_<V6&60~&)8G9~%9hD| zV0Z>i$h83(*skgols>B7a*Hlb<AO#rq&xCmdkEO*8xzLyJsn22Prm}7DK+)z*0N?` zq#itKOzg_rT~o(Kay#{?h~rOjV+7XYr8^1;W7W**npMbp1?Zab@UBN)+_U{)lnuH( zR3s`R-4=uxuIV%tu*6V#hx}J%`NUC28q01lrtVg`btXM~uRAiGvLR%zGY-(Pj`oaW zJAlQ7@7Ez=bn{GBXFr4OH}zdlNdu>1b6C+dp;Zka7cOE(KK-Mnk~=F<P4z@s;gxH& zEP^$JV6~fLG*VuxE^ocKq1yJDqx4>k-lc2E4E5)$i2P;E<4%4@##vT%J4G2YS1v0V z;>Dn|fNuW&a?>MR$oJvGdI~((y2`Fn)6%>S+W$%=lCrKFG{W!UFpL~Uaj6L%y&3(Y zox1<(QkftI0U4m<kcSu?MRcaR^qNk3o;%;h(VwDj7rKblt8VpSnE;X8l@Jm$Y~P{X zI`VAZ<+GGDHO@-c+~9Q}0v%`v7B$6y3(=!NLl~<L7a@|25;Cm5b7I^QkZPId8;>K% z4!ti@7}<=ORdquBl2bn8Ij7#rf10C)I6Um;ei0lB^GRB~^r$8@{v>@7w9dr(gX9Za zQpp?8^GSZeJFxRpIwsyir=@gy?ydF&+ymjDgf3l{CX&DDhTowIv(bXK?S>TghIpX( z&fKfZER1DN&mvzp*L0oq6#b2O-Sm!>&ZlUP=huzm8w!p0_yy$^unrcM<<f1)1}C=L zJYsZR$g?HrHbd`(K*kj%b^`di3WHfjh>F>bq`-tru6PX~mORF_ZRUH<b#K!dl_Q>M z5#JZcX(tn~gDgi8X}!{;S3D;JAwyHqUBK{bepJGR|3cuac6V3nOkiSz4XyhIIjXj! z_Oi~nWy#O(N*9dKFn$L5R_zJXQ4TD$b(w+joLsIQ9*k`n!u20{SXDJl{DgHSKWasq z7R;PRay^weql8^I+vz>aYO{u2ugJzZmYFA+k6qGV@TB2-5h=I2xtY3qxA+r~mXKQ! zogRs4Y&ah^P(@W1<h*D>KN<7xmYC92{3%Hl(U<0_+5=1$k{W}mixW;UDzzj_=3UnL zR^$`ArBCKudnYdFJps3bSm&+LTIOV?*4lPQnr3TKPK_NFf|=5E`qCxc5)YrMiCSFe zDm0?!5xY%`ZoN~EJ(CBXcMPswXPq^Fv9P!EAU<b)v}$#_Z3QX;_`G<50{~7SC}r~k z=Kv{R7L~<ti7f;%E+8@$NbcM-Yr6{Ute2v_nDnl9R2mcu(oO`16W%!T$N0B#_j2M* zB;}@Yjx}KhZaP!Kh%NAs&@Cmp&elnBmj1{(KwIXjm{;D#$o#Dn5N*r(=(1K|eOTp` z!mY9EGiZuCNGx(wAaVBTS6?VT3+}?GY4pmL^!*!-3cav5K)n3Dq-Hi7d9HPq+XuBj znZVO+4ex&>oWyOlUdg3{dZJ|L<ZmFi-ffxPgphV{j-hI@;sD_KNVxytMdC5z@RB3` zn89-Uj&kK7-}068BFKz@Lux_#tJ1s0UftrX47gtUX>UWAH}|RF_^~*h4?C@1<DNRt zG9(YrVLp<uwPUB>qdX1)PhlKP`j3cvuL=4BVJ&}+0b;DoziLoqW!n}%0jWJ06Pxk3 z^SUF?`eqQQ%7(XG1T;r%yW}dH`3e)UBzo#1q>9RRX90{}{isKNluh*{J!3jC5HXLJ z*eIjODulIvDnR;mCa$xT;FS8kzZ<4fDWH5F{LK?2KyCXYbHFqo87a6nA{bnQ$@5VL z!-R6|ho(3RsoB`&oc4T6)xP8C86<|lpWsZGqF9a7-O=K>M^tL$;+}a>n44nGLKDEp zP;Vy#jHb*a0c#i5C-2a<7>=tA?XZsC>BSbWi)7r>ny7&omF5K_`Y9mlE&l9zQP-6I zAN8t<JQ`1-eD={Nst2*wS`!FP2+^cw>@qcGCGP!*X7bWm1DQu)B$SAa<w~hJxe0aF z&YVl*6kirs+9gD0(%P6uG|%*><r~%mU*j@9=_wBoYbm+T&bPc5S6^iY{U!8aYwOLE zibW++uN4zN4<54){Y&_6BC!{V@b@OO4uucCg{3m8v3p*77o_YbzH_Q98%=h1ULX~! zvIhhga6VmAHGpgYOQWKpzvvUP+moCjsC6P1F&P^x{i~6r;zpiIFYHyW<1Eun$t?sN z7Ce!$u_sw;jE2iBz~gj$D}wT&U}zLOoYBAxS$AM`^0rU7t6YU!DLfy0^CXO*b>ab~ z-3{@Q2yhZk2V_g)Jn%L(CydWx*fxKM^GM%H5FsZ_w;BX#OI7vNE+as5;Is))mc^N= z^DKKj^ImKeF1%bQDC#)G=KJX;w#Ejh_*~1a(^me~>@^F9_JRnZg#<3C;OZqM<&LDf z^<nkemWv%nXX&U5?VZhdu=ixa5D~g+k`3yD!j_Fdv>R;IQN$~-&zkv8{?ubCL^+&S zMkDnt>%0z7O@bV11*KU-yO1_T6?U3CVATL?hE}Fv+hq~MbP>8$|HH|g7mnD38=P9U ztmz$`ogSI?;J~Q^j?9KT(AFs784Zr$v)#|FiGtEBQN@6fc^@26(AHR-0|Ji_(pg8Y zFrB@EGiaLP=e8i8BZaqsi8DA^V_4aPpIliF?jW!ShVE3jBMELOs`qzZ2;C(Q;8u55 zHv8;Y$FG~?kS|=-{nn^=O`WMd6Ou0~JaHRt<Tu9_+cOg{>eWAKm+vP!!*!1mUY&KO zZ0)Igrb=$#J41MPYi>z9qkDFLkMAlj{rKJ$wad5!^i*N4RYSp^d+Zp<5k}pV%u2?~ zS`Lk+#2;M_XR9z}rpZ<~y>GHKuHO_zcn&bBJxG0Sz$2;MPwz}_E3ay&r9GCh<g#7P zEf27LO+b+sb7p(X_1u5b2^2HFyGro}>MdYXE#%}L!6Ur?PEkKHQ+vMG;(U{HstuL) zC*n)mMmcQqjQ2L(hB+sC*%8OijnnI!jjpi1I2`iZ#!E5-UYOpNtDjS0<klINMfkrn za)f$=zW9BF``;PvVglAEr~<_68Q%Shp7Gkg8^Hjqkb%0bp*f9;*ox+jHW2Q7LL&3^ z381mt5FZSd!qhh1HYuS={gd7rOW?K?(J1W{T(NgMFnBTxX2e7I>P2w~S(-^J6$Yvp zpJxei{ihwVh?tWTH_<IV5vxEy9!>5!_VzcDUMA2mw_n|d%^pybxlAhkZpQQ6(3$*k zeIl758(z|f$HA;awc^|)#g<?S7k|<I#>j!KUOXo?_M`J1DdPGW!VX7e>$DI8?ZzHv zciePN#prH@IfAYrvKMvc%Tprdl!T5s%6+zMhqm~btu&1E2)pVyqPWv+5dt`kWY4NI zIqHNY<vLI%II4ai=fD{6@Y*iTuY{HO?tK`|Q{f`>_+|N=ni703K<Fb(bE+G$wxQNT z>S6P(YoCapV^Wt8jWnV=B9@H}4abq_-W|n&h^pgF{me(9{A>H;wDP4A+voWPiAsuI zi0-eROsTdY$St!*j}xyf(owP}QS9ps^}V5Y?d@Vqg0#{>F;<d!w_I{;p`e`Qju_M8 zE@?NE!e4Trt~b=!nZNN79-MSGf4+bHBx^%Vyp$-zcu43R1;wu6L~c}LP^0-nyQ|k8 zQO`t6VlJiH*IsQ?AkWMr((Ry(30=<LP9WZmb2F?D8#*KC7V8p3r-FDe5c)y`_h1P3 z@Z;!?o5}AcI2OlGeH*l46Pjf!#`0!PXgWv&ruRB;Un90K?G$@4NLB)Yhg|%H=t={R zsr79SW=}HH$wSJt-6^V268z01pn7V@=0x)>{Z%d8B-;KK>KGnOlx5Q5*HwtgaLYV` zfe!=_Z&tq{p?G;5(b6t}5K~cK^fcI0yHo89z-0{#qze?1MlTL<i)~;RRL54or}o;? z={O9?gLQD?Fi`pDV-}FMZgm8u>T3aTwWyE}NMr$6NjTh(@OiDlhu#MOqbCCF;Kke^ zUGco}l)}HfMh@VrgUj#0*3v0-pv2hKED<y*g0Y=vp~Xxt6YqyPq%Dg&5>~Nc>I}z! z;aje&?}9Y7iG^+|KuQR_Zm}#z*=T}qm>CuANgb=0Y$O1KR3G_wVgDMBxn-4YQFbDr z4)lN#wS^vbt}SDv(+S*8KWs|rBMH?FEdR<5dAN|9V=YbGDNOqiE#G%N75ZXiNOLu3 z?-8wd_0|~h&F7fR33+9tyBYB!e9L&LNmrKa+dkqzzyZSikfGIFjWg0!jQ5rvwYTb- z9YoAyR&QqoM~{J|%8INcP59DEs^-n%^X1{r{F;>%%N0`K0pi58@=M&-l`CH`3$3W^ zsGRqPW&rzwh2EdhpVlqJqY@*a;zJ<-+ma*5Lj9pno?=YF`m0Fy0InY4yl68qMF^zQ zAv->A6D!%|go0AA?JWM!q#X?IMPkOdv+Md;ClY{C{14+2PMYMLOW?#0m70yTTkLrp zKz;pRADDiDhB$F1G9DCp#U*w$#7Sb|;c>_Vi=f7Y72MN5o7sc`V{c3Rg%nZ<yt9)M zzwX3u<jJFP@nm<`WZ)_<5kSt<C*iR9iSRvs)M=^#53+Ny62_f9U!%tDA2zt_+yhsM z)$j>E%}Im5Z37;zRkbYxJ458JdG!rKp1u>+u}9bxWSqIk!btOZg1SJ@(VhPJKh*Vp zXG;FN)<9<`bE|);>b<B-O$`3aD2neK`S$|9fBTexzN0Y<)88vGl9V)M5&4n6icyno zQr?^LLqpe-#}$M-k?7<21Qa9;GI(ALnm6jGXgG~%L;pY^+TQi4BKjuYP0}PQK%NV7 znoeilaPFizy7+v2yg~E9TarKSMMD6qBeNeWQ|05exFS=L&!R694(QYd&mGbXD7o*j zF*t45X}pD4yXmlVk<mX^sYf5xFrKcF4I{iYJEFZP?;H*Dp8@DqPsgspNl()$HDl?3 z!(ePyp+jeI6&UMdxNO-`xA8iYd~i<~{s?ToV%vmXV{<>A=(j#&n5t;$+yj4re*WFt zV)fa!cej)jOfVLSP0J|ts3aCd^Bm+oC@2w9)F9!4`M${o4J9ktb{zng^E3^BW<tZy zNSB~r2s(23n!bbmRqffy=|<fnb9a7RHwNo1$(9{p3@jX?v}C~^g4-HI@udn~lBf*d zO0rXLwC?2OF3Aw@(Rh~Hd`nf*H1Nqir(`EP;9Y5UDk>2e4ho^d%r!^|9K+1TFFpW7 zf`0t9j2SZNo`+qfk7mhg)uK%s=Q36U<|_##(?DqSbl(XTC~Y(UvLuw4q|d!!Pnnow zL2a$e)P?;8CcDDA^$r-Dklj{`3MCQoj(ou9!jbW=2(aay0us=PVguy9F8Da%3x_@R zGx-c&?vz_^V4yzEXy~+rfVzjFK6{Qw#**W^^B`IGBXaef&dl%YoeXRY0%R;3%HJGH z9|^3AK5~IQL6HwBo+?*4-WY`!A7{q*$9urw|0?Y(;Hp}>x9KkFR=T@Wy1N^RLpMhn z1VK8ayE~-2RJyxEly0Pz@_)EquUB7hec${zZakd*JTq(7teL&fp7khv;8Tg=c30uf z?sSt92!&F9{YI!dG};tdwcjJjjFHr$u>T3dWSprlx~anpv7wim)}G}tTRPMnf!cah zt(edJwKhffD6uW%-(%aEke8(q*S>7}g7f{+&ijPWaxJCWwjd{tg8R!&VZXWm?~W{O z=kOG$3UmS(*?#?sFhX75CKiC{^UlX`boS}fFX?Y|W+K3KVfTC-o`+|r-l!o46Lhjb zEu1eF*&Ix`(Bg~`pfkzI1>^7rVgyUKB_VWGiNjlngbS02VPJfu8t2Oi!EbvM!#w(V zAtD*gBqd|zQecbcvAyT{p=2T$`-d!y4GY&*VVe<?9c7Xz_#LzfnsrAK-2j`X#su|Y zn3yL47>cCi^+Y*`n3jotg@khlVk{XhS+Y4nNI-(QTp_d8#}7ELrUapTrAV=4d(j;= z{P)AcQIL|Pp>+*|p3{}S82a?&s#3(q3)o1<Ip@f3TY#A+TGhyN<RlSe_DL$3VV?z^ z3_Uu(wWz<i;{n@n_M-8zR<pj5wC#4&$I}l&^bg>ABqb*GE!+88tI8#Xk7|9fWU|t8 zJ0Gyk6Gb9b<&(#4qoSS%#IlS#O9*&#_eh`Mh#L*Pbz72iL3yD{^R}8YP6`EveB~|9 zkQD9svo1MUiOH&LAxUmPeR-)ilHXvYBiYhrcVXq(-5Y;wC+u7V9a|z-W${l0YZlQU z9r8mRNqS-h;$e;vZ0%m;#S`P#JS&vB^%Z2eaj70ta*h?XU2ni9Nn*#jThMd+eVnc3 zWJX4F?9d1qrye~r%}1Ora>z!I)z>05Z7L#m)7PSmLB)dc(3=Z0*V7`|tg~Pq+Y=#6 z(VGhr#G&1I@55iJ)U32#4*e1#{_TfoQ{yf<6r%HpfLvv^-9h8y?mQf1h;?6);RI9G zXw$`3O^soy;gD`vZ<<mQWQ4mfkFgG~`5zq$FUZ{uHr%|1biD4ky)~m%pPZX3dQyQr z({G&k&OGumlCxhqkpsh;(aGf9EWKdir%lPt5!-GhZb97lN_tdBtf;wZhHNikQ-=a5 zQjt1W`SHS-Mt9<Q4hN4*mOk&MwRiAVi1t?|mmAx%v0s1%zBWkp<25JJ8=j&u^MDsK zDFU?ddm=s6g49As&GeV56k?pChn-Bi{IqBO<kYhu`?G*}WR*0o2TU_3^77F*PO;1D zFQRk3M!02@>{7ty?Ibh)dQ0vbFUrxQ<QLBPpD!yv;}LBm&+W)_TM`a#L+x5X5Nwn4 z?l9OCO7CYiR0Sz$&v|RBA<lU^&56fs&S`C8nT@E=UHCtNRiiU;OzVoL?_vzZn&7lN zFyMuJxTuHoQK<BAhDu50=;>Z>OVs=nC<D!=&xu@zW`+Bw@NAZGGv!K6TE|ZjmY8Nw z8sq`YHl}N~C%EQgd39T)^`2BpLr(1t+Z$C0IebS$%O@F7041fPV;vq==UIUs8aMYR zVXT;rrgV}p`5727t0tJ232{SNXVp_Iexe<pblr4X=W}S2cGjp(5;QS+BfLsKh>y@b zW70hno$wwZNioSRicF~2!{r^8GgDIzZ>qAXXYH?N&Oe>7^+1Jbi-NcUqX1$9fGAi% zVh;X&-ezC0;jCoay&d$0ZB~&pc$dk-?R>iJ+))SLa#9h)M>2-1aNaMBngf-aBQ>|J zTV+<>b6jCp)YzxlJ&CbfrS}ffz5>VDk<`n{Jmy*M;JAY3+R^k<eEmW3il|`+bt(IG zT)JJU)0M9JI8j~bVF^dz@<PJx&775zuh~1K^via926{_sVYIzZ@HBD9hHIiT?~qP= z*md8*!HikC=iWpUaH@|qbeEc@IxSv09KvJk@-+yyIJ+g1p`A*|YYce8l-}aJx2tP8 zz@RR>Y&;jWyFNZdO0&BKNusFs0^j))*m+xJFHSpr%q<hj_#m8JMF^0f@RGpNz@OF{ zzNnLUrLNDnMYEoX^cIj9j#!cNfPnsnRKQrBYC@uvH^NXZY&y#GldVNfwY^B&xo39c zg6$3G>D^Chy_4Y)m0dxhyA=K}pUVGp=vI6R{3&+FYb@Gh0x-YGyVje#^0p~0kOJR| z>sYuVzN%H*P@qekc8bOh$eEa5w&Xi-ahXqt4uNm~l-1)FX8aKx(%@rE(#x@g;0N;T zBoY{F6vuBrm2_o2^96|_@-5p*Qyd$O9`HGj9O<<)U2hCW34%Ltso>*R>5IbgJ*Pjw ztd^CM>w^>_#+;xsZFngla$z+W6ePC5lxNrph7x>`C50LWl*;Q2cncP3QPfEmvm*o+ zh44&{m6!tQ0TT2^LAn%`JoG@;b0Ny~8bUhT8vL~yHy^jTAzEs_iCiHj3-1Df)o|J| zl>}Stc()n$p6j_7lOs1!(pWvgM3#ef7kK-5G6d@M=&^k?xfC1r)q+kQPM!J<8;#1w zeET78yIS3yi#B(vSl26}NS><f^DKzE*%w+2@E7Y;t7a~5f(z$V784b4i~S{L(3jat zMvA(%&nB45dTbVlE(*f+6okqTWz8iQDAvX?%=i-(!c!GI-&Ls`2uO_VA6lhAiuf_5 zo7W<rny6K-KOXCaYX2fO;{>G@R(hpE$(59*T(gyzRCf?XHI)2LX)rn?X|F(wA8nL| z3M?=POAV>;q-h*NtiG=C14c10a!m~|tmo5_cF;JJIbx2ni6YyqqWsAs;?2~9x4b0E zM8jGICdDtY;P$j4TYyV6DzPusohnsq7~n8gdC{eIEwE&%n&Ao}?Ur&)aLGs`b}N}F z(YXL0<YbLK!zgIAh-;z<Xz1`1!tu4qJ|xXo0)h~?JES{VX5gwFUCwM0hHsG;EveG_ z3)sv!pR%U0#7MLCN9tCp_j1~(quo9{U3VWATH9oP-V`)w_b`;enjGDXadv8a{O!S3 z@xX0Ssy(=wH($5r+D`LnJnBtZ-;Ejn%!pf;9!|UO`8$n#B+tBb$P=~Cf*-^2KO;cm z512a9$M^T?jhs4vMo_drajV&CnBX(q*kO3*UTTdh6#eSSLeB#Br%zB>QfldePUBDY z4k%y_BzewFkoS<GxoS-q-pjNS5Q;s2b^6@9`>of|TJ$#i0BLs0n_{*{=MZWu<X+J) zb#h9g`zVUa5^EG<KZYHC;bsmwfqNMH()Z<)fUXj8k=UZ(07lVFu?_l>x-q;V+#B{t z51H%`N;h*`n*w$Z8h?EtQVUHO_`tFA&YP@OUByP<S}nz;Qb}@4Exf_j^Pwiz4fs#` zU-pn#-C%fYN_uG24Cyghddm2nKA~GE5gC7sU_!2Zk(c9~gl&FjS&otVNz)OJLsjch zCX|H~Z5z}I9=#EE(GFiP)s1{a4_>Hu7Gly9Yk<jcY$31pofjgXePXR7ig(Pkrj%xj zB}^a_`->L_T|F_u_E{$gyC}0Win$+lZTw-~&$0@I&yY%Xw5P^SwZxdR;tkd%UWW^u zA)t(x;^t3sOH_Q|Zi`d}qKuBar{!}U6SqasouQOFHdpNEdx;*cnO}juo(Sp|))Qtp ze40AKz!<sCduWpS{G7gYXm2UnhB_RoaDLPLod#}5oCX#$!^Q&RRr9pR(;fG6R{Rpp zAl|~tZ5s8akE0RuB?-q6{^d^Una1GWfjU=&5ogdT$&x!MVP#=!v(D!PcrR#^L;Dsg zLylrrWl{x}Z0(@ic`QEfoQ!3xYpuQ(mH=Wezi6{;2XGXbav(s`hOb99k!e47g89PW z{{=<;Oh9KF7C08ArTDn%&AA9IsvS+1t+-IjGBWI?1wt6vWUEK0o&cqKO$dHo=NY2m z*ovE?eO8Rmmcp%ajOhn{X$`Dco3j`TJCIWNsm(rZlhPSZ?;U|~vo^YFDwP>(FFil2 zzJc6jGTgh9C(zG7!q&!UZ5WiXo;9EO+^JHwBfPG-9`k$ur@dA83vgb<81O)H(3PYb z7#QgA4_A_37u^S*?p|@z(_flS`A3ILS>K30c_fNXh{hZcz(8U=heR&K3@_DzUKt?G z8sNu_5euIhtW`<-O2<_^|G9;plB4XH)>v~L<I{>6ow|i?#&-R(^!9v)qsxrzp;&1o zi|f&&OW2cs9#>HQ!^<@1(aWVRJj0ty49Q@Nm``;<E}_XWLow?%(xIwCX0oi3G3e4V z6v=e`{HVj|ryGr|QCg2iqVtx>XvOmB8RAT%CDZ7z2T9Z!l@G9G`PsGTpN0U4@o6_( zx|~oyi$}<oeN+n6+CkrPr<N^?TPKJqa|+R!uNffI@PCh23#Ff<fW5bz^H$VEtdC+w z>K%2VHXRPOOUP4Ve1;iMi&l%60L`Yi6r{uhIH${`wC*+JTP@;YvbD|Pax)A3zyz&b zY=ULuSXrBkU<TvTIB!3_Zq6nx`VEn)Wd=s4Fd{q0DqSzt(04o7ElaED4WANp^=JzX zXmy#K!v;4dTPQx)O90b^+Pza`y?RAu#d2l!K4b${Wu(hvbQdUPOHEg3Y1Ugnvek@^ z5kXen@r;hIf}XGPYBw36Z?D2MG>%i)ohap|jWgM~ga{DpP|@vmF~A_n;t)OdSz@q@ zZ)q83xM=RW)LV5)oZHGynmfwY;PDP?6peud*<s{r5&upx<wT+BtYw{dL4VZ_RUc~a z4K~+cf~Vf9K~$q6A?CXN9JaMxn0mL4$Xn#11aZ>r1RnXfGc!u1SyR<gDpc?%YK&$2 zjYZ8iMu7N7%9KSbX%=}L*x_9XFsTb7DS8c$%q=(4UQpy$Q!3HX#B&H=wPFFnOmiV) zWU)0gW=C{YEID?bUnSC)rMETKeo9+7sUDc1o!`Y?H>hrGtuJw7lx2f%!e(To(bv-< zEd>{bqzz3x{j_j0G2lLZ3UKD>O3Y}<Yiup4FR?M8z~<HE9QCanm;mtW%m_T6z-C}f z*KNjKwQ}zV#Gj67wKF!daA(utTBiBF3L=Y`WFD>l44BTZQx6N>iW{iH4|hfymOdp< zi4qmFRe$OTkUn0GSy(+0KMgZa)IfA9OJlH5az!l_f0{o{>5|OuqvUEbG3Hm701+cm zFmEdj2^$e3VH5JgC@aRny0V;R3QWDs*9l*=YPDdq?rpP@+{<WvPK|Q8OblB%Bgu^q zQbpCaqWVSoI)Y^94_={iG9cFn0G6$Q;;GuKdN30*BIn~U6eBzWgz&v)2ou%4j4!6< z7MNk62XYxAF!BtnxnAt*<eu+|R>9j=IZOu3znxJs0%zZc7k@+^_b?1QE2hoqVqhL% z1&HLGN7r3MM;LyGZt^<3*0u<NONYb0p3n-_wO(RTXSb>+ZYJUCh4oDB03e?;K*!{R zloBI;hbhDO#-T<Y8)5Qty%{os70dIJ>Ub`(j&XP=-gzRc<QenarHUE!g-4bSv5yQh zbF=s;2xrDv7#|`bLmQfu^H}>cNIQo(MDfIUWMgpjb~HcD_Sk3XMUOC2C&8_zX;oiv zT$pS35_rX7DJ9bJvK?9|F+X)r6y6-y^||}NRhm@5KC(j)gJsGxiZB2%cCoIWlb@sK zP#@jW*4i$zzDoo--)Zn=8oyozqC{2s<o>ZrRW}2}TZVo=RLF?t4hWuV&&)QM?Fil6 z{r;uV_7;vMe&L>41G2gz!lZeIVD&j`r4rVc8y%wWJG@K!9xTvX6aha9woJ~SU++)k zOhnfepxrEwc3u%DCe*&%Evh3f)QjSmqnU}VPP@bka#O2dXr6ulh9modf=W>RIcsfg zNvAqv;znqF_mn%v3G<WUSe9D&+_7a`-9cD&#_~tl!<xdvM9yG}GAc#RCCX?;2xw+P zT7n~LB^=#37Ic)r8LT1^8EgH<E)S`&0W6gMyJ{!5*&KEJ_k-y&R64rUT?Ul7OCOm$ zwb6ZPA0oJVnxH=1<r&lNg~fY8DT{HnprmwIs({xwi5AhdSttIH`=zRF3vUW{naS0H zoJuTyko%Q(HudrQp?1!csjg&UclNnQqDqDkk{85!coD-9_2T$3sQEP^6hakLXvixe zGp}mAl*44w0cX4l0_XWgZi?&mnx^6%yW2~ZP>k*gS4t=y#SJ95h#Hczuli7rUL5)p z<ONOjLmtRmXv6d7qN=B>IdrVhTMyfY*eS)FaSUB8^2H6SzzQ`%z1NP^hmdv>P$6qF zd`jq9QuKO4^s?4)gWM&cQY4<E$+F)sJb)G~)e7-Ms5HjXmen$jO3Uz}l`Rp6<EmI! z-7rRrE@g3obc|dAxnT^1bOA6bg`Z_7+DqF}vBJK(8d(v`zdeOUo#sjFQdna1wlvdi z`kiJ%#)%57z<R#tjvW(GT%LZ+HF=X^j3@a{UZ(-C7;013t--Oc+ZY`Ryc+REsAN~J z!Ew^|$4~{wISS&$8+fKR;iLCwuBGMGuZk^|*$G2l=-9mOt;)m*7t5&<f-n&Ed-CXc zv>)~LuA3ZvGZY{lv&6URi!f#aX{H?gK|m6Pq_^Q%B?e@ICgB6gwpAM5@UCjqH!dNC z7(@aO2r|YNb6ZzoSMiePG&is`hiT|yYs?Yw&!3{{M|#i5zRr2<cCWe&HpkG$9etQk zUn{2Dy2^9hO6W5sn>qf_@uO#jiHmMyNbXo|-g!MtPILi--_>@Y(i7xJnJ4c;9h})D zwqQ74A<tGI2k1gp)h9?5k$`zIAh>lSt_B$#lO6NddXZkk<Ui;o<91=nE-R*)!nL8b zv$R=63+;+Iiw8(+6D;8(h%r-ROOOoom!uqti*>FGBarTsJP(caRA<wA0u+6OYq?F# zNWl3*dHuW}rqmiJidsYC>JXCHx(YZB)x6Pa-NmX1nbluKu5eRrh5o2kIEr3NGdFPE z6UipiJbB1;$$0oVNiVeRxzKI@1z7f&wX>`mjQ<<_V0dO`k?je=f}1FchMpXTY%oI} z`nLwWlx@^ciZxzv0p88z=|2HSyxY^KxbU|!=2G8P+R_F&XKXpa+}Mm2Jbce1fna>P zY+_g6CjuP`)^=It3<*h%bv$Yh4KR96Rj!|hn!yr4$T&TsSI)T>ZmRhh_#{?Oj63_y zriFFVIZrdl_KjtzMh?>Sz4gVKtqg=Q?|scP=<#F%nlLtmPS)?b=-xdz^5|B45ZMhD z9mszXzyn5qie=;&gZVi_^G2>U>aAgX{-bKCM#Qch@cb*&*Yb)myBS6&9LMxvkDFHL zRJuo_`3G4gqb%5!aO?uwbRbTg!nH8waYF-G<f$u3Fm(xR;Y<fj?!b-nPW-Eq*tYik z1tQa5c_@&HA`5r~Q8HB|jy#TACoBGB=+@mU#HaT7IFyiuaXu?yDns|NJ~tYqV!K@8 zr+m#D8U7@&unbLo3p?A&r;Zk)Xhp=9>#lHgFUuOvEQC*1UN3rK2`3X-6LdWAiGS+~ z`AYfS9$U<jgkf`l^)Tz+rRz#dJA01ji)Air_qCMRGodKHhKh;+gwHFL`|U?@fa=L- z&lTnqn~Nz3YwX_(bm{KAA_PoviR(t6BOS`31a#$GfQywU56@zY9ukatWECR>q&>;S ze7S6XwS~mV6+{ZU4MwGq!K_nBdpfJ7#_{%P^>w3{OB7)eRJc?Metr(<@d<+N7uM_3 zWxbs<a80af*~r30n`MF;_0_1h=XC=9@!_B2TIdgwt86SI`f+$^2x0efhjp7*^2ucx z_i{r3P1#TAf)tPHflg*4_We3j8wxtmcYIlR)}qfUeY`nxSh2aXkIWT;Y_SRPq!{Is zlhqf~a6y)SwZqS27bsh{+VT=7DVc&?ZFZE2JCWZGhq$78XNH5f&7kUX1?Y6bF}Q$s zEp;vESVA4-X^X9sJ=RxfksfQ<2s6Dx+0`VUt`NH|F}PikFENa`;*dVKR({P`>?5$t zMLumWc3Y5lHOwA<0s39|R-<{i{7C%PQ~G>Sc}4R2z=LzwmHhmn(CtXxm44Bh%<~gw zcN`<1iteMDPr_j<Oijs*7)Dc)S<tT7QXaT<8!su|1fjAG9~8LrH4#&;jWM%TE+aW~ zyKfsg5v}q(1Sb-KiEfNg=`z@*YL^s+(qO!p%-uevl|IA0VAwj~-l0ES*H&Q;E4-vf zHBJys+8`M^VK#HGjnXDOK{p#_1=qX4<4KRZddV<ELm;j>(Fr4<pxDQCeKKOsG%npt z$f7VfG+vDoTTfr;DNO8uZXYAr+U?{trFA7(s+AC#McxR+M09qLW>j9&&QPfd&P`fG zK0Lt->epQ#T9SoyVm%o?8!qtF<=fFI@bQJ&G~F(38eXp_N`~RWFJaN=z;0$e+GxsL zwWlF}&HvUm#Oh;W^w#99Lgt{{VuE2rb$g2SJKN2&gQX`$FonzS)sXf0&J6i&M?UF{ z_8TpCwqWT=`&WjI_CpWHa|3k_9F$`_<#Bjbwq=5X?4IUL7)U;Ao@McECZufJ$Ca|@ znb@X6dFZ{5E0W0<IKd*>JVxka#LYS{6&7EzV+kOS^Laz1l0Z+EeTL-0!6*>MnJzow z0Mo>L#&gD743(Q9nHr|D4Skip!OHA^rkb+qHKG5(V?sZ9Qux-cz=q20ODB7l(rEv* zmj`U};E9uWGt6UsPR0s<7)I;IcM1*9BK?${!l{;7{FWl6+261v-$Z)h?@(tsM$)kN zD@#E~vboDbCygoYoRQV3rHC+1$nHa{ZUf@L9<YZPyv-q+^CG@Y;-bR2Eveg{W$vJP z>~pSnRu;>kL&xqFX#E(OC~=GJ1a{*1U>2yfP^}+m@%V1>VomYs5r98Gha1N;IjAkp zhzYSW(YZ3wX+Iz0x+rh{{V7q{Heqj5Yhkk1s9QkTtk#YX;ab<>z`=67iEG8_;pSos zU6t!4Oj`(cQ%i_OmgDA=5B|-|VJ5yN`YK2c6S`-dpA^?6#cRXOc#f!fA^}?PWg-=u ztwOiW77p?$6|sSY@tN)7(Tt(tjQBc}(mPoC>lOXeY%aiw$^j&k>*2U@aXb$9^f>K2 zS7nU|!=t<p;u0-!F%*l+#{^^NmOi=u5C^81Jhx*PQjBkT6t+a{U?Z#wi^WG=271A^ z#z_+e-q}XEQuVY<yUlO$E{L00DLp1*Xo>A1rgfs+O=mwu<9z+xsu+cOOvuOzB1#Fd z&d)vv(Spy7S7JYam;5yn?HqDiE6l+x_@o{53SwL{Mk^8bDcIV99q}a`#P}p!xXgzK z1HSkb&&j+JulUNzCLIOxdF&o#u1UzaF3EuR+Q#k8$;J{jOaglab>4FeO-Hs^yjTOb za^RPG&`Ywg?iTFAO<a&3*|LM)>kY)aDi>WbIIGJ&dLRiaaKJ4$%AN6~^N<Roh14U_ z2jNs=e}b~5wv$mGqSrm~o#+m4>2O3kh0KA=&P#pJHw@Q!ZKFY3>Vxj4J1mhjl%xZr zJCOT=$XOpryrDh|`VKK|n>1?jD(zxiQKZq%FZ+nUu1DFZvxxoR8A?4m?iBgP)p>By zQmVT%t|15pIPPZo20oqrxX23c5+Z+K1|*J)DS?&58Gj{Oq;lr~MeB5vL>M_sJ$+1x z^pRI#m4LnD7<+^7of!a{`2!gJ1((uu|Fs~5Sd@WY?bwiDz4wJPO(}lR1Ijc`?s!@T zN4e*&*t}xMGGcmhaV{Q*R}C}eNA;`b*+oPBeQ~MfHic)T>6aD*x6+D=3oZr|^`}$y z0#g-R^>yq{7XJOY$oW1gQ7{&w7I_H@&?ze@S&+UQPK&Hvm<!enc`GzPp`FWc6qN9U zk}<dGXO(J>byz}@Plly1C<mPdVd&~2Q?sqS=JHTrJCV_YNaYoZhjTD(Q9jqI;Fk>7 zk-b;45pPa>OQU0~Otvo`QHf0<OIso3r`t9pBd2j>e)c%Qqv9$?RX$J^Kv#%Qay}>i zc?=-D5Qjn=hXOMrY%b${`9ZC7y}S-<?9e@~WLo9f0m0ZI?ZB)C70*-=2KUr*S@`I7 zluX#dVr4vz)^!@ulfnV8R7X1Gqnw)kcq}7nJR<gpM2=evvx-B!ZP1<Hkv_e2=XJ1+ zJ`zi|^p!$w4$xzz`z81SvB}ZSVkezy^g-tRZf&~V&LnH(K|o8z$Mi+!{aK8uY*sf4 zcw>O~BX58;0BYiXu^x~Z(7dp-n)DVoe^uYK!kW@kB5^T=aM`Nfe62Opes4965$-rZ za}|CfqjQUhz~ub8PU?=$J6Xm=8*OB$8T$esL9i<AcjTo#v4^CE8ru^H%gGX=Mxh$p z$4y=|*E?l|nRF%`LY-Ti>@JCtiKAEE#5G<I^ZQ+9ttM651{ppMF(%dzzG`^*?A_MD zlH$&dO~L5&*lnZkWz+2lnMWrP`_xuXS+P&yrsA~MVW--vqQJT<@pH?Ek}x;yUZ;3@ zl1zTDA=teMytq8x`C~}mkt}_{f%A9`u)o9bM!AE&^}mC@BlZxi^&cSM_Q<pC8EJSk zUay?uLb74WXA$GV5^vp7q|*|>IMyzTW34&Hcp%fP)(KE(NhqH#&-Gq72P(Bh6HnXa z^wurz;MqQG%Q+}?t>xPmutjaFSeAI0b9-s=MP!kQa3Q_p)iPb6MO1LrLauS^MN(49 z)6nDvj2x{!bc%)89J%Xu>e<L(+JmB^$qa0g1K3rs1MMnxPX)aq0VbEMBc}s#$M}nw zxxzMj6pM;5*xJls$vW3A<%7ai*oA8^DM6uNub$_aIKVjSHj~764Zi)*$i?-T+tkXu zc8M+@GNB058S}&N%#gO-_<*0NoK{PwGVj7g6KMnAPh8^M>=@I(zjjVG2}yR_tYEFe z>VA3hLe54UKCd+_MmKJp^NK)*CMT_4iI5_SzsLpuR*#f2+q9cJPEBq%QjGXBjl4Li zB#Mjw7=6NGP#fr?X-UkM@!_p5HAiO%WNVbd79{`Zd&sg#n~02PFDDkxL9KM_sPu}S zO&yb(n4YV%&O98v-7EGrmx0(q?#vo3HrtxA_tp6&?}qsh(wnpesTd73HTPhhD~Wx~ z?xZu1={}Sk?vs#l$r?uF5Qwk6e?~4gldiFx&{(HSii3<<QSL%qJ337J9&V>NZ_i-2 z;<Aw48w{`uNu#Tn`?)!)<eUU^l5HEpn_E98Di89?qS^4FDC#cHS)-tz;PmOvs0aA1 zYNp7S8FLcOgixL<J>}w24B2zmiw~#oy<x$SpS|c#??VeLilK%8E9KN$xJcNz$hYP( ztRvL(NN5vJAYRk^0`ckO@fcmrp@|SPbR9rrW_(nlviihvIa%@<jfkN<N?WALIfpc! z)MElqm7UCRfqpc1dOwlK<i5QtBfMOjUcDzR#;gfxYd6(&Gq}mp1_@0;dJD0KqREYv z=e|WJ8EEko=?^}5zbd7n30x!ASfAkzw0=>H7rah9^g5IA;~tM7>a~!MQ3=yDe|=P_ zS8-pW=GAh0oIZ_+i{`aT8f?+f)=3M8`-dLF$Z2$36;q~be&*sZiP=1LE%O2NlKQpN zIf7#T=0ShiWra+=Xw4q?icRN!?xTa5YZqSRfhBON7F>n;*ub5ZBCXfqGUtQEax3!j zR}C~}hT_V%RtrUjqKjwNJEXTB3;8QTiMw1AcDIzxA}gX?I}H=*SAx7nM}<M>eA~f3 zz8TX;AhpbvZK7M^E2-Csg5wX-iM^wk&rw*!jxfm_Qu5PXX<Fv(I0sN<a`&^?2M}bG zKR$EFseQne@a&kwnP8U?Vuy6e>~7fgS|7;wdd!iG|6QO`7+Uaa0WX&GF)Sci*Gdmb z-D@=5t%Du)j`3LvpizaK_7|fQ3N?#9rs71x<$>rWi`W)IGtVv3opc_{c_N^lIA4BW z%k^rgUVwm`skV6F;K(DhO+m%iLjtP{k`GM}vM-%DI9<y#?3=j?)k(6pEvE%<T|8v+ z1#|j1#^e()C{G?~;xH8VALQk;7V2xPaq(I<wT4|S6RD}HCOIJ{`|LQi$D!zq``k46 z;G85ONb}Sk_W{lGX-S}YE7D6s`47}WN(%kk9b+8V)*D%**w95=FVHGD#1d4ZR<S1@ ze({nDaNY*(<{OU8LfI9eWrb)M-+CF^cYPoXc3`nI=Y!xt1m1T8MbifdRXz$9Ut$Qc zPlrq5PWWJrI|5CFTO7fygt_~Xg4*g*g^fEhtNw&z(Acv;wvP`!pBbt&-?4l?SF~un zqe;*H;&*aB>cDsxoOMm5Q~V`aW9M0R)*TPW_Otx+D;&)6FAAw=U-HAwpN+YmJ)0Q5 zdcDmkIwnl^f)@!@0FXBk7MN(j0`!<>-e#zM=6j(yeuq>0%qS1e$W5#ynsy?>Z(q~? zIMrShH%$ttwevj45WQp~%w<PC4KmrvEh|=RvCw6QDI^9>qn}RYAcjd9Vx1pczUdL2 zRC)(;I*%jbv$$9coL7a{FM>wZ0uH-RPvq1mYdWmktO?B0!Q#qVaujiM9zu!>-b$QF z?#85P<OPFw)Q-%PpY_!>Ik7t?X`J&~^c3a!Z=ypU_-}R<jbj=sTgt>WVys4Qj6w5o zOC!?79zw~k%ckaw^Tx#c>8lNDwoISAKq=o4o^lY{T_4W!-<?iPJDrjTLq_(3e_1}n z1-{4o5;w*exrz~7DrdP7ZX~<uW8G&p;jlNbSpax!y3Yvo+9x{nY*N<L;dkd#awn<1 z=(STSQLe>3NKW{TR`}+zv(yFP67ZiR+~WX4g^m&w>6U4dY!PU~Tr*?FxzP!@r54G` zvUK5((#@xo8Lnn>?Q0&?uN`>tN$cArCd}{bx>pCplG<iSsVwfJt63+5Q4CI0+cNX_ zgU8mAl$0d8GwjRrBH>_(iu10_rUn@HU7IVO0)piRG46tuPZR`uBjc>6_ITan_i}nt z9MfyD0|_*u!(QTzY<OyU0Qiv0hxiZ{buL*uD;|2o<@7vCTNi#sDHF?_ybgV1oE2=A z<&7=_J++oz==fl#2W8iOpM0`0Kms>$0(T7o-(F8THhj>sx+)bxDbv=AYbU)>B~l`m zM4DE4O;NL2z8yM=th%fL%(=_Khs;E!of}zraC}V&stZmQ`Jt8-^=^}+yltA%n)SXQ zA|Ii+iWxTG^!#yHkFXW-g}(2FO<a$1RKsi!(K_>Co-jAIcF!&Qf}3x3gr`NBBU9Re z5APti>5)fm+g{a0Iby7fKDmUBor>wfg0SlK-1!cCWB2^iD*t7sfsOXr>2=_QhkHPs zh$vm$ky<LuWZ>S#1a2|}#Yr{w3G4i{@UZ0;m4TNUalCvYINUj-)!R9Zn{HDmi@m&v zv9*Fwz1N3qFez}aQmqlv8$QM_g<QK`A1eZQH4kU&98+$ZRUz8!q$g@*r>FUJOsin@ z#b+mwm5L%Kh;3v}7AvEV#)*J93zr{pIs$TVCZAAWxl><3H!jFdjk@8LUm(QYncy3@ zq9l68%7ix*ceo{r^o&~O9)lk+3l_+RD{j-SxdL;KJr_)FQtViKL>sbwGLP{H0LZUJ zuM2P~BswuE^}CLBB$4KOju(U!+Qh>@blpiTx;4!8w4|cjOXb4{)*KT8Gr9dw?a~h) z%p2P<t!)H5CLE_Qtm)5mx=kRoz)sGbL+)0v*Pu>vT0Q`b2=3=Rd!au?z=JE^p3_o! zLw8V(qV-g85kN#bXR(=D)$^*)i%G(hKql3;Whii@uQ<B1IQFp1yCYeIES(ttKrY|L z{2A{ePw>c7l!EvXpw1S5giac`m9yEHggK?H#I5c~w6x2oKtND5`$~1E1#R8_OZgfG zzS=pLIg3Rj^B5w=nuCuF(St!!h5FueB%>%qz3Z2ZA6L^lYuiNBT*k#+ZnpD?;@z7x zJ~e82JNHqGWboukNe)r7?vG;o_>=X)!HvQ^{lJ6@=%<SH`pg?&;HV2e8xKJ>h~qaB zV#-YwJ(#R0nyzXQ;Ea#So$i#zMm_3IDes_=-*1r`T3?gSrfqae!UcQ7HCL;ELH}st zLAH8octE#tE0%m{H<U(z@Z{(Ga!5rSlDf}DnjS3U8vQHu@p3^j?em2)?HQb`G(v?n zoUA+<G8vL5&QAqNGs6op8Z*iwoGfx77PR1O(`1uJ0GJMpK@wK)Rn*61IBKiIPsTZi zn>uECEOZHY>NyK2ElrwVMeNjbj;teAPxZvTTVP%`j=6<jxoZ?e;+iuk#?H~2=zYqf z?UUTw`8j0tT`l(7Iz%fEUb4HOexT$ldpx#j?!HRFvM>lb&5xlpA*g#fO5Qc%J92xf z*_v|k=1?^m@oonJ;ayLpj@M1s&H87BBK^Hbw6i4Uojvw>1d?J6wX@WU479VloQ(@e zuX`cGDq)%sTt~P+%L|>gah>OaHwZ)#oEAMqZaipfdgZ=kth4Hhyu<d1N9Kqt*u`b6 zTwsRi=)nNnsLzCcZgTfDM>9`La}V>D+AQ~Q%r1VSwvPT9=a$B7l7cgF(O??tF6W?- z>JW4m@hRb$S`FcpwTABn8=dfVDxxrKQkanZsot31rtuhi4Fd3`HZt#=9>gwN;&T+1 zL_ZXcME`<Rjw~N%=xt^;xD=xqKeLqcENwH$Z3i|ekCY}D_9vXrBbjHPN3wTj6{hY9 zNha?JOQy<anG7LknI}CMvtdKvow0!#W*LPgGOzdDOnH^%ZiWPGormFly9CEOz68bF zzl6j)zXXwDZwJl5O!ZuWg~|^*C02Np{ZzOx1v<+-Syd=(NMy`%GdXK=xHyY#s7A;< zWlhLD8KdMO$*hpDjbCG}ke<#XMh3J*>|j~Mu6YQ@dZ9E1G;>5kll!0F-gv|E`+I-- z^FRLj?c}d$KY#p<+;_;DIS6PQKN=f9k@`2uHh#r6exP^6ZTu{4{B~^o@NE3Jbsvpj zzXS&QBcqu^N_lWj8Y7$5qp<dZ(KtSjYZlsU7T!#*7F>x#1{Bz6KPm9dF!2x?99A1c zm_*dEynxiVxPa9+MFA6-Wf#Utg%mCwVjr_|M%zYrX5Lnrgl!%oglJwOL_mP_EoZ*X zqI0+1V!4HxRF|_(E;qwx5AvHy#>+Z4qqOi(hQ?<N@-JsmgtTRc%(5PSkY(gdzRi0I z96R5n9;0+---dBUvgqQ6WVbT&=S(#b9_4Zt(wzh74(p92CBrV5MZzw_QI)fB9UILJ z1sNee41DE<%lID4&cuUc@<5I$2KeYeY2UcJy?9Qc+l9s@#CSzzU!|K-b>FGmh3>+( zyFGUfD^$ODPCe9Hv#J!UMWV_Yt3{z|535DEiU6xcx+)cGF?Wt3bh%{CHuOM!-!pW% zd`__Yg3cw#7#6cdwW<ZvT65p7`%rn`p!-mDKg;-%?!wm?mDWY7DhSh4t4b2nQ?05O zb5E&EwW>2zpnMJ^^eN3np7Dz2ez375mZwZrA=b5@@hJ60neiyiMXNCr^@V7+Pu^Tu zXj|UgU}#&;Tojh4detnZr+yU|rl)FE1E!~LRVMXCr125$g=+UZ)qV5Owu-q?tZShv zRIF>MDnsgvDr3+GWV?Ox=PIzSBaAa?FLb+o3g?cnuKkTOX)Ydz-YQo;rnxZhzRQ{0 z3cXdYV#2&mHFoU2E1&y9dx6>=NpqpteV0G?8jBC*0?u8*=*t(x{ZW(c;uqlIs&lA_ zU8OBm&&-~3UG$bJ8a>OLV~WjDoqPEB#qII$hc7VK%h(rN((B38(dIE?8U5()Cln`` z%qMb2>olh8Jh9Q{v}B{&;r!j<{55#)o6IM1pdZ$gA;5ZaZy#G`bYXb3IXKxUPdNWZ zI6qc6e|$JUBAz=o%SqZ`UrkbUVQ6$=O0+pV*{F&{1|Xb&5YOET*Ik48WQz3!3eSBP z*L@ey9Us?SB%I$G*Bu|veTMZU0Q4`*NtyY&rSbawn?4Mq^$^w*cD9qW(Z0!!zR7_; z43l+BllA$&z8Yy#)GahNo}i=`gsTsnP%u|9K`pi2pDpneehsxQjsB8K7HlN(lkR)7 zWj^go#+kO%r<onimug?AibnecQlmXl$XwycTxrNI^&}46!<SC+ng(&3aG0GkJNxuU z`t;xS>G$`wy^QvJkbBY;J`jc%Uy2)FiWi^AOv?mNGw3O5O;c_PRCb5XJqf8Y%y|lN zO<^|6Vm8ZW#>H$Fgj+MqY{tcERsk47VKb{R88R46TJB0}^JC%bNFv%WSsCiR^AfyZ z@Y>h?JlFeauEDXnE_(8ne43r@CeRv@%3ha;kLSg0VdY~VgzL30kBy%pGo}~Ffvc>4 zai(bg(g54+mj&mDeA~aQh$P#jvYtefWA@&zDud9t`G%0hY^*`*F5Hmp@j;&Kx+23H zCTP@>(Fb3Ck`Q=L>ghcW%5&cY$`Q@@H_tJNS{OMw1At~v?M#4{_IA#{ZzIsK)Fe>H z^$E1?01m`pLfA4SXyA9ChrZGf#-tvz@J(PKwIT|XPU1`siG{D#alK{m$#+9ppL5oE zd~JRMOPtA<jFEuS5!8D5?y`OG)Xw$f^7XTJC<lz!G;SDdBulDe!Fk~zWyqW?;$k#2 zETEA>Pitdy#d~mUD0Ahv=n2r;mg_99-m8`&oFt=ZW3-H7fl>!{A7_VGm|7s@XrT(@ zToF9SeQpZX&uNTN5H86|ENd}+fIK;%z!P~8zE^Iki(XzxY^kf$!FmwTUd$ujDKAHy zh#x)1Y^77cN14qz(?XgZ$r>228E1*-oPX2&f!=bD$Ekxxch-q6#U!+5w9J`nI48hu zz@-}g!{LYeDudedUZ954u%2=CDvxS4|1)u38~UbXJ`6>D|ImXfguXO007h83D3Gkp z8Uvji|EPHB!Ix@vdMt$cHGBfqgonEr%haDpq{P9GI7HDgUlpYm9PR0in?xZdd~D?$ zC&*KMZSbJt0W&Xkyd2B`$6PWJS?(n{Do^J?bLyFvWAzZ4d(rc}ws&*F0#G?2DwHPN zWPbJ65Hh{$llq;>UJN(&LY+``M%xA~6)>r59<#A%9Z%hdYg=of)WptSY?|VUlfTyy zu}~ZRte;1}Kq1LhAU{rQ+dKG}I!;}$^Qn%WK}vfrX^`N>t~M>hhgH9$7cm<t?ztW` z;hWlR$KCy=DjNas62_beOVFxK=V%c=>2!PIMt>-x?oe0mNC++6PN;gd3ZGH(N#dpb zv0OK9d+c#r=);e>p_w1Ur}>@z$dO=?f|L)!#U>UeoHl#(ho>XOPT4#^GF^q`Dhd&4 zu#mfOvCyyKV7<=K71=Lyb|uF<-HSp>PbV0_FMmu(6a?#NIa$@v?dutTbY}Lbk&YBR zy?4NlPbWKP&QrwRC2cAieLehO$kM%j^vT$sgE3Q>dqcSG>wF5muylIQu}l5JU}nd9 zj?xV{T6-JvS0W*3=Emmkx}&S@!}STTS~aMHfttE~1d>5V9_tQB)9dG7j#Nn-J5<#9 z(chRI>$k+Hz~dOdWy&^jgWd_JP_&6WoIHIKWMQ;>chiAhSQ=(1@4EuVV!6cOG}`NX z#*l%oM^S!$>@79gR4j~Y>EFvAMUZDD4#slku}g?z=#Ezx2dlI8Aw^*7FwY>%r_TN% zas_`pOkdF{GN&@81r=2&8ZUq6K`Y|;lgBXppEZ!sqa0W;*F^+l!%EkIllyh5dJYBN zIGs}%uGwz3HvqisbQA4hq5%;&`?Z9MXN$89G@88}R}c}U$Fhl?CxUV5JmA1A1T_F_ z6a>*D^Ox5F=7J-Oln;v<Bd#L54vAX#=7U#JTsh)aC%ft(3{KuAI+i1ybAQqWf4{O- z5t&wGZrRmPE1LmlLa(RV^I3pB|H@^&H7EfHu$Aw<L{k81TV6OAC0OfTxz;j|ZChC% zJPxm0Q~!c;;KCXjka$JR#QFl5(r%V?=h-U%0l_T9I?@T1nrGBwrI#AJBpH`<)yGMU zvM2Zol4+x%>C4Ltd$cBMli_G7!H&j<{c)MGQjIm?VE{duu;C5XcTx|5ZuxL@YI0;l zkDtR@hCYFam7KurSl)dDbZ7N#4WoOO^|^^l7xuFqW%@~)+<kBahromc#X``&s(8?2 zo}h^MM}Y?e#mV0a8W`xF5U9wDs|hnmD@ZWi4S<36Cw@B>4lE9Is<;y*n-1th=Aakm z{ilB~d46=D-~KpNURXg|LR?jiQC{LZ0vOnR8pv;F+#kPDgQT=U<PD&A?!^%Qe8&A# zsee8L<B$K`pE3XY8CFIvMux5cW9Q$5O8x`@_Z#J^8P4n(GH5ygDR$ES0RRS;=Z66P z%ZFS+vL|0PPe2odogKi2_3Om&y<%T+5AvrO5kYMr0@?L`n)z1p{3K9+jr&{ueg~CS zlmgf|e3Q2O8QW6F$C(+lsZ$h$PxTELbj<Th2if2knvs7bOea^dw*maR!qmY%lhUA6 z?CKyDZPFh=(Xsy?^w)UtwOzDVpbA2RnoIKoUMk+-;|ZG@IRJmv_WKTpiFL4_1HsXN z+W#Z$67k=|{*WgA&nCTx4Ld{FrU$uzrr6hgvfnD4=Xd?^?_uxd(td4$)Z<vhd5{w& zf%d-q=vvkSe~&3==?nze{mKOQqvWYqViPn7*BA7<A6ef@o?kd91@^DH-^s|q0bnZP z4g~z}Uq4s)*U~{>f$s;?g3vF4O@33N{b5Yq8#7+MB`hA)^@N~t1v>GMlIMpm{C7CN z5Ec^sjp1**?hAVFivy51sDefs%O70#qX8NuEAR_*{9fxn2E|v7Z+oAA_6*MRZ5&vT zrAI-pdzpf7CC^V-`oF;cMEAE%GCiy?L;;1+P*CK!SF`_C^8CtV{sZTK?PXZQUxJ%D z8JPi{f9U0VH*8o#ztIKReFN0f+}~;lI_CL3{#zWedpL4Y8+&KK9~xz9?hbzumfRun zKZt@@@<1%?Kd^i>`n%eGW%+|Z6=($f3x;H+O^kVvr$Uf`@(=&O@WS*T8GbhLSD7;L zJ!{7nkiUEaRh<6^$ZV&7gfwJfCYQD|u?NK!01zPVW&&`yzX16)6Owr-au5y}SQ*Ik zsefRq2mTwTU!zJLS0M#}>fR5ETtA}vhyM#yu3w{4tEYA$gACmP3e-QM%18Y(Di_DE zQTwcXC!ah31M>%^H~0}XEA3yPa{i3^zo8IR-QUZ7D3mDwB`V-sKm9mR?!)2LnJ7gP z$QzPC78Uvi1v=*WJ^x3n?~d??EB}K>;J%<cA_6hl{Lip-tNf>$OWU~^*;ty2fuvOb zfn|53qJ9e0ruU!~!@UsSx02_#SN$(pB%F+F|H-|rjW$0`f@t<ZG@v>2kCNwyUH30( z{;l1foYpHifT-+2w)?R~VxYz6FQ$<{^|+j+ttC+04PXiYnEr#|@(%d18bCCvpcefY zH4&TsiAKR5s0y$%H8QsOYrFlg`x5+NwR`u)ZTTgO8K^TX?f$&J{u;^HlKd^8Ko-dX zRrklWhVtJ7{TAPU>*AjW*jHc&9>@%LSkUwaS{~jjT7D~eei?0lTf^_bKL|ShJZ*QO zmOo|y*%=yCd4_M`pktn&dH3Jq|5oR}gub8nI>qACvq7-(py>GHf@7uUpP}z{ipdon zOzchnVm?xSvZKcYsyP~n;>Ts<X#c;1|Fy-jg@hToU_ezy`FT+xG5GIr|D+83-Q54{ zaG+)M@8#a%2F8C0Zee6+>TF?T4ftVQaUU*ikk4*QK>+8V*N>h7J@F4vUsrN}jQC&M z;lZ-9X*~#=4`kyX2Rhi~f5ZPvFZu0?!c~m25k!LpqG1Cazm+^c<f(t4`J(}UOE9HE zs+s@-#{~^du5aj|BPcdb|679Jdi1N+er>Fu#o52{<oSWm{wI>J3ZcIy`C0J$I|=&y zza{xD|NY%Hzebv$m9W2~YcBj3^j~`aSBjtIdB0Ooy!!`=uXg#P$A4|0pJi{q<1?)M zH~hcwsQ(?r_}@5wyZxf{{+BerYjXT-@t>t+zd_~s1^+G1FRuT-n&&SsmhQvx&!Ugt z+2l6<uGW8CkbUL&)%D*!{LeCV-{C)R{}=e5=>E1zKTE59=WN>jf8zX6XzkDab>GCF zMYO)t2_F1^(EYD3-;@0;_41tz`t(1L{S5xU9*p}9`8N-yMgsrUBJTH;Y2R;T-iPj= zlcs$G%JVb$8<ZbYsQuZ-_n1HDulkNjh5FYu`>SkLKcoMgV&yyfD&{|;|8K&TpW%Pr z`~Mxj2>+kpf9|NS5%cG7;l3j}JpOCMUw<d3Bnu5%*4$rP34wKj?o~#Jfo4Up{{y8z Be2@SD literal 0 HcmV?d00001 diff --git a/cb-tools/java-source/gradle/wrapper/gradle-wrapper.jar b/cb-tools/java-source/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..0087cd3b18659b5577cf6ad3ef61f8eb9416ebba GIT binary patch literal 51348 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 Vhu^-h=$~}~qQo@;l019%{{a3e65;>= literal 0 HcmV?d00001 diff --git a/cb-tools/java-source/gradle/wrapper/gradle-wrapper.properties b/cb-tools/java-source/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..7dfc31e8 --- /dev/null +++ b/cb-tools/java-source/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Jul 02 20:43:27 CEST 2014 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-1.4-bin.zip diff --git a/cb-tools/java-source/gradlew b/cb-tools/java-source/gradlew new file mode 100644 index 00000000..91a7e269 --- /dev/null +++ b/cb-tools/java-source/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/cb-tools/java-source/gradlew.bat b/cb-tools/java-source/gradlew.bat new file mode 100644 index 00000000..8a0b282a --- /dev/null +++ b/cb-tools/java-source/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/cb-tools/java-source/index.html b/cb-tools/java-source/index.html new file mode 100644 index 00000000..7e46cbb1 --- /dev/null +++ b/cb-tools/java-source/index.html @@ -0,0 +1,88 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset='utf-8'> + + <title>TooTallNate/Java-WebSocket @ GitHub</title> + + <style type="text/css"> + body { + margin-top: 1.0em; + background-color: #3d84ba; + font-family: Helvetica, Arial, FreeSans, san-serif; + color: #ffffff; + } + #container { + margin: 0 auto; + width: 700px; + } + h1 { font-size: 3.8em; color: #c27b45; margin-bottom: 3px; } + h1 .small { font-size: 0.4em; } + h1 a { text-decoration: none } + h2 { font-size: 1.5em; color: #c27b45; } + h3 { text-align: center; color: #c27b45; } + a { color: #c27b45; } + .description { font-size: 1.2em; margin-bottom: 30px; margin-top: 30px; font-style: italic;} + .download { float: right; } + pre { background: #000; color: #fff; padding: 15px;} + hr { border: 0; width: 80%; border-bottom: 1px solid #aaa} + .footer { text-align:center; padding-top:30px; font-style: italic; } + </style> +</head> + +<body> + <a href="https://github.com/TooTallNate/Java-WebSocket"><img style="position: absolute; top: 0; right: 0; border: 0;" src="http://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on GitHub" /></a> + + <div id="container"> + + <div class="download"> + <a href="https://github.com/TooTallNate/Java-WebSocket/zipball/master"> + <img border="0" width="90" src="https://github.com/images/modules/download/zip.png"></a> + <a href="https://github.com/TooTallNate/Java-WebSocket/tarball/master"> + <img border="0" width="90" src="https://github.com/images/modules/download/tar.png"></a> + </div> + + <h1><a href="https://github.com/TooTallNate/Java-WebSocket">Java-WebSocket</a> + <span class="small">by <a href="https://github.com/TooTallNate">TooTallNate</a></span></h1> + + <div class="description"> + A barebones WebSocket client and server implementation written in 100% Java. + </div> + + + + + + + + + + + <h2>Authors</h2> + <p>Davidiusdadi (DavidRohmer@web.de) <br/>Bob Corsaro (bob@embed.ly) <br/>Don Park (don@donpark.org) <br/>David Rohmer (firstlastname@web.de) <br/>swax (john.m.marshall@gmail.com) <br/>Jarrod Ribble (jribble@netiq.com) <br/>Julian Gautier (julian.gautier@alumni.neumont.edu) <br/>Kristijan Sedlak (k.sedlak@mandatela.si) <br/>morkai (lukasz@walukiewicz.eu) <br/>Nathaniel Michael (natecmichael@gmail.com) <br/>Nathan Rajlich (nathan@tootallnate.net) <br/>sippykup (nobody@nowhere.com) <br/> </p> + + + + <h2>Contact</h2> + <p>Nathan Rajlich (nathan@tootallnate.net) <br/> </p> + + + <h2>Download</h2> + <p> + You can download this project in either + <a href="https://github.com/TooTallNate/Java-WebSocket/zipball/master">zip</a> or + <a href="https://github.com/TooTallNate/Java-WebSocket/tarball/master">tar formats. + </p> + <p>You can also clone the project with <a href="http://git-scm.com">Git</a> + by running: + <pre>$ git clone git://github.com/TooTallNate/Java-WebSocket</pre> + </p> + + <div class="footer"> + get the source code on GitHub : <a href="https://github.com/TooTallNate/Java-WebSocket">TooTallNate/Java-WebSocket</a> + </div> + + </div> + +</body> +</html> diff --git a/cb-tools/java-source/java-source/.gitignore b/cb-tools/java-source/java-source/.gitignore new file mode 100644 index 00000000..d7e41aae --- /dev/null +++ b/cb-tools/java-source/java-source/.gitignore @@ -0,0 +1,16 @@ +*.class +.git +.gradle +build +*.svn +*~ +target +*.ipr +*.iml +*.iws +.settings +.project +.classpath +bin + +/doc \ No newline at end of file diff --git a/cb-tools/java-source/java-source/.travis.yml b/cb-tools/java-source/java-source/.travis.yml new file mode 100644 index 00000000..067c53e4 --- /dev/null +++ b/cb-tools/java-source/java-source/.travis.yml @@ -0,0 +1,5 @@ +language: java +jdk: + - oraclejdk7 + +script: "./gradlew test" diff --git a/cb-tools/java-source/java-source/CodeFormatterProfile.xml b/cb-tools/java-source/java-source/CodeFormatterProfile.xml new file mode 100644 index 00000000..73325ded --- /dev/null +++ b/cb-tools/java-source/java-source/CodeFormatterProfile.xml @@ -0,0 +1,279 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<profiles version="11"> +<profile kind="CodeFormatterProfile" name="Java-WebSocket CodeFormatterProfile" version="11"> +<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.disabling_tag" value="@formatter:off"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value="end_of_line"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" value="0"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value="16"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value="16"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value="48"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_binary_operator" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value="end_of_line"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="0"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.continuation_indentation" value="2"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value="16"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="0"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_binary_operator" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="0"/> +<setting id="org.eclipse.jdt.core.compiler.source" value="1.5"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value="16"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="0"/> +<setting id="org.eclipse.jdt.core.formatter.align_type_members_on_columns" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="16"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.lineSplit" value="600"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value="0"/> +<setting id="org.eclipse.jdt.core.formatter.indentation.size" value="4"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.enabling_tag" value="@formatter:on"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value="64"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_assignment" value="0"/> +<setting id="org.eclipse.jdt.core.compiler.problem.assertIdentifier" value="error"/> +<setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="tab"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method" value="0"/> +<setting id="org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="16"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value="end_of_line"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_method_declaration" value="0"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch" value="end_of_line"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value="do not insert"/> +<setting id="org.eclipse.jdt.core.compiler.problem.enumIdentifier" value="error"/> +<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" value="end_of_line"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value="end_of_line"/> +<setting id="org.eclipse.jdt.core.formatter.compact_else_if" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value="end_of_line"/> +<setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="4"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value="16"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.indent_empty_lines" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value="end_of_line"/> +<setting id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="16"/> +<setting id="org.eclipse.jdt.core.compiler.compliance" value="1.5"/> +<setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression" value="16"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="64"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_binary_expression" value="16"/> +<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value="end_of_line"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value="insert"/> +<setting id="org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode" value="enabled"/> +<setting id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_label" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value="16"/> +<setting id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="1000"/> +<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" value="1"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value="end_of_line"/> +<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="0"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.wrap_before_binary_operator" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value="0"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="64"/> +<setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_if" value="52"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="0"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.comment.format_html" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="16"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.comment.format_source_code" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value="16"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="insert"/> +<setting id="org.eclipse.jdt.core.compiler.codegen.targetPlatform" value="1.5"/> +<setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation" value="0"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.comment.format_header" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.comment.format_block_comments" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="0"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value="end_of_line"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value="0"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value="false"/> +</profile> +</profiles> diff --git a/cb-tools/java-source/java-source/LICENSE b/cb-tools/java-source/java-source/LICENSE new file mode 100644 index 00000000..5a93449e --- /dev/null +++ b/cb-tools/java-source/java-source/LICENSE @@ -0,0 +1,22 @@ + Copyright (c) 2010-2012 Nathan Rajlich + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. diff --git a/cb-tools/java-source/java-source/README.markdown b/cb-tools/java-source/java-source/README.markdown new file mode 100644 index 00000000..0aaab7a2 --- /dev/null +++ b/cb-tools/java-source/java-source/README.markdown @@ -0,0 +1,163 @@ +[![Build Status](https://travis-ci.org/ck1125/Java-WebSocket.png?branch=master)](https://travis-ci.org/ck1125/Java-WebSocket) +Java WebSockets +=============== + +This repository contains a barebones WebSocket server and client implementation +written in 100% Java. The underlying classes are implemented `java.nio`, which allows for a +non-blocking event-driven model (similar to the +[WebSocket API](http://dev.w3.org/html5/websockets/) for web browsers). + +Implemented WebSocket protocol versions are: + + * [RFC 6455](http://tools.ietf.org/html/rfc6455) + * [Hybi 17](http://tools.ietf.org/id/draft-ietf-hybi-thewebsocketprotocol-17.txt) + * [Hybi 10](http://tools.ietf.org/id/draft-ietf-hybi-thewebsocketprotocol-10.txt) + * [Hixie 76](http://tools.ietf.org/id/draft-hixie-thewebsocketprotocol-76.txt) + * [Hixie 75](http://tools.ietf.org/id/draft-hixie-thewebsocketprotocol-75.txt) + +[Here](https://github.com/TooTallNate/Java-WebSocket/wiki/Drafts) some more details about protocol versions/drafts. + + +##Build +You can build using Ant or Maven but there is nothing against just putting the source path ```src/main/java ``` on your applications buildpath. + +###Ant + +``` bash +ant +``` + +will create the javadoc of this library at ```doc/``` and build the library itself: ```dest/java_websocket.jar``` + +The ant targets are: ```compile```, ```jar```, ```doc``` and ```clean``` + +###Maven + +To use maven just add this dependency to your pom.xml: +```xml +<dependency> + <groupId>org.java-websocket</groupId> + <artifactId>Java-WebSocket</artifactId> + <version>1.3.0</version> +</dependency> +``` + +Running the Examples +------------------- + +**Note:** If you're on Windows, then replace the `:` (colon) in the classpath +in the commands below with a `;` (semicolon). + +After you build the library you can start the chat server (a `WebSocketServer` subclass): + +``` bash +java -cp build/examples:dist/java_websocket.jar ChatServer +``` + +Now that the server is started, you need to connect some clients. Run the +Java chat client (a `WebSocketClient` subclass): + +``` bash +java -cp build/examples:dist/java_websocket.jar ChatClient +``` + +The chat client is a simple Swing GUI application that allows you to send +messages to all other connected clients, and receive messages from others in a +text box. + +In the example folder is also a simple HTML file chat client `chat.html`, which can be opened by any browser. If the browser natively supports the WebSocket API, then it's +implementation will be used, otherwise it will fall back to a +[Flash-based WebSocket Implementation](http://github.com/gimite/web-socket-js). + + +Writing your own WebSocket Server +--------------------------------- + +The `org.java_websocket.server.WebSocketServer` abstract class implements the +server-side of the +[WebSocket Protocol](http://www.whatwg.org/specs/web-socket-protocol/). +A WebSocket server by itself doesn't do anything except establish socket +connections though HTTP. After that it's up to **your** subclass to add purpose. + + +Writing your own WebSocket Client +--------------------------------- + +The `org.java_websocket.server.WebSocketClient` abstract class can connect to +valid WebSocket servers. The constructor expects a valid `ws://` URI to +connect to. Important events `onOpen`, `onClose`, `onMessage` and `onIOError` +get fired throughout the life of the WebSocketClient, and must be implemented +in **your** subclass. + +WSS Support +--------------------------------- +This library supports wss. +To see how to use wss please take a look at the examples.<br> + +If you do not have a valid **certificate** in place then you will have to create a self signed one. +Browsers will simply refuse the connection in case of a bad certificate and will not ask the user to accept it. +So the first step will be to make a browser to accept your self signed certificate. ( https://bugzilla.mozilla.org/show_bug.cgi?id=594502 ).<br> +If the websocket server url is `wss://localhost:8000` visit the url `https://localhost:8000` with your browser. The browser will recognize the handshake and allow you to accept the certificate. This technique is also demonstrated in this [video](http://www.youtube.com/watch?v=F8lBdfAZPkU). + +The vm option `-Djavax.net.debug=all` can help to find out if there is a problem with the certificate. + +It is currently not possible to accept ws and wss connections at the same time via the same websocket server instance. + +For some reason firefox does not allow multible connections to the same wss server if the server uses a different port than the default port(443). + + +If you want to use `wss` on the android platfrom you should take a look at [this](http://blog.antoine.li/2010/10/22/android-trusting-ssl-certificates/). + +I ( @Davidiusdadi ) would be glad if you would give some feedback whether wss is working fine for you or not. + +Minimum Required JDK +-------------------- + +`Java-WebSocket` is known to work with: + + * Java 1.5 (aka SE 6) + * Android 1.6 (API 4) + +Other JRE implementations may work as well, but haven't been tested. + + +Testing in Android Emulator +--------------------------- + +Please note Android Emulator has issues using `IPv6 addresses`. Executing any +socket related code (like this library) inside it will address an error + +``` bash +java.net.SocketException: Bad address family +``` + +You have to manually disable `IPv6` by calling + +``` java +java.lang.System.setProperty("java.net.preferIPv6Addresses", "false"); +java.lang.System.setProperty("java.net.preferIPv4Stack", "true"); +``` + +somewhere in your project, before instantiating the `WebSocketClient` class. +You can check if you are currently testing in the Android Emulator like this + +``` java +if ("google_sdk".equals( Build.PRODUCT )) { + // ... disable IPv6 +} +``` + + +Getting Support +--------------- + +If you are looking for help using `Java-WebSocket` you might want to check out the +[#java-websocket](http://webchat.freenode.net/?channels=java-websocket) IRC room +on the FreeNode IRC network. + + +License +------- + +Everything found in this repo is licensed under an MIT license. See +the `LICENSE` file for specifics. diff --git a/cb-tools/java-source/java-source/autobahn reports/clients/index.html b/cb-tools/java-source/java-source/autobahn reports/clients/index.html new file mode 100644 index 00000000..5fa8f93d --- /dev/null +++ b/cb-tools/java-source/java-source/autobahn reports/clients/index.html @@ -0,0 +1,3614 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +table { + border-collapse: collapse; + border-spacing: 0px; +} + +td { + margin: 0; + border: 1px solid #fff; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + font-size: 0.9em; + color: #fff; +} + +table#agent_case_results { + border-collapse: collapse; + border-spacing: 0px; + border-radius: 10px; + margin-left: 20px; + margin-right: 20px; + margin-bottom: 40px; +} + +td.outcome_desc { + width: 100%; + color: #333; + font-size: 0.8em; +} + +tr.agent_case_result_row a { + color: #eee; +} + +td.agent { + color: #fff; + font-size: 1.0em; + text-align: center; + background-color: #048; + font-size: 0.8em; + word-wrap: break-word; + padding: 4px; + width: 140px; +} + +td.case { + background-color: #666; + text-align: left; + padding-left: 40px; + font-size: 0.9em; +} + +td.case_category { + color: #fff; + background-color: #000; + text-align: left; + padding-left: 20px; + font-size: 1.0em; +} + +td.case_subcategory { + color: #fff; + background-color: #333; + text-align: left; + padding-left: 30px; + font-size: 0.9em; +} + +td.close { + width: 15px; + padding: 6px; + font-size: 0.7em; + color: #fff; + min-width: 0px; +} + +td.case_ok { + background-color: #0a0; + text-align: center; +} + +td.case_almost { + background-color: #6d6; + text-align: center; +} + +td.case_non_strict, td.case_no_close { + background-color: #9a0; + text-align: center; +} + +td.case_info { + background-color: #4095BF; + text-align: center; +} + +td.case_failed { + background-color: #900; + text-align: center; +} + +td.case_missing { + color: #fff; + background-color: #a05a2c; + text-align: center; +} + +span.case_duration { + font-size: 0.7em; + color: #fff; +} + +*.unselectable { + user-select: none; + -moz-user-select: -moz-none; + -webkit-user-select: none; + -khtml-user-select: none; +} + +div#toggle_button { + position: fixed; + bottom: 10px; + right: 10px; + background-color: rgba(60, 60, 60, 0.5); + border-radius: 12px; + color: #fff; + font-size: 0.7em; + padding: 5px 10px; +} + +div#toggle_button:hover { + background-color: #028ec9; +} +</style> + <script language="javascript"> +var isClosed = false; + +function closeHelper(display,colspan) { + // hide all close codes + var a = document.getElementsByClassName("close_hide"); + for (var i in a) { + if (a[i].style) { + a[i].style.display = display; + } + } + + // set colspans + var a = document.getElementsByClassName("close_flex"); + for (var i in a) { + a[i].colSpan = colspan; + } + + var a = document.getElementsByClassName("case_subcategory"); + for (var i in a) { + a[i].colSpan = 1 * colspan + 1; + } +} + +function toggleClose() { + if (window.isClosed == false) { + closeHelper("none",1); + window.isClosed = true; + } else { + closeHelper("table-cell",2); + window.isClosed = false; + } +} +</script> + </head> + <body> + <a href="#"><div id="toggle_button" class="unselectable" onclick="toggleClose();">Toggle Details</div></a> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <div id="master_report_header" class="block"> + <p id="intro">Summary report generated on 2012-02-04T14:47:30Z (UTC) by <a href="http://www.tavendo.de/autobahn">Autobahn WebSockets</a> v0.4.10.</p> + + <table id="case_outcome_desc"> + <tr> + <td class="case_ok">Pass</td> + <td class="outcome_desc">Test case was executed and passed successfully.</td> + </tr> + <tr> + <td class="case_non_strict">Non-Strict</td> + <td class="outcome_desc">Test case was executed and passed non-strictly. + A non-strict behavior is one that does not adhere to a SHOULD-behavior as described in the protocol specification or + a well-defined, canonical behavior that appears to be desirable but left open in the protocol specification. + An implementation with non-strict behavior is still conformant to the protocol specification.</td> + </tr> + <tr> + <td class="case_failed">Fail</td> + <td class="outcome_desc">Test case was executed and failed. An implementation which fails a test case - other + than a performance/limits related one - is non-conforming to a MUST-behavior as described in the protocol specification.</td> + </tr> + <tr> + <td class="case_info">Info</td> + <td class="outcome_desc">Informational test case which detects certain implementation behavior left unspecified by the spec + but nevertheless potentially interesting to implementors.</td> + </tr> + <tr> + <td class="case_missing">Missing</td> + <td class="outcome_desc">Test case is missing, either because it was skipped via the test suite configuration + or deactivated, i.e. because the implementation does not implement the tested feature or breaks during running + the test case.</td> + </tr> + </table> + </div> + <table id="agent_case_results"> + <tr class="case_category_row"> + <td class="case_category">1 Framing</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">1.1 Text Messages</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_1_1">Case 1.1.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_1_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_1_2">Case 1.1.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_1_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_1_3">Case 1.1.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_1_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_1_4">Case 1.1.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_1_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_1_5">Case 1.1.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_1_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_1_6">Case 1.1.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_1_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_1_7">Case 1.1.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_1_7.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_1_8">Case 1.1.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_1_8.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">1 Framing</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">1.2 Binary Messages</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_2_1">Case 1.2.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_2_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_2_2">Case 1.2.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_2_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_2_3">Case 1.2.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_2_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_2_4">Case 1.2.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_2_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_2_5">Case 1.2.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_2_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_2_6">Case 1.2.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_2_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_2_7">Case 1.2.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_2_7.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_2_8">Case 1.2.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_2_8.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">2 Pings/Pongs</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_2_1">Case 2.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_2_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_2_2">Case 2.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_2_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_2_3">Case 2.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_2_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_2_4">Case 2.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_2_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_2_5">Case 2.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_2_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_2_6">Case 2.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_2_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_2_7">Case 2.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_2_7.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_2_8">Case 2.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_2_8.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_2_9">Case 2.9</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_2_9.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_2_10">Case 2.10</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_2_10.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_2_11">Case 2.11</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_2_11.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">3 Reserved Bits</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_3_1">Case 3.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_3_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_3_2">Case 3.2</a></td> + <td class="case_non_strict"><a href="tootallnate_websocket_case_3_2.html">Non-Strict</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_3_3">Case 3.3</a></td> + <td class="case_non_strict"><a href="tootallnate_websocket_case_3_3.html">Non-Strict</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_3_4">Case 3.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_3_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_3_5">Case 3.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_3_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_3_6">Case 3.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_3_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_3_7">Case 3.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_3_7.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">4 Opcodes</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">4.1 Non-control Opcodes</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_4_1_1">Case 4.1.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_4_1_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_4_1_2">Case 4.1.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_4_1_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_4_1_3">Case 4.1.3</a></td> + <td class="case_non_strict"><a href="tootallnate_websocket_case_4_1_3.html">Non-Strict</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_4_1_4">Case 4.1.4</a></td> + <td class="case_non_strict"><a href="tootallnate_websocket_case_4_1_4.html">Non-Strict</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_4_1_5">Case 4.1.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_4_1_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">4 Opcodes</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">4.2 Control Opcodes</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_4_2_1">Case 4.2.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_4_2_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_4_2_2">Case 4.2.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_4_2_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_4_2_3">Case 4.2.3</a></td> + <td class="case_non_strict"><a href="tootallnate_websocket_case_4_2_3.html">Non-Strict</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_4_2_4">Case 4.2.4</a></td> + <td class="case_non_strict"><a href="tootallnate_websocket_case_4_2_4.html">Non-Strict</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_4_2_5">Case 4.2.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_4_2_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">5 Fragmentation</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_1">Case 5.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_2">Case 5.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_3">Case 5.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_4">Case 5.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_5">Case 5.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_6">Case 5.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_7">Case 5.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_7.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_8">Case 5.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_8.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_9">Case 5.9</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_9.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_10">Case 5.10</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_10.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_11">Case 5.11</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_11.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_12">Case 5.12</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_12.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_13">Case 5.13</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_13.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_14">Case 5.14</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_14.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_15">Case 5.15</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_15.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_16">Case 5.16</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_16.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_17">Case 5.17</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_17.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_18">Case 5.18</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_18.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_19">Case 5.19</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_19.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_20">Case 5.20</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_20.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.1 Valid UTF-8 with zero payload fragments</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_1_1">Case 6.1.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_1_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_1_2">Case 6.1.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_1_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_1_3">Case 6.1.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_1_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.2 Valid UTF-8 unfragmented, fragmented on code-points and within code-points</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_2_1">Case 6.2.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_2_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_2_2">Case 6.2.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_2_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_2_3">Case 6.2.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_2_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_2_4">Case 6.2.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_2_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.3 Invalid UTF-8 differently fragmented</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_3_1">Case 6.3.1</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_3_1.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_3_2">Case 6.3.2</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_3_2.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.4 Fail-fast on invalid UTF-8</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_4_1">Case 6.4.1</a></td> + <td class="case_non_strict"><a href="tootallnate_websocket_case_6_4_1.html">Non-Strict</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_4_2">Case 6.4.2</a></td> + <td class="case_non_strict"><a href="tootallnate_websocket_case_6_4_2.html">Non-Strict</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_4_3">Case 6.4.3</a></td> + <td class="case_non_strict"><a href="tootallnate_websocket_case_6_4_3.html">Non-Strict</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_4_4">Case 6.4.4</a></td> + <td class="case_non_strict"><a href="tootallnate_websocket_case_6_4_4.html">Non-Strict</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.5 Some valid UTF-8 sequences</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_5_1">Case 6.5.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_5_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.6 All prefixes of a valid UTF-8 string that contains multi-byte code points</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_6_1">Case 6.6.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_6_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_6_2">Case 6.6.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_6_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_6_3">Case 6.6.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_6_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_6_4">Case 6.6.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_6_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_6_5">Case 6.6.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_6_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_6_6">Case 6.6.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_6_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_6_7">Case 6.6.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_6_7.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_6_8">Case 6.6.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_6_8.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_6_9">Case 6.6.9</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_6_9.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_6_10">Case 6.6.10</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_6_10.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_6_11">Case 6.6.11</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_6_11.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.7 First possible sequence of a certain length</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_7_1">Case 6.7.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_7_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_7_2">Case 6.7.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_7_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_7_3">Case 6.7.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_7_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_7_4">Case 6.7.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_7_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.8 First possible sequence length 5/6 (invalid codepoints)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_8_1">Case 6.8.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_8_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_8_2">Case 6.8.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_8_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.9 Last possible sequence of a certain length</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_9_1">Case 6.9.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_9_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_9_2">Case 6.9.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_9_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_9_3">Case 6.9.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_9_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_9_4">Case 6.9.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_9_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.10 Last possible sequence length 4/5/6 (invalid codepoints)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_10_1">Case 6.10.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_10_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_10_2">Case 6.10.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_10_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_10_3">Case 6.10.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_10_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.11 Other boundary conditions</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_11_1">Case 6.11.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_11_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_11_2">Case 6.11.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_11_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_11_3">Case 6.11.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_11_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_11_4">Case 6.11.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_11_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_11_5">Case 6.11.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_11_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.12 Unexpected continuation bytes</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_12_1">Case 6.12.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_12_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_12_2">Case 6.12.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_12_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_12_3">Case 6.12.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_12_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_12_4">Case 6.12.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_12_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_12_5">Case 6.12.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_12_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_12_6">Case 6.12.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_12_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_12_7">Case 6.12.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_12_7.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_12_8">Case 6.12.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_12_8.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.13 Lonely start characters</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_13_1">Case 6.13.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_13_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_13_2">Case 6.13.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_13_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_13_3">Case 6.13.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_13_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_13_4">Case 6.13.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_13_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_13_5">Case 6.13.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_13_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.14 Sequences with last continuation byte missing</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_14_1">Case 6.14.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_14_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_14_2">Case 6.14.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_14_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_14_3">Case 6.14.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_14_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_14_4">Case 6.14.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_14_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_14_5">Case 6.14.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_14_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_14_6">Case 6.14.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_14_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_14_7">Case 6.14.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_14_7.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_14_8">Case 6.14.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_14_8.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_14_9">Case 6.14.9</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_14_9.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_14_10">Case 6.14.10</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_14_10.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.15 Concatenation of incomplete sequences</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_15_1">Case 6.15.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_15_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.16 Impossible bytes</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_16_1">Case 6.16.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_16_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_16_2">Case 6.16.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_16_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_16_3">Case 6.16.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_16_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.17 Examples of an overlong ASCII character</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_17_1">Case 6.17.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_17_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_17_2">Case 6.17.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_17_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_17_3">Case 6.17.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_17_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_17_4">Case 6.17.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_17_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_17_5">Case 6.17.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_17_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.18 Maximum overlong sequences</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_18_1">Case 6.18.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_18_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_18_2">Case 6.18.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_18_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_18_3">Case 6.18.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_18_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_18_4">Case 6.18.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_18_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_18_5">Case 6.18.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_18_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.19 Overlong representation of the NUL character</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_19_1">Case 6.19.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_19_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_19_2">Case 6.19.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_19_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_19_3">Case 6.19.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_19_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_19_4">Case 6.19.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_19_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_19_5">Case 6.19.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_19_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.20 Single UTF-16 surrogates</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_20_1">Case 6.20.1</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_20_1.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_20_2">Case 6.20.2</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_20_2.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_20_3">Case 6.20.3</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_20_3.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_20_4">Case 6.20.4</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_20_4.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_20_5">Case 6.20.5</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_20_5.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_20_6">Case 6.20.6</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_20_6.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_20_7">Case 6.20.7</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_20_7.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.21 Paired UTF-16 surrogates</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_21_1">Case 6.21.1</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_21_1.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_21_2">Case 6.21.2</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_21_2.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_21_3">Case 6.21.3</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_21_3.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_21_4">Case 6.21.4</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_21_4.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_21_5">Case 6.21.5</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_21_5.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_21_6">Case 6.21.6</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_21_6.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_21_7">Case 6.21.7</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_21_7.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_21_8">Case 6.21.8</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_21_8.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.22 Non-character code points (valid UTF-8)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_1">Case 6.22.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_2">Case 6.22.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_3">Case 6.22.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_4">Case 6.22.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_5">Case 6.22.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_6">Case 6.22.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_7">Case 6.22.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_7.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_8">Case 6.22.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_8.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_9">Case 6.22.9</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_9.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_10">Case 6.22.10</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_10.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_11">Case 6.22.11</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_11.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_12">Case 6.22.12</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_12.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_13">Case 6.22.13</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_13.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_14">Case 6.22.14</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_14.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_15">Case 6.22.15</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_15.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_16">Case 6.22.16</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_16.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_17">Case 6.22.17</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_17.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_18">Case 6.22.18</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_18.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_19">Case 6.22.19</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_19.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_20">Case 6.22.20</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_20.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_21">Case 6.22.21</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_21.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_22">Case 6.22.22</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_22.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_23">Case 6.22.23</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_23.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_24">Case 6.22.24</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_24.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_25">Case 6.22.25</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_25.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_26">Case 6.22.26</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_26.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_27">Case 6.22.27</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_27.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_28">Case 6.22.28</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_28.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_29">Case 6.22.29</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_29.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_30">Case 6.22.30</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_30.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_31">Case 6.22.31</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_31.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_32">Case 6.22.32</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_32.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_33">Case 6.22.33</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_33.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_34">Case 6.22.34</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_34.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.23 Unicode replacement character</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_23_1">Case 6.23.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_23_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">7 Close Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">7.1 Basic close behavior (fuzzer initiated)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_1_1">Case 7.1.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_1_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_1_2">Case 7.1.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_1_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_1_3">Case 7.1.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_1_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_1_4">Case 7.1.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_1_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_1_5">Case 7.1.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_1_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_1_6">Case 7.1.6</a></td> + <td class="case_info"><a href="tootallnate_websocket_case_7_1_6.html">Info</a></td><td class="close close_hide case_info"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">7 Close Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">7.3 Close frame structure: payload length (fuzzer initiated)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_3_1">Case 7.3.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_3_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_3_2">Case 7.3.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_3_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_3_3">Case 7.3.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_3_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_3_4">Case 7.3.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_3_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_3_5">Case 7.3.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_3_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_3_6">Case 7.3.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_3_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">7 Close Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">7.5 Close frame structure: payload value (fuzzer initiated)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_5_1">Case 7.5.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_5_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">7 Close Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">7.7 Close frame structure: valid close codes (fuzzer initiated)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_1">Case 7.7.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_2">Case 7.7.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_3">Case 7.7.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_4">Case 7.7.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_5">Case 7.7.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_6">Case 7.7.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_7">Case 7.7.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_7.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_8">Case 7.7.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_8.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_9">Case 7.7.9</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_9.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_10">Case 7.7.10</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_10.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_11">Case 7.7.11</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_11.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_12">Case 7.7.12</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_12.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_13">Case 7.7.13</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_13.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">7 Close Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">7.9 Close frame structure: invalid close codes (fuzzer initiated)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_1">Case 7.9.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_2">Case 7.9.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_3">Case 7.9.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_4">Case 7.9.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_5">Case 7.9.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_6">Case 7.9.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_7">Case 7.9.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_7.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_8">Case 7.9.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_8.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_9">Case 7.9.9</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_9.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_10">Case 7.9.10</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_10.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_11">Case 7.9.11</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_11.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_12">Case 7.9.12</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_12.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_13">Case 7.9.13</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_13.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">7 Close Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">7.13 Informational close information (fuzzer initiated)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_13_1">Case 7.13.1</a></td> + <td class="case_info"><a href="tootallnate_websocket_case_7_13_1.html">Info</a></td><td class="close close_hide case_info"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_13_2">Case 7.13.2</a></td> + <td class="case_info"><a href="tootallnate_websocket_case_7_13_2.html">Info</a></td><td class="close close_hide case_info"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">9 Limits/Performance</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">9.1 Text Message (increasing size)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_1_1">Case 9.1.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_1_1.html">Pass</a><br/><span class="case_duration">88 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_1_2">Case 9.1.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_1_2.html">Pass</a><br/><span class="case_duration">354 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_1_3">Case 9.1.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_1_3.html">Pass</a><br/><span class="case_duration">1278 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_1_4">Case 9.1.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_1_4.html">Pass</a><br/><span class="case_duration">5138 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_1_5">Case 9.1.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_1_5.html">Pass</a><br/><span class="case_duration">10252 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_1_6">Case 9.1.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_1_6.html">Pass</a><br/><span class="case_duration">20383 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">9 Limits/Performance</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">9.2 Binary Message (increasing size)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_2_1">Case 9.2.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_2_1.html">Pass</a><br/><span class="case_duration">50 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_2_2">Case 9.2.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_2_2.html">Pass</a><br/><span class="case_duration">149 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_2_3">Case 9.2.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_2_3.html">Pass</a><br/><span class="case_duration">576 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_2_4">Case 9.2.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_2_4.html">Pass</a><br/><span class="case_duration">2368 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_2_5">Case 9.2.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_2_5.html">Pass</a><br/><span class="case_duration">4974 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_2_6">Case 9.2.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_2_6.html">Pass</a><br/><span class="case_duration">9816 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">9 Limits/Performance</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">9.3 Fragmented Text Message (fixed size, increasing fragment size)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_3_1">Case 9.3.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_3_1.html">Pass</a><br/><span class="case_duration">73618 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_3_2">Case 9.3.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_3_2.html">Pass</a><br/><span class="case_duration">21904 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_3_3">Case 9.3.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_3_3.html">Pass</a><br/><span class="case_duration">9511 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_3_4">Case 9.3.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_3_4.html">Pass</a><br/><span class="case_duration">6355 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_3_5">Case 9.3.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_3_5.html">Pass</a><br/><span class="case_duration">5412 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_3_6">Case 9.3.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_3_6.html">Pass</a><br/><span class="case_duration">5167 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_3_7">Case 9.3.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_3_7.html">Pass</a><br/><span class="case_duration">5097 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_3_8">Case 9.3.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_3_8.html">Pass</a><br/><span class="case_duration">5109 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_3_9">Case 9.3.9</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_3_9.html">Pass</a><br/><span class="case_duration">5064 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">9 Limits/Performance</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">9.4 Fragmented Binary Message (fixed size, increasing fragment size)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_4_1">Case 9.4.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_4_1.html">Pass</a><br/><span class="case_duration">68983 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_4_2">Case 9.4.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_4_2.html">Pass</a><br/><span class="case_duration">19104 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_4_3">Case 9.4.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_4_3.html">Pass</a><br/><span class="case_duration">6678 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_4_4">Case 9.4.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_4_4.html">Pass</a><br/><span class="case_duration">3500 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_4_5">Case 9.4.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_4_5.html">Pass</a><br/><span class="case_duration">2588 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_4_6">Case 9.4.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_4_6.html">Pass</a><br/><span class="case_duration">2373 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_4_7">Case 9.4.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_4_7.html">Pass</a><br/><span class="case_duration">2271 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_4_8">Case 9.4.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_4_8.html">Pass</a><br/><span class="case_duration">2251 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_4_9">Case 9.4.9</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_4_9.html">Pass</a><br/><span class="case_duration">2252 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">9 Limits/Performance</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">9.5 Text Message (fixed size, increasing chop size)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_5_1">Case 9.5.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_5_1.html">Pass</a><br/><span class="case_duration">18135 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_5_2">Case 9.5.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_5_2.html">Pass</a><br/><span class="case_duration">9891 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_5_3">Case 9.5.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_5_3.html">Pass</a><br/><span class="case_duration">5464 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_5_4">Case 9.5.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_5_4.html">Pass</a><br/><span class="case_duration">3387 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_5_5">Case 9.5.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_5_5.html">Pass</a><br/><span class="case_duration">2355 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_5_6">Case 9.5.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_5_6.html">Pass</a><br/><span class="case_duration">1902 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">9 Limits/Performance</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">9.6 Binary Text Message (fixed size, increasing chop size)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_6_1">Case 9.6.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_6_1.html">Pass</a><br/><span class="case_duration">17756 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_6_2">Case 9.6.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_6_2.html">Pass</a><br/><span class="case_duration">9052 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_6_3">Case 9.6.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_6_3.html">Pass</a><br/><span class="case_duration">4858 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_6_4">Case 9.6.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_6_4.html">Pass</a><br/><span class="case_duration">2682 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_6_5">Case 9.6.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_6_5.html">Pass</a><br/><span class="case_duration">1675 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_6_6">Case 9.6.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_6_6.html">Pass</a><br/><span class="case_duration">1146 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">9 Limits/Performance</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">9.7 Text Message Roundtrip Time (fixed number, increasing size)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_7_1">Case 9.7.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_7_1.html">Pass</a><br/><span class="case_duration">258 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_7_2">Case 9.7.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_7_2.html">Pass</a><br/><span class="case_duration">273 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_7_3">Case 9.7.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_7_3.html">Pass</a><br/><span class="case_duration">348 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_7_4">Case 9.7.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_7_4.html">Pass</a><br/><span class="case_duration">595 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_7_5">Case 9.7.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_7_5.html">Pass</a><br/><span class="case_duration">1671 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_7_6">Case 9.7.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_7_6.html">Pass</a><br/><span class="case_duration">5664 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">9 Limits/Performance</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">9.8 Binary Message Roundtrip Time (fixed number, increasing size)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_8_1">Case 9.8.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_8_1.html">Pass</a><br/><span class="case_duration">247 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_8_2">Case 9.8.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_8_2.html">Pass</a><br/><span class="case_duration">262 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_8_3">Case 9.8.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_8_3.html">Pass</a><br/><span class="case_duration">302 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_8_4">Case 9.8.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_8_4.html">Pass</a><br/><span class="case_duration">396 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_8_5">Case 9.8.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_8_5.html">Pass</a><br/><span class="case_duration">957 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_8_6">Case 9.8.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_8_6.html">Pass</a><br/><span class="case_duration">2814 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">10 Autobahn Protocol Options</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">10.1 Auto-Fragmentation</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_10_1_1">Case 10.1.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_10_1_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + </table> + <br/><hr/> + <div id="test_case_descriptions"> + <br/> + <a name="case_desc_1_1_1"></a> + <h2>Case 1.1.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message with payload 0.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with empty payload). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_1_2"></a> + <h2>Case 1.1.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 125.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_1_3"></a> + <h2>Case 1.1.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 126.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_1_4"></a> + <h2>Case 1.1.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 127.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_1_5"></a> + <h2>Case 1.1.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 128.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_1_6"></a> + <h2>Case 1.1.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 65535.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_1_7"></a> + <h2>Case 1.1.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 65536.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_1_8"></a> + <h2>Case 1.1.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 65536. Sent out data in chops of 997 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_2_1"></a> + <h2>Case 1.2.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message with payload 0.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with empty payload). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_2_2"></a> + <h2>Case 1.2.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 125.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_2_3"></a> + <h2>Case 1.2.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 126.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_2_4"></a> + <h2>Case 1.2.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 127.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_2_5"></a> + <h2>Case 1.2.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 128.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_2_6"></a> + <h2>Case 1.2.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 65535.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_2_7"></a> + <h2>Case 1.2.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 65536.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_2_8"></a> + <h2>Case 1.2.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 65536. Sent out data in chops of 997 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_2_1"></a> + <h2>Case 2.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send ping without payload.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Pong (with empty payload) is sent in reply to Ping. Clean close with normal code.</p> + <br/> + <a name="case_desc_2_2"></a> + <h2>Case 2.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send ping with small text payload.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Pong with payload echo'ed is sent in reply to Ping. Clean close with normal code.</p> + <br/> + <a name="case_desc_2_3"></a> + <h2>Case 2.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send ping with small binary (non UTF-8) payload.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Pong with payload echo'ed is sent in reply to Ping. Clean close with normal code.</p> + <br/> + <a name="case_desc_2_4"></a> + <h2>Case 2.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send ping with binary payload of 125 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Pong with payload echo'ed is sent in reply to Ping. Clean close with normal code.</p> + <br/> + <a name="case_desc_2_5"></a> + <h2>Case 2.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send ping with binary payload of 126 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Connection is failed immediately (1002/Protocol Error), since control frames are only allowed to have payload up to and including 125 octets..</p> + <br/> + <a name="case_desc_2_6"></a> + <h2>Case 2.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send ping with binary payload of 125 octets, send in octet-wise chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Pong with payload echo'ed is sent in reply to Ping. Implementations must be TCP clean. Clean close with normal code.</p> + <br/> + <a name="case_desc_2_7"></a> + <h2>Case 2.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send unsolicited pong without payload. Verify nothing is received. Clean close with normal code.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Nothing.</p> + <br/> + <a name="case_desc_2_8"></a> + <h2>Case 2.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send unsolicited pong with payload. Verify nothing is received. Clean close with normal code.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Nothing.</p> + <br/> + <a name="case_desc_2_9"></a> + <h2>Case 2.9</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send unsolicited pong with payload. Send ping with payload. Verify pong for ping is received.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Nothing in reply to own Pong, but Pong with payload echo'ed in reply to Ping. Clean close with normal code.</p> + <br/> + <a name="case_desc_2_10"></a> + <h2>Case 2.10</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 10 Pings with payload.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Pongs for our Pings with all the payloads. Note: This is not required by the Spec .. but we check for this behaviour anyway. Clean close with normal code.</p> + <br/> + <a name="case_desc_2_11"></a> + <h2>Case 2.11</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 10 Pings with payload. Send out octets in octet-wise chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Pongs for our Pings with all the payloads. Note: This is not required by the Spec .. but we check for this behaviour anyway. Clean close with normal code.</p> + <br/> + <a name="case_desc_3_1"></a> + <h2>Case 3.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message with <b>RSV = 1</b>.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately (1002/protocol error), since RSV must be 0, when no extension defining RSV meaning has been negoiated.</p> + <br/> + <a name="case_desc_3_2"></a> + <h2>Case 3.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message, then send again with <b>RSV = 2</b>, then send Ping.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echo for first message is received, but then connection is failed immediately, since RSV must be 0, when no extension defining RSV meaning has been negoiated. The Pong is not received.</p> + <br/> + <a name="case_desc_3_3"></a> + <h2>Case 3.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message, then send again with <b>RSV = 3</b>, then send Ping. Octets are sent in frame-wise chops. Octets are sent in octet-wise chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echo for first message is received, but then connection is failed immediately, since RSV must be 0, when no extension defining RSV meaning has been negoiated. The Pong is not received.</p> + <br/> + <a name="case_desc_3_4"></a> + <h2>Case 3.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message, then send again with <b>RSV = 4</b>, then send Ping. Octets are sent in octet-wise chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echo for first message is received, but then connection is failed immediately, since RSV must be 0, when no extension defining RSV meaning has been negoiated. The Pong is not received.</p> + <br/> + <a name="case_desc_3_5"></a> + <h2>Case 3.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small binary message with <b>RSV = 5</b>.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since RSV must be 0.</p> + <br/> + <a name="case_desc_3_6"></a> + <h2>Case 3.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send Ping with <b>RSV = 6</b>.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since RSV must be 0.</p> + <br/> + <a name="case_desc_3_7"></a> + <h2>Case 3.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send Close with <b>RSV = 7</b>.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since RSV must be 0.</p> + <br/> + <a name="case_desc_4_1_1"></a> + <h2>Case 4.1.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send frame with reserved non-control <b>Opcode = 3</b>.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately.</p> + <br/> + <a name="case_desc_4_1_2"></a> + <h2>Case 4.1.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send frame with reserved non-control <b>Opcode = 4</b> and non-empty payload.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately.</p> + <br/> + <a name="case_desc_4_1_3"></a> + <h2>Case 4.1.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message, then send frame with reserved non-control <b>Opcode = 5</b>, then send Ping.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.</p> + <br/> + <a name="case_desc_4_1_4"></a> + <h2>Case 4.1.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message, then send frame with reserved non-control <b>Opcode = 6</b> and non-empty payload, then send Ping.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.</p> + <br/> + <a name="case_desc_4_1_5"></a> + <h2>Case 4.1.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message, then send frame with reserved non-control <b>Opcode = 7</b> and non-empty payload, then send Ping.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.</p> + <br/> + <a name="case_desc_4_2_1"></a> + <h2>Case 4.2.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send frame with reserved control <b>Opcode = 11</b>.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately.</p> + <br/> + <a name="case_desc_4_2_2"></a> + <h2>Case 4.2.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send frame with reserved control <b>Opcode = 12</b> and non-empty payload.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately.</p> + <br/> + <a name="case_desc_4_2_3"></a> + <h2>Case 4.2.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message, then send frame with reserved control <b>Opcode = 13</b>, then send Ping.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.</p> + <br/> + <a name="case_desc_4_2_4"></a> + <h2>Case 4.2.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message, then send frame with reserved control <b>Opcode = 14</b> and non-empty payload, then send Ping.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.</p> + <br/> + <a name="case_desc_4_2_5"></a> + <h2>Case 4.2.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message, then send frame with reserved control <b>Opcode = 15</b> and non-empty payload, then send Ping.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.</p> + <br/> + <a name="case_desc_5_1"></a> + <h2>Case 5.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send Ping fragmented into 2 fragments.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Connection is failed immediately, since control message MUST NOT be fragmented.</p> + <br/> + <a name="case_desc_5_2"></a> + <h2>Case 5.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send Pong fragmented into 2 fragments.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Connection is failed immediately, since control message MUST NOT be fragmented.</p> + <br/> + <a name="case_desc_5_3"></a> + <h2>Case 5.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text Message fragmented into 2 fragments.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Message is processed and echo'ed back to us.</p> + <br/> + <a name="case_desc_5_4"></a> + <h2>Case 5.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text Message fragmented into 2 fragments, octets are sent in frame-wise chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Message is processed and echo'ed back to us.</p> + <br/> + <a name="case_desc_5_5"></a> + <h2>Case 5.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text Message fragmented into 2 fragments, octets are sent in octet-wise chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Message is processed and echo'ed back to us.</p> + <br/> + <a name="case_desc_5_6"></a> + <h2>Case 5.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text Message fragmented into 2 fragments, one ping with payload in-between.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>A pong is received, then the message is echo'ed back to us.</p> + <br/> + <a name="case_desc_5_7"></a> + <h2>Case 5.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text Message fragmented into 2 fragments, one ping with payload in-between. Octets are sent in frame-wise chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>A pong is received, then the message is echo'ed back to us.</p> + <br/> + <a name="case_desc_5_8"></a> + <h2>Case 5.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text Message fragmented into 2 fragments, one ping with payload in-between. Octets are sent in octet-wise chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>A pong is received, then the message is echo'ed back to us.</p> + <br/> + <a name="case_desc_5_9"></a> + <h2>Case 5.9</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send unfragmented Text Message after Continuation Frame with FIN = true, where there is nothing to continue, sent in one chop.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since there is no message to continue.</p> + <br/> + <a name="case_desc_5_10"></a> + <h2>Case 5.10</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send unfragmented Text Message after Continuation Frame with FIN = true, where there is nothing to continue, sent in per-frame chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since there is no message to continue.</p> + <br/> + <a name="case_desc_5_11"></a> + <h2>Case 5.11</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send unfragmented Text Message after Continuation Frame with FIN = true, where there is nothing to continue, sent in octet-wise chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since there is no message to continue.</p> + <br/> + <a name="case_desc_5_12"></a> + <h2>Case 5.12</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send unfragmented Text Message after Continuation Frame with FIN = false, where there is nothing to continue, sent in one chop.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since there is no message to continue.</p> + <br/> + <a name="case_desc_5_13"></a> + <h2>Case 5.13</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send unfragmented Text Message after Continuation Frame with FIN = false, where there is nothing to continue, sent in per-frame chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since there is no message to continue.</p> + <br/> + <a name="case_desc_5_14"></a> + <h2>Case 5.14</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send unfragmented Text Message after Continuation Frame with FIN = false, where there is nothing to continue, sent in octet-wise chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since there is no message to continue.</p> + <br/> + <a name="case_desc_5_15"></a> + <h2>Case 5.15</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text Message fragmented into 2 fragments, then Continuation Frame with FIN = false where there is nothing to continue, then unfragmented Text Message, all sent in one chop.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since there is no message to continue.</p> + <br/> + <a name="case_desc_5_16"></a> + <h2>Case 5.16</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Repeated 2x: Continuation Frame with FIN = false (where there is nothing to continue), then text Message fragmented into 2 fragments.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since there is no message to continue.</p> + <br/> + <a name="case_desc_5_17"></a> + <h2>Case 5.17</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Repeated 2x: Continuation Frame with FIN = true (where there is nothing to continue), then text Message fragmented into 2 fragments.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since there is no message to continue.</p> + <br/> + <a name="case_desc_5_18"></a> + <h2>Case 5.18</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text Message fragmented into 2 fragments, with both frame opcodes set to text, sent in one chop.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since all data frames after the initial data frame must have opcode 0.</p> + <br/> + <a name="case_desc_5_19"></a> + <h2>Case 5.19</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>A fragmented text message is sent in multiple frames. After + sending the first 2 frames of the text message, a Ping is sent. Then we wait 1s, + then we send 2 more text fragments, another Ping and then the final text fragment. + Everything is legal.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The peer immediately answers the first Ping before + it has received the last text message fragment. The peer pong's back the Ping's + payload exactly, and echo's the payload of the fragmented message back to us.</p> + <br/> + <a name="case_desc_5_20"></a> + <h2>Case 5.20</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Same as Case 5.19, but send all frames with SYNC = True. + Note, this does not change the octets sent in any way, only how the stream + is chopped up on the wire.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Same as Case 5.19. Implementations must be agnostic to how + octet stream is chopped up on wire (must be TCP clean).</p> + <br/> + <a name="case_desc_6_1_1"></a> + <h2>Case 6.1.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message of length 0.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>A message is echo'ed back to us (with empty payload).</p> + <br/> + <a name="case_desc_6_1_2"></a> + <h2>Case 6.1.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented text message, 3 fragments each of length 0.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>A message is echo'ed back to us (with empty payload).</p> + <br/> + <a name="case_desc_6_1_3"></a> + <h2>Case 6.1.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented text message, 3 fragments, first and last of length 0, middle non-empty.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>A message is echo'ed back to us (with payload = payload of middle fragment).</p> + <br/> + <a name="case_desc_6_2_1"></a> + <h2>Case 6.2.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a valid UTF-8 text message in one fragment.<br><br>MESSAGE:<br>Hello-µ@ßöäüàá-UTF-8!!<br>48656c6c6f2dc2b540c39fc3b6c3a4c3bcc3a0c3a12d5554462d382121</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_2_2"></a> + <h2>Case 6.2.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a valid UTF-8 text message in two fragments, fragmented on UTF-8 code point boundary.<br><br>MESSAGE FRAGMENT 1:<br>Hello-µ@ßöä<br>48656c6c6f2dc2b540c39fc3b6c3a4<br><br>MESSAGE FRAGMENT 2:<br>üàá-UTF-8!!<br>c3bcc3a0c3a12d5554462d382121</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_2_3"></a> + <h2>Case 6.2.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a valid UTF-8 text message in fragments of 1 octet, resulting in frames ending on positions which are not code point ends.<br><br>MESSAGE:<br>Hello-µ@ßöäüàá-UTF-8!!<br>48656c6c6f2dc2b540c39fc3b6c3a4c3bcc3a0c3a12d5554462d382121</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_2_4"></a> + <h2>Case 6.2.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a valid UTF-8 text message in fragments of 1 octet, resulting in frames ending on positions which are not code point ends.<br><br>MESSAGE:<br>κόσμε<br>cebae1bdb9cf83cebcceb5</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_3_1"></a> + <h2>Case 6.3.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send invalid UTF-8 text message unfragmented.<br><br>MESSAGE:<br>κόσμε���edited<br>cebae1bdb9cf83cebcceb5eda080656469746564</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_3_2"></a> + <h2>Case 6.3.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send invalid UTF-8 text message in fragments of 1 octet, resulting in frames ending on positions which are not code point ends.<br><br>MESSAGE:<br>κόσμε���edited<br>cebae1bdb9cf83cebcceb5eda080656469746564</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_4_1"></a> + <h2>Case 6.4.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send invalid UTF-8 text message in 3 fragments (frames). +First frame payload is valid, then wait, then 2nd frame which contains the payload making the sequence invalid, then wait, then 3rd frame with rest. +Note that PART1 and PART3 are valid UTF-8 in themselves, PART2 is a 0x11000 encoded as in the UTF-8 integer encoding scheme, but the codepoint is invalid (out of range). +<br><br>MESSAGE PARTS:<br> +PART1 = κόσμε (cebae1bdb9cf83cebcceb5)<br> +PART2 = ���� (f4908080)<br> +PART3 = edited (656469746564)<br> +</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The first frame is accepted, we expect to timeout on the first wait. The 2nd frame should be rejected immediately (fail fast on UTF-8). If we timeout, we expect the connection is failed at least then, since the complete message payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_4_2"></a> + <h2>Case 6.4.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Same as Case 6.4.1, but in 2nd frame, we send only up to and including the octet making the complete payload invalid. +<br><br>MESSAGE PARTS:<br> +PART1 = κόσμε� (cebae1bdb9cf83cebcceb5f4)<br> +PART2 = � (90)<br> +PART3 = ��edited (8080656469746564)<br> +</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The first frame is accepted, we expect to timeout on the first wait. The 2nd frame should be rejected immediately (fail fast on UTF-8). If we timeout, we expect the connection is failed at least then, since the complete message payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_4_3"></a> + <h2>Case 6.4.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Same as Case 6.4.1, but we send message not in 3 frames, but in 3 chops of the same message frame. +<br><br>MESSAGE PARTS:<br> +PART1 = κόσμε (cebae1bdb9cf83cebcceb5)<br> +PART2 = ���� (f4908080)<br> +PART3 = edited (656469746564)<br> +</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The first chop is accepted, we expect to timeout on the first wait. The 2nd chop should be rejected immediately (fail fast on UTF-8). If we timeout, we expect the connection is failed at least then, since the complete message payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_4_4"></a> + <h2>Case 6.4.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Same as Case 6.4.2, but we send message not in 3 frames, but in 3 chops of the same message frame. +<br><br>MESSAGE PARTS:<br> +PART1 = κόσμε� (cebae1bdb9cf83cebcceb5f4)<br> +PART2 = � (90)<br> +PART3 = ()<br> +</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The first chop is accepted, we expect to timeout on the first wait. The 2nd chop should be rejected immediately (fail fast on UTF-8). If we timeout, we expect the connection is failed at least then, since the complete message payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_5_1"></a> + <h2>Case 6.5.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>κόσμε<br>cebae1bdb9cf83cebcceb5</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_6_1"></a> + <h2>Case 6.6.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�<br>ce</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_6_2"></a> + <h2>Case 6.6.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>κ<br>ceba</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_6_3"></a> + <h2>Case 6.6.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>κ�<br>cebae1</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_6_4"></a> + <h2>Case 6.6.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>κ�<br>cebae1bd</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_6_5"></a> + <h2>Case 6.6.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>κό<br>cebae1bdb9</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_6_6"></a> + <h2>Case 6.6.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>κό�<br>cebae1bdb9cf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_6_7"></a> + <h2>Case 6.6.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>κόσ<br>cebae1bdb9cf83</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_6_8"></a> + <h2>Case 6.6.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>κόσ�<br>cebae1bdb9cf83ce</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_6_9"></a> + <h2>Case 6.6.9</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>κόσμ<br>cebae1bdb9cf83cebc</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_6_10"></a> + <h2>Case 6.6.10</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>κόσμ�<br>cebae1bdb9cf83cebcce</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_6_11"></a> + <h2>Case 6.6.11</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>κόσμε<br>cebae1bdb9cf83cebcceb5</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_7_1"></a> + <h2>Case 6.7.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�<br>00</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_7_2"></a> + <h2>Case 6.7.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>c280</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_7_3"></a> + <h2>Case 6.7.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>ࠀ<br>e0a080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_7_4"></a> + <h2>Case 6.7.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>𐀀<br>f0908080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_8_1"></a> + <h2>Case 6.8.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�����<br>f888808080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_8_2"></a> + <h2>Case 6.8.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>fc8480808080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_9_1"></a> + <h2>Case 6.9.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>7f</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_9_2"></a> + <h2>Case 6.9.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>߿<br>dfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_9_3"></a> + <h2>Case 6.9.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>efbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_9_4"></a> + <h2>Case 6.9.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f48fbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_10_1"></a> + <h2>Case 6.10.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>����<br>f7bfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_10_2"></a> + <h2>Case 6.10.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�����<br>fbbfbfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_10_3"></a> + <h2>Case 6.10.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>fdbfbfbfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_11_1"></a> + <h2>Case 6.11.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>ed9fbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_11_2"></a> + <h2>Case 6.11.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>ee8080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_11_3"></a> + <h2>Case 6.11.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�<br>efbfbd</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_11_4"></a> + <h2>Case 6.11.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f48fbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_11_5"></a> + <h2>Case 6.11.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>����<br>f4908080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_12_1"></a> + <h2>Case 6.12.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�<br>80</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_12_2"></a> + <h2>Case 6.12.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�<br>bf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_12_3"></a> + <h2>Case 6.12.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>��<br>80bf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_12_4"></a> + <h2>Case 6.12.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>80bf80</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_12_5"></a> + <h2>Case 6.12.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>����<br>80bf80bf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_12_6"></a> + <h2>Case 6.12.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�����<br>80bf80bf80</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_12_7"></a> + <h2>Case 6.12.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>80bf80bf80bf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_12_8"></a> + <h2>Case 6.12.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���������������������������������������������������������������<br>808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_13_1"></a> + <h2>Case 6.13.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>� � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � <br>c020c120c220c320c420c520c620c720c820c920ca20cb20cc20cd20ce20cf20d020d120d220d320d420d520d620d720d820d920da20db20dc20dd20de20</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_13_2"></a> + <h2>Case 6.13.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>� � � � � � � � � � � � � � � <br>e020e120e220e320e420e520e620e720e820e920ea20eb20ec20ed20ee20</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_13_3"></a> + <h2>Case 6.13.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>� � � � � � � <br>f020f120f220f320f420f520f620</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_13_4"></a> + <h2>Case 6.13.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>� � � <br>f820f920fa20</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_13_5"></a> + <h2>Case 6.13.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>� <br>fc20</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_14_1"></a> + <h2>Case 6.14.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�<br>c0</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_14_2"></a> + <h2>Case 6.14.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>��<br>e080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_14_3"></a> + <h2>Case 6.14.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>f08080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_14_4"></a> + <h2>Case 6.14.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>����<br>f8808080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_14_5"></a> + <h2>Case 6.14.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�����<br>fc80808080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_14_6"></a> + <h2>Case 6.14.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�<br>df</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_14_7"></a> + <h2>Case 6.14.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�<br>efbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_14_8"></a> + <h2>Case 6.14.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>f7bfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_14_9"></a> + <h2>Case 6.14.9</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>����<br>fbbfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_14_10"></a> + <h2>Case 6.14.10</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�����<br>fdbfbfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_15_1"></a> + <h2>Case 6.15.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�����������������������������<br>c0e080f08080f8808080fc80808080dfefbff7bfbffbbfbfbffdbfbfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_16_1"></a> + <h2>Case 6.16.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�<br>fe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_16_2"></a> + <h2>Case 6.16.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�<br>ff</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_16_3"></a> + <h2>Case 6.16.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>����<br>fefeffff</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_17_1"></a> + <h2>Case 6.17.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>��<br>c0af</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_17_2"></a> + <h2>Case 6.17.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>e080af</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_17_3"></a> + <h2>Case 6.17.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>����<br>f08080af</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_17_4"></a> + <h2>Case 6.17.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�����<br>f8808080af</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_17_5"></a> + <h2>Case 6.17.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>fc80808080af</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_18_1"></a> + <h2>Case 6.18.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>��<br>c1bf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_18_2"></a> + <h2>Case 6.18.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>e09fbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_18_3"></a> + <h2>Case 6.18.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>����<br>f08fbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_18_4"></a> + <h2>Case 6.18.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�����<br>f887bfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_18_5"></a> + <h2>Case 6.18.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>fc83bfbfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_19_1"></a> + <h2>Case 6.19.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>��<br>c080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_19_2"></a> + <h2>Case 6.19.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>e08080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_19_3"></a> + <h2>Case 6.19.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>����<br>f0808080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_19_4"></a> + <h2>Case 6.19.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�����<br>f880808080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_19_5"></a> + <h2>Case 6.19.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>fc8080808080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_20_1"></a> + <h2>Case 6.20.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>eda080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_20_2"></a> + <h2>Case 6.20.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>edadbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_20_3"></a> + <h2>Case 6.20.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>edae80</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_20_4"></a> + <h2>Case 6.20.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>edafbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_20_5"></a> + <h2>Case 6.20.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>edb080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_20_6"></a> + <h2>Case 6.20.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>edbe80</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_20_7"></a> + <h2>Case 6.20.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>edbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_21_1"></a> + <h2>Case 6.21.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>eda080edb080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_21_2"></a> + <h2>Case 6.21.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>eda080edbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_21_3"></a> + <h2>Case 6.21.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>edadbfedb080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_21_4"></a> + <h2>Case 6.21.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>edadbfedbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_21_5"></a> + <h2>Case 6.21.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>edae80edb080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_21_6"></a> + <h2>Case 6.21.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>edae80edbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_21_7"></a> + <h2>Case 6.21.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>edafbfedb080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_21_8"></a> + <h2>Case 6.21.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>edafbfedbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_22_1"></a> + <h2>Case 6.22.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>efbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_2"></a> + <h2>Case 6.22.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>efbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_3"></a> + <h2>Case 6.22.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f09fbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_4"></a> + <h2>Case 6.22.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f09fbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_5"></a> + <h2>Case 6.22.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f0afbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_6"></a> + <h2>Case 6.22.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f0afbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_7"></a> + <h2>Case 6.22.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f0bfbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_8"></a> + <h2>Case 6.22.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f0bfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_9"></a> + <h2>Case 6.22.9</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f18fbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_10"></a> + <h2>Case 6.22.10</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f18fbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_11"></a> + <h2>Case 6.22.11</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f19fbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_12"></a> + <h2>Case 6.22.12</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f19fbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_13"></a> + <h2>Case 6.22.13</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f1afbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_14"></a> + <h2>Case 6.22.14</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f1afbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_15"></a> + <h2>Case 6.22.15</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f1bfbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_16"></a> + <h2>Case 6.22.16</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f1bfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_17"></a> + <h2>Case 6.22.17</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f28fbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_18"></a> + <h2>Case 6.22.18</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f28fbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_19"></a> + <h2>Case 6.22.19</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f29fbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_20"></a> + <h2>Case 6.22.20</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f29fbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_21"></a> + <h2>Case 6.22.21</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f2afbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_22"></a> + <h2>Case 6.22.22</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f2afbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_23"></a> + <h2>Case 6.22.23</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f2bfbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_24"></a> + <h2>Case 6.22.24</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f2bfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_25"></a> + <h2>Case 6.22.25</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f38fbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_26"></a> + <h2>Case 6.22.26</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f38fbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_27"></a> + <h2>Case 6.22.27</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f39fbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_28"></a> + <h2>Case 6.22.28</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f39fbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_29"></a> + <h2>Case 6.22.29</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f3afbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_30"></a> + <h2>Case 6.22.30</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f3afbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_31"></a> + <h2>Case 6.22.31</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f3bfbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_32"></a> + <h2>Case 6.22.32</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f3bfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_33"></a> + <h2>Case 6.22.33</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f48fbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_34"></a> + <h2>Case 6.22.34</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f48fbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_23_1"></a> + <h2>Case 6.23.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�<br>efbfbd</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_7_1_1"></a> + <h2>Case 7.1.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a message followed by a close frame</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echoed message followed by clean close with normal code.</p> + <br/> + <a name="case_desc_7_1_2"></a> + <h2>Case 7.1.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send two close frames</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal code. Second close frame ignored.</p> + <br/> + <a name="case_desc_7_1_3"></a> + <h2>Case 7.1.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a ping after close message</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal code, no pong.</p> + <br/> + <a name="case_desc_7_1_4"></a> + <h2>Case 7.1.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message after sending a close frame.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal code. Text message ignored.</p> + <br/> + <a name="case_desc_7_1_5"></a> + <h2>Case 7.1.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send message fragment1 followed by close then fragment</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal code.</p> + <br/> + <a name="case_desc_7_1_6"></a> + <h2>Case 7.1.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 256K message followed by close then a ping</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Case outcome depends on implimentation defined close behavior. Message and close frame are sent back to back. If the close frame is processed before the text message write is complete (as can happen in asyncronous processing models) the close frame is processed first and the text message may not be recieved or may only be partially recieved.</p> + <br/> + <a name="case_desc_7_3_1"></a> + <h2>Case 7.3.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a close frame with payload length 0 (no close code, no close reason)</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal code.</p> + <br/> + <a name="case_desc_7_3_2"></a> + <h2>Case 7.3.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a close frame with payload length 1</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error or drop TCP.</p> + <br/> + <a name="case_desc_7_3_3"></a> + <h2>Case 7.3.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a close frame with payload length 2 (regular close with a code)</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal code.</p> + <br/> + <a name="case_desc_7_3_4"></a> + <h2>Case 7.3.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a close frame with close code and close reason</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal code.</p> + <br/> + <a name="case_desc_7_3_5"></a> + <h2>Case 7.3.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a close frame with close code and close reason of maximum length (123)</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal code.</p> + <br/> + <a name="case_desc_7_3_6"></a> + <h2>Case 7.3.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a close frame with close code and close reason which is too long (124) - total frame payload 126 octets</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or dropped TCP connection.</p> + <br/> + <a name="case_desc_7_5_1"></a> + <h2>Case 7.5.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a close frame with invalid UTF8 payload</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error or invalid utf8 code or dropped TCP.</p> + <br/> + <a name="case_desc_7_7_1"></a> + <h2>Case 7.7.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 1000</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_2"></a> + <h2>Case 7.7.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 1001</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_3"></a> + <h2>Case 7.7.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 1002</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_4"></a> + <h2>Case 7.7.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 1003</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_5"></a> + <h2>Case 7.7.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 1007</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_6"></a> + <h2>Case 7.7.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 1008</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_7"></a> + <h2>Case 7.7.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 1009</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_8"></a> + <h2>Case 7.7.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 1010</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_9"></a> + <h2>Case 7.7.9</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 1011</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_10"></a> + <h2>Case 7.7.10</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 3000</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_11"></a> + <h2>Case 7.7.11</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 3999</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_12"></a> + <h2>Case 7.7.12</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 4000</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_13"></a> + <h2>Case 7.7.13</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 4999</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_9_1"></a> + <h2>Case 7.9.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 0</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_2"></a> + <h2>Case 7.9.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 999</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_3"></a> + <h2>Case 7.9.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 1004</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_4"></a> + <h2>Case 7.9.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 1005</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_5"></a> + <h2>Case 7.9.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 1006</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_6"></a> + <h2>Case 7.9.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 1012</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_7"></a> + <h2>Case 7.9.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 1013</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_8"></a> + <h2>Case 7.9.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 1014</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_9"></a> + <h2>Case 7.9.9</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 1015</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_10"></a> + <h2>Case 7.9.10</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 1016</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_11"></a> + <h2>Case 7.9.11</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 1100</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_12"></a> + <h2>Case 7.9.12</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 2000</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_13"></a> + <h2>Case 7.9.13</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 2999</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_13_1"></a> + <h2>Case 7.13.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with close code 5000</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Actual events are undefined by the spec.</p> + <br/> + <a name="case_desc_7_13_2"></a> + <h2>Case 7.13.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with close code 65536</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Actual events are undefined by the spec.</p> + <br/> + <a name="case_desc_9_1_1"></a> + <h2>Case 9.1.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 64 * 2**10 (64k).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_1_2"></a> + <h2>Case 9.1.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 256 * 2**10 (256k).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_1_3"></a> + <h2>Case 9.1.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 1 * 2**20 (1M).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_1_4"></a> + <h2>Case 9.1.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 4 * 2**20 (4M).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_1_5"></a> + <h2>Case 9.1.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 8 * 2**20 (8M).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_1_6"></a> + <h2>Case 9.1.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 16 * 2**20 (16M).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_2_1"></a> + <h2>Case 9.2.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 64 * 2**10 (64k).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_2_2"></a> + <h2>Case 9.2.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 256 * 2**10 (256k).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_2_3"></a> + <h2>Case 9.2.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 1 * 2**20 (1M).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_2_4"></a> + <h2>Case 9.2.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 4 * 2**20 (4M).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_2_5"></a> + <h2>Case 9.2.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 8 * 2**20 (16M).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_2_6"></a> + <h2>Case 9.2.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 16 * 2**20 (16M).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_3_1"></a> + <h2>Case 9.3.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 64.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_3_2"></a> + <h2>Case 9.3.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 256.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_3_3"></a> + <h2>Case 9.3.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 1k.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_3_4"></a> + <h2>Case 9.3.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 4k.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_3_5"></a> + <h2>Case 9.3.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 16k.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_3_6"></a> + <h2>Case 9.3.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 64k.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_3_7"></a> + <h2>Case 9.3.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 256k.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_3_8"></a> + <h2>Case 9.3.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 1M.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_3_9"></a> + <h2>Case 9.3.9</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented text message message with message payload of length 4 * 2**20 (8M). Sent out in fragments of 4M.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_4_1"></a> + <h2>Case 9.4.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 64.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_4_2"></a> + <h2>Case 9.4.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 256.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_4_3"></a> + <h2>Case 9.4.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 1k.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_4_4"></a> + <h2>Case 9.4.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 4k.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_4_5"></a> + <h2>Case 9.4.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 16k.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_4_6"></a> + <h2>Case 9.4.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 64k.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_4_7"></a> + <h2>Case 9.4.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 256k.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_4_8"></a> + <h2>Case 9.4.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 1M.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_4_9"></a> + <h2>Case 9.4.9</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 4M.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_5_1"></a> + <h2>Case 9.5.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 64 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_5_2"></a> + <h2>Case 9.5.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 128 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_5_3"></a> + <h2>Case 9.5.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 256 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_5_4"></a> + <h2>Case 9.5.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 512 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_5_5"></a> + <h2>Case 9.5.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 1024 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_5_6"></a> + <h2>Case 9.5.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 2048 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_6_1"></a> + <h2>Case 9.6.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 64 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_6_2"></a> + <h2>Case 9.6.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 128 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_6_3"></a> + <h2>Case 9.6.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 256 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_6_4"></a> + <h2>Case 9.6.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 512 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_6_5"></a> + <h2>Case 9.6.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 1024 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_6_6"></a> + <h2>Case 9.6.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 2048 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_7_1"></a> + <h2>Case 9.7.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 text messages of payload size 0 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text messages (with payload as sent). Timeout case after 60 secs.</p> + <br/> + <a name="case_desc_9_7_2"></a> + <h2>Case 9.7.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 text messages of payload size 16 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text messages (with payload as sent). Timeout case after 60 secs.</p> + <br/> + <a name="case_desc_9_7_3"></a> + <h2>Case 9.7.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 text messages of payload size 64 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text messages (with payload as sent). Timeout case after 60 secs.</p> + <br/> + <a name="case_desc_9_7_4"></a> + <h2>Case 9.7.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 text messages of payload size 256 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text messages (with payload as sent). Timeout case after 120 secs.</p> + <br/> + <a name="case_desc_9_7_5"></a> + <h2>Case 9.7.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 text messages of payload size 1024 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text messages (with payload as sent). Timeout case after 240 secs.</p> + <br/> + <a name="case_desc_9_7_6"></a> + <h2>Case 9.7.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 text messages of payload size 4096 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text messages (with payload as sent). Timeout case after 480 secs.</p> + <br/> + <a name="case_desc_9_8_1"></a> + <h2>Case 9.8.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 binary messages of payload size 0 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary messages (with payload as sent). Timeout case after 60 secs.</p> + <br/> + <a name="case_desc_9_8_2"></a> + <h2>Case 9.8.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 binary messages of payload size 16 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary messages (with payload as sent). Timeout case after 60 secs.</p> + <br/> + <a name="case_desc_9_8_3"></a> + <h2>Case 9.8.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 binary messages of payload size 64 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary messages (with payload as sent). Timeout case after 60 secs.</p> + <br/> + <a name="case_desc_9_8_4"></a> + <h2>Case 9.8.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 binary messages of payload size 256 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary messages (with payload as sent). Timeout case after 120 secs.</p> + <br/> + <a name="case_desc_9_8_5"></a> + <h2>Case 9.8.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 binary messages of payload size 1024 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary messages (with payload as sent). Timeout case after 240 secs.</p> + <br/> + <a name="case_desc_9_8_6"></a> + <h2>Case 9.8.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 binary messages of payload size 4096 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary messages (with payload as sent). Timeout case after 480 secs.</p> + <br/> + <a name="case_desc_10_1_1"></a> + <h2>Case 10.1.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message with payload of length 65536 and <b>autoFragmentSize = 1300</b>.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent and transmitted frame counts as expected). Clean close with normal code.</p> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/java-source/autobahn reports/servers/index.html b/cb-tools/java-source/java-source/autobahn reports/servers/index.html new file mode 100644 index 00000000..a5d20b2b --- /dev/null +++ b/cb-tools/java-source/java-source/autobahn reports/servers/index.html @@ -0,0 +1,3614 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +table { + border-collapse: collapse; + border-spacing: 0px; +} + +td { + margin: 0; + border: 1px solid #fff; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + font-size: 0.9em; + color: #fff; +} + +table#agent_case_results { + border-collapse: collapse; + border-spacing: 0px; + border-radius: 10px; + margin-left: 20px; + margin-right: 20px; + margin-bottom: 40px; +} + +td.outcome_desc { + width: 100%; + color: #333; + font-size: 0.8em; +} + +tr.agent_case_result_row a { + color: #eee; +} + +td.agent { + color: #fff; + font-size: 1.0em; + text-align: center; + background-color: #048; + font-size: 0.8em; + word-wrap: break-word; + padding: 4px; + width: 140px; +} + +td.case { + background-color: #666; + text-align: left; + padding-left: 40px; + font-size: 0.9em; +} + +td.case_category { + color: #fff; + background-color: #000; + text-align: left; + padding-left: 20px; + font-size: 1.0em; +} + +td.case_subcategory { + color: #fff; + background-color: #333; + text-align: left; + padding-left: 30px; + font-size: 0.9em; +} + +td.close { + width: 15px; + padding: 6px; + font-size: 0.7em; + color: #fff; + min-width: 0px; +} + +td.case_ok { + background-color: #0a0; + text-align: center; +} + +td.case_almost { + background-color: #6d6; + text-align: center; +} + +td.case_non_strict, td.case_no_close { + background-color: #9a0; + text-align: center; +} + +td.case_info { + background-color: #4095BF; + text-align: center; +} + +td.case_failed { + background-color: #900; + text-align: center; +} + +td.case_missing { + color: #fff; + background-color: #a05a2c; + text-align: center; +} + +span.case_duration { + font-size: 0.7em; + color: #fff; +} + +*.unselectable { + user-select: none; + -moz-user-select: -moz-none; + -webkit-user-select: none; + -khtml-user-select: none; +} + +div#toggle_button { + position: fixed; + bottom: 10px; + right: 10px; + background-color: rgba(60, 60, 60, 0.5); + border-radius: 12px; + color: #fff; + font-size: 0.7em; + padding: 5px 10px; +} + +div#toggle_button:hover { + background-color: #028ec9; +} +</style> + <script language="javascript"> +var isClosed = false; + +function closeHelper(display,colspan) { + // hide all close codes + var a = document.getElementsByClassName("close_hide"); + for (var i in a) { + if (a[i].style) { + a[i].style.display = display; + } + } + + // set colspans + var a = document.getElementsByClassName("close_flex"); + for (var i in a) { + a[i].colSpan = colspan; + } + + var a = document.getElementsByClassName("case_subcategory"); + for (var i in a) { + a[i].colSpan = 1 * colspan + 1; + } +} + +function toggleClose() { + if (window.isClosed == false) { + closeHelper("none",1); + window.isClosed = true; + } else { + closeHelper("table-cell",2); + window.isClosed = false; + } +} +</script> + </head> + <body> + <a href="#"><div id="toggle_button" class="unselectable" onclick="toggleClose();">Toggle Details</div></a> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <div id="master_report_header" class="block"> + <p id="intro">Summary report generated on 2012-02-04T15:47:22Z (UTC) by <a href="http://www.tavendo.de/autobahn">Autobahn WebSockets</a> v0.4.10.</p> + + <table id="case_outcome_desc"> + <tr> + <td class="case_ok">Pass</td> + <td class="outcome_desc">Test case was executed and passed successfully.</td> + </tr> + <tr> + <td class="case_non_strict">Non-Strict</td> + <td class="outcome_desc">Test case was executed and passed non-strictly. + A non-strict behavior is one that does not adhere to a SHOULD-behavior as described in the protocol specification or + a well-defined, canonical behavior that appears to be desirable but left open in the protocol specification. + An implementation with non-strict behavior is still conformant to the protocol specification.</td> + </tr> + <tr> + <td class="case_failed">Fail</td> + <td class="outcome_desc">Test case was executed and failed. An implementation which fails a test case - other + than a performance/limits related one - is non-conforming to a MUST-behavior as described in the protocol specification.</td> + </tr> + <tr> + <td class="case_info">Info</td> + <td class="outcome_desc">Informational test case which detects certain implementation behavior left unspecified by the spec + but nevertheless potentially interesting to implementors.</td> + </tr> + <tr> + <td class="case_missing">Missing</td> + <td class="outcome_desc">Test case is missing, either because it was skipped via the test suite configuration + or deactivated, i.e. because the implementation does not implement the tested feature or breaks during running + the test case.</td> + </tr> + </table> + </div> + <table id="agent_case_results"> + <tr class="case_category_row"> + <td class="case_category">1 Framing</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">1.1 Text Messages</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_1_1">Case 1.1.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_1_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_1_2">Case 1.1.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_1_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_1_3">Case 1.1.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_1_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_1_4">Case 1.1.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_1_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_1_5">Case 1.1.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_1_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_1_6">Case 1.1.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_1_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_1_7">Case 1.1.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_1_7.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_1_8">Case 1.1.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_1_8.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">1 Framing</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">1.2 Binary Messages</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_2_1">Case 1.2.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_2_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_2_2">Case 1.2.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_2_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_2_3">Case 1.2.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_2_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_2_4">Case 1.2.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_2_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_2_5">Case 1.2.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_2_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_2_6">Case 1.2.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_2_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_2_7">Case 1.2.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_2_7.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_1_2_8">Case 1.2.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_1_2_8.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">2 Pings/Pongs</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_2_1">Case 2.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_2_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_2_2">Case 2.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_2_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_2_3">Case 2.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_2_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_2_4">Case 2.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_2_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_2_5">Case 2.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_2_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_2_6">Case 2.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_2_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_2_7">Case 2.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_2_7.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_2_8">Case 2.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_2_8.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_2_9">Case 2.9</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_2_9.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_2_10">Case 2.10</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_2_10.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_2_11">Case 2.11</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_2_11.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">3 Reserved Bits</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_3_1">Case 3.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_3_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_3_2">Case 3.2</a></td> + <td class="case_non_strict"><a href="tootallnate_websocket_case_3_2.html">Non-Strict</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_3_3">Case 3.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_3_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_3_4">Case 3.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_3_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_3_5">Case 3.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_3_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_3_6">Case 3.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_3_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_3_7">Case 3.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_3_7.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">4 Opcodes</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">4.1 Non-control Opcodes</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_4_1_1">Case 4.1.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_4_1_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_4_1_2">Case 4.1.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_4_1_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_4_1_3">Case 4.1.3</a></td> + <td class="case_non_strict"><a href="tootallnate_websocket_case_4_1_3.html">Non-Strict</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_4_1_4">Case 4.1.4</a></td> + <td class="case_non_strict"><a href="tootallnate_websocket_case_4_1_4.html">Non-Strict</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_4_1_5">Case 4.1.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_4_1_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">4 Opcodes</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">4.2 Control Opcodes</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_4_2_1">Case 4.2.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_4_2_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_4_2_2">Case 4.2.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_4_2_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_4_2_3">Case 4.2.3</a></td> + <td class="case_non_strict"><a href="tootallnate_websocket_case_4_2_3.html">Non-Strict</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_4_2_4">Case 4.2.4</a></td> + <td class="case_non_strict"><a href="tootallnate_websocket_case_4_2_4.html">Non-Strict</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_4_2_5">Case 4.2.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_4_2_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">5 Fragmentation</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_1">Case 5.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_2">Case 5.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_3">Case 5.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_4">Case 5.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_5">Case 5.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_6">Case 5.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_7">Case 5.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_7.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_8">Case 5.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_8.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_9">Case 5.9</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_9.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_10">Case 5.10</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_10.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_11">Case 5.11</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_11.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_12">Case 5.12</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_12.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_13">Case 5.13</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_13.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_14">Case 5.14</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_14.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_15">Case 5.15</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_15.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_16">Case 5.16</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_16.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_17">Case 5.17</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_17.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_18">Case 5.18</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_18.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_19">Case 5.19</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_19.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_5_20">Case 5.20</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_5_20.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.1 Valid UTF-8 with zero payload fragments</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_1_1">Case 6.1.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_1_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_1_2">Case 6.1.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_1_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_1_3">Case 6.1.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_1_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.2 Valid UTF-8 unfragmented, fragmented on code-points and within code-points</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_2_1">Case 6.2.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_2_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_2_2">Case 6.2.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_2_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_2_3">Case 6.2.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_2_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_2_4">Case 6.2.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_2_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.3 Invalid UTF-8 differently fragmented</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_3_1">Case 6.3.1</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_3_1.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_3_2">Case 6.3.2</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_3_2.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.4 Fail-fast on invalid UTF-8</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_4_1">Case 6.4.1</a></td> + <td class="case_non_strict"><a href="tootallnate_websocket_case_6_4_1.html">Non-Strict</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_4_2">Case 6.4.2</a></td> + <td class="case_non_strict"><a href="tootallnate_websocket_case_6_4_2.html">Non-Strict</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_4_3">Case 6.4.3</a></td> + <td class="case_non_strict"><a href="tootallnate_websocket_case_6_4_3.html">Non-Strict</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_4_4">Case 6.4.4</a></td> + <td class="case_non_strict"><a href="tootallnate_websocket_case_6_4_4.html">Non-Strict</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.5 Some valid UTF-8 sequences</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_5_1">Case 6.5.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_5_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.6 All prefixes of a valid UTF-8 string that contains multi-byte code points</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_6_1">Case 6.6.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_6_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_6_2">Case 6.6.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_6_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_6_3">Case 6.6.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_6_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_6_4">Case 6.6.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_6_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_6_5">Case 6.6.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_6_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_6_6">Case 6.6.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_6_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_6_7">Case 6.6.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_6_7.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_6_8">Case 6.6.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_6_8.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_6_9">Case 6.6.9</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_6_9.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_6_10">Case 6.6.10</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_6_10.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_6_11">Case 6.6.11</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_6_11.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.7 First possible sequence of a certain length</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_7_1">Case 6.7.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_7_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_7_2">Case 6.7.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_7_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_7_3">Case 6.7.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_7_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_7_4">Case 6.7.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_7_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.8 First possible sequence length 5/6 (invalid codepoints)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_8_1">Case 6.8.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_8_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_8_2">Case 6.8.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_8_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.9 Last possible sequence of a certain length</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_9_1">Case 6.9.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_9_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_9_2">Case 6.9.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_9_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_9_3">Case 6.9.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_9_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_9_4">Case 6.9.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_9_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.10 Last possible sequence length 4/5/6 (invalid codepoints)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_10_1">Case 6.10.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_10_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_10_2">Case 6.10.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_10_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_10_3">Case 6.10.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_10_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.11 Other boundary conditions</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_11_1">Case 6.11.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_11_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_11_2">Case 6.11.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_11_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_11_3">Case 6.11.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_11_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_11_4">Case 6.11.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_11_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_11_5">Case 6.11.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_11_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.12 Unexpected continuation bytes</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_12_1">Case 6.12.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_12_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_12_2">Case 6.12.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_12_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_12_3">Case 6.12.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_12_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_12_4">Case 6.12.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_12_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_12_5">Case 6.12.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_12_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_12_6">Case 6.12.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_12_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_12_7">Case 6.12.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_12_7.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_12_8">Case 6.12.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_12_8.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.13 Lonely start characters</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_13_1">Case 6.13.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_13_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_13_2">Case 6.13.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_13_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_13_3">Case 6.13.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_13_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_13_4">Case 6.13.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_13_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_13_5">Case 6.13.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_13_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.14 Sequences with last continuation byte missing</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_14_1">Case 6.14.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_14_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_14_2">Case 6.14.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_14_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_14_3">Case 6.14.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_14_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_14_4">Case 6.14.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_14_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_14_5">Case 6.14.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_14_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_14_6">Case 6.14.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_14_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_14_7">Case 6.14.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_14_7.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_14_8">Case 6.14.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_14_8.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_14_9">Case 6.14.9</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_14_9.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_14_10">Case 6.14.10</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_14_10.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.15 Concatenation of incomplete sequences</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_15_1">Case 6.15.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_15_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.16 Impossible bytes</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_16_1">Case 6.16.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_16_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_16_2">Case 6.16.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_16_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_16_3">Case 6.16.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_16_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.17 Examples of an overlong ASCII character</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_17_1">Case 6.17.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_17_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_17_2">Case 6.17.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_17_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_17_3">Case 6.17.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_17_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_17_4">Case 6.17.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_17_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_17_5">Case 6.17.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_17_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.18 Maximum overlong sequences</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_18_1">Case 6.18.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_18_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_18_2">Case 6.18.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_18_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_18_3">Case 6.18.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_18_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_18_4">Case 6.18.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_18_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_18_5">Case 6.18.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_18_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.19 Overlong representation of the NUL character</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_19_1">Case 6.19.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_19_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_19_2">Case 6.19.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_19_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_19_3">Case 6.19.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_19_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_19_4">Case 6.19.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_19_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_19_5">Case 6.19.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_19_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.20 Single UTF-16 surrogates</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_20_1">Case 6.20.1</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_20_1.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_20_2">Case 6.20.2</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_20_2.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_20_3">Case 6.20.3</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_20_3.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_20_4">Case 6.20.4</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_20_4.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_20_5">Case 6.20.5</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_20_5.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_20_6">Case 6.20.6</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_20_6.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_20_7">Case 6.20.7</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_20_7.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.21 Paired UTF-16 surrogates</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_21_1">Case 6.21.1</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_21_1.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_21_2">Case 6.21.2</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_21_2.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_21_3">Case 6.21.3</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_21_3.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_21_4">Case 6.21.4</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_21_4.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_21_5">Case 6.21.5</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_21_5.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_21_6">Case 6.21.6</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_21_6.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_21_7">Case 6.21.7</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_21_7.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_21_8">Case 6.21.8</a></td> + <td class="case_failed"><a href="tootallnate_websocket_case_6_21_8.html">Fail</a></td><td class="close close_hide case_failed"><span class="close_code">Fail</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.22 Non-character code points (valid UTF-8)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_1">Case 6.22.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_2">Case 6.22.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_3">Case 6.22.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_4">Case 6.22.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_5">Case 6.22.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_6">Case 6.22.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_7">Case 6.22.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_7.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_8">Case 6.22.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_8.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_9">Case 6.22.9</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_9.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_10">Case 6.22.10</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_10.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_11">Case 6.22.11</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_11.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_12">Case 6.22.12</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_12.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_13">Case 6.22.13</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_13.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_14">Case 6.22.14</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_14.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_15">Case 6.22.15</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_15.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_16">Case 6.22.16</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_16.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_17">Case 6.22.17</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_17.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_18">Case 6.22.18</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_18.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_19">Case 6.22.19</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_19.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_20">Case 6.22.20</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_20.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_21">Case 6.22.21</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_21.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_22">Case 6.22.22</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_22.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_23">Case 6.22.23</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_23.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_24">Case 6.22.24</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_24.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_25">Case 6.22.25</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_25.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_26">Case 6.22.26</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_26.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_27">Case 6.22.27</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_27.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_28">Case 6.22.28</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_28.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_29">Case 6.22.29</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_29.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_30">Case 6.22.30</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_30.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_31">Case 6.22.31</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_31.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_32">Case 6.22.32</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_32.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_33">Case 6.22.33</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_33.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_22_34">Case 6.22.34</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_22_34.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">6 UTF-8 Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">6.23 Unicode replacement character</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_6_23_1">Case 6.23.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_6_23_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">7 Close Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">7.1 Basic close behavior (fuzzer initiated)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_1_1">Case 7.1.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_1_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_1_2">Case 7.1.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_1_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_1_3">Case 7.1.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_1_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_1_4">Case 7.1.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_1_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_1_5">Case 7.1.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_1_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_1_6">Case 7.1.6</a></td> + <td class="case_info"><a href="tootallnate_websocket_case_7_1_6.html">Info</a></td><td class="close close_hide case_info"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">7 Close Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">7.3 Close frame structure: payload length (fuzzer initiated)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_3_1">Case 7.3.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_3_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_3_2">Case 7.3.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_3_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_3_3">Case 7.3.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_3_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_3_4">Case 7.3.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_3_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_3_5">Case 7.3.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_3_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_3_6">Case 7.3.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_3_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">7 Close Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">7.5 Close frame structure: payload value (fuzzer initiated)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_5_1">Case 7.5.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_5_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">7 Close Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">7.7 Close frame structure: valid close codes (fuzzer initiated)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_1">Case 7.7.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_2">Case 7.7.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_3">Case 7.7.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_4">Case 7.7.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_5">Case 7.7.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_6">Case 7.7.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_7">Case 7.7.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_7.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_8">Case 7.7.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_8.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_9">Case 7.7.9</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_9.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_10">Case 7.7.10</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_10.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_11">Case 7.7.11</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_11.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_12">Case 7.7.12</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_12.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_7_13">Case 7.7.13</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_7_13.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">7 Close Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">7.9 Close frame structure: invalid close codes (fuzzer initiated)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_1">Case 7.9.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_2">Case 7.9.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_2.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_3">Case 7.9.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_3.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_4">Case 7.9.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_4.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_5">Case 7.9.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_5.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_6">Case 7.9.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_6.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_7">Case 7.9.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_7.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_8">Case 7.9.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_8.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_9">Case 7.9.9</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_9.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_10">Case 7.9.10</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_10.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_11">Case 7.9.11</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_11.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_12">Case 7.9.12</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_12.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_9_13">Case 7.9.13</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_7_9_13.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">7 Close Handling</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">7.13 Informational close information (fuzzer initiated)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_13_1">Case 7.13.1</a></td> + <td class="case_info"><a href="tootallnate_websocket_case_7_13_1.html">Info</a></td><td class="close close_hide case_info"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_7_13_2">Case 7.13.2</a></td> + <td class="case_info"><a href="tootallnate_websocket_case_7_13_2.html">Info</a></td><td class="close close_hide case_info"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">9 Limits/Performance</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">9.1 Text Message (increasing size)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_1_1">Case 9.1.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_1_1.html">Pass</a><br/><span class="case_duration">67 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_1_2">Case 9.1.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_1_2.html">Pass</a><br/><span class="case_duration">260 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_1_3">Case 9.1.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_1_3.html">Pass</a><br/><span class="case_duration">1042 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_1_4">Case 9.1.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_1_4.html">Pass</a><br/><span class="case_duration">4180 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_1_5">Case 9.1.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_1_5.html">Pass</a><br/><span class="case_duration">8364 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_1_6">Case 9.1.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_1_6.html">Pass</a><br/><span class="case_duration">16993 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">9 Limits/Performance</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">9.2 Binary Message (increasing size)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_2_1">Case 9.2.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_2_1.html">Pass</a><br/><span class="case_duration">39 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_2_2">Case 9.2.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_2_2.html">Pass</a><br/><span class="case_duration">85 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_2_3">Case 9.2.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_2_3.html">Pass</a><br/><span class="case_duration">338 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_2_4">Case 9.2.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_2_4.html">Pass</a><br/><span class="case_duration">1375 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_2_5">Case 9.2.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_2_5.html">Pass</a><br/><span class="case_duration">2740 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_2_6">Case 9.2.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_2_6.html">Pass</a><br/><span class="case_duration">5554 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">9 Limits/Performance</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">9.3 Fragmented Text Message (fixed size, increasing fragment size)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_3_1">Case 9.3.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_3_1.html">Pass</a><br/><span class="case_duration">70578 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_3_2">Case 9.3.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_3_2.html">Pass</a><br/><span class="case_duration">20991 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_3_3">Case 9.3.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_3_3.html">Pass</a><br/><span class="case_duration">8471 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_3_4">Case 9.3.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_3_4.html">Pass</a><br/><span class="case_duration">5286 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_3_5">Case 9.3.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_3_5.html">Pass</a><br/><span class="case_duration">4403 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_3_6">Case 9.3.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_3_6.html">Pass</a><br/><span class="case_duration">4160 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_3_7">Case 9.3.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_3_7.html">Pass</a><br/><span class="case_duration">4123 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_3_8">Case 9.3.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_3_8.html">Pass</a><br/><span class="case_duration">4096 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_3_9">Case 9.3.9</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_3_9.html">Pass</a><br/><span class="case_duration">4074 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">9 Limits/Performance</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">9.4 Fragmented Binary Message (fixed size, increasing fragment size)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_4_1">Case 9.4.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_4_1.html">Pass</a><br/><span class="case_duration">69384 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_4_2">Case 9.4.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_4_2.html">Pass</a><br/><span class="case_duration">17962 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_4_3">Case 9.4.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_4_3.html">Pass</a><br/><span class="case_duration">5637 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_4_4">Case 9.4.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_4_4.html">Pass</a><br/><span class="case_duration">2497 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_4_5">Case 9.4.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_4_5.html">Pass</a><br/><span class="case_duration">1597 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_4_6">Case 9.4.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_4_6.html">Pass</a><br/><span class="case_duration">1359 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_4_7">Case 9.4.7</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_4_7.html">Pass</a><br/><span class="case_duration">1299 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_4_8">Case 9.4.8</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_4_8.html">Pass</a><br/><span class="case_duration">1296 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_4_9">Case 9.4.9</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_4_9.html">Pass</a><br/><span class="case_duration">1273 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">9 Limits/Performance</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">9.5 Text Message (fixed size, increasing chop size)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_5_1">Case 9.5.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_5_1.html">Pass</a><br/><span class="case_duration">17529 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_5_2">Case 9.5.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_5_2.html">Pass</a><br/><span class="case_duration">9293 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_5_3">Case 9.5.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_5_3.html">Pass</a><br/><span class="case_duration">5166 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_5_4">Case 9.5.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_5_4.html">Pass</a><br/><span class="case_duration">3168 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_5_5">Case 9.5.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_5_5.html">Pass</a><br/><span class="case_duration">2137 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_5_6">Case 9.5.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_5_6.html">Pass</a><br/><span class="case_duration">1571 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">9 Limits/Performance</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">9.6 Binary Text Message (fixed size, increasing chop size)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_6_1">Case 9.6.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_6_1.html">Pass</a><br/><span class="case_duration">16825 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_6_2">Case 9.6.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_6_2.html">Pass</a><br/><span class="case_duration">8639 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_6_3">Case 9.6.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_6_3.html">Pass</a><br/><span class="case_duration">4504 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_6_4">Case 9.6.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_6_4.html">Pass</a><br/><span class="case_duration">2434 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_6_5">Case 9.6.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_6_5.html">Pass</a><br/><span class="case_duration">1461 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_6_6">Case 9.6.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_6_6.html">Pass</a><br/><span class="case_duration">909 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">9 Limits/Performance</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">9.7 Text Message Roundtrip Time (fixed number, increasing size)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_7_1">Case 9.7.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_7_1.html">Pass</a><br/><span class="case_duration">267 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_7_2">Case 9.7.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_7_2.html">Pass</a><br/><span class="case_duration">289 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_7_3">Case 9.7.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_7_3.html">Pass</a><br/><span class="case_duration">321 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_7_4">Case 9.7.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_7_4.html">Pass</a><br/><span class="case_duration">551 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_7_5">Case 9.7.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_7_5.html">Pass</a><br/><span class="case_duration">1435 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_7_6">Case 9.7.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_7_6.html">Pass</a><br/><span class="case_duration">4887 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">9 Limits/Performance</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">9.8 Binary Message Roundtrip Time (fixed number, increasing size)</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_8_1">Case 9.8.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_8_1.html">Pass</a><br/><span class="case_duration">227 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_8_2">Case 9.8.2</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_8_2.html">Pass</a><br/><span class="case_duration">259 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_8_3">Case 9.8.3</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_8_3.html">Pass</a><br/><span class="case_duration">255 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_8_4">Case 9.8.4</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_8_4.html">Pass</a><br/><span class="case_duration">355 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_8_5">Case 9.8.5</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_8_5.html">Pass</a><br/><span class="case_duration">636 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_9_8_6">Case 9.8.6</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_9_8_6.html">Pass</a><br/><span class="case_duration">1940 ms</span></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + <tr class="case_category_row"> + <td class="case_category">10 Autobahn Protocol Options</td> + <td class="agent close_flex" colspan="2">tootallnate/websocket</td> + </tr> + <tr class="case_subcategory_row"> + <td class="case_subcategory" colspan="3">10.1 Auto-Fragmentation</td> + </tr> + <tr class="agent_case_result_row"> + <td class="case"><a href="#case_desc_10_1_1">Case 10.1.1</a></td> + <td class="case_ok"><a href="tootallnate_websocket_case_10_1_1.html">Pass</a></td><td class="close close_hide case_ok"><span class="close_code">None</span></td> + </tr> + </table> + <br/><hr/> + <div id="test_case_descriptions"> + <br/> + <a name="case_desc_1_1_1"></a> + <h2>Case 1.1.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message with payload 0.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with empty payload). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_1_2"></a> + <h2>Case 1.1.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 125.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_1_3"></a> + <h2>Case 1.1.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 126.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_1_4"></a> + <h2>Case 1.1.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 127.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_1_5"></a> + <h2>Case 1.1.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 128.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_1_6"></a> + <h2>Case 1.1.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 65535.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_1_7"></a> + <h2>Case 1.1.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 65536.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_1_8"></a> + <h2>Case 1.1.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 65536. Sent out data in chops of 997 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_2_1"></a> + <h2>Case 1.2.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message with payload 0.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with empty payload). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_2_2"></a> + <h2>Case 1.2.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 125.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_2_3"></a> + <h2>Case 1.2.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 126.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_2_4"></a> + <h2>Case 1.2.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 127.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_2_5"></a> + <h2>Case 1.2.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 128.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_2_6"></a> + <h2>Case 1.2.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 65535.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_2_7"></a> + <h2>Case 1.2.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 65536.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_1_2_8"></a> + <h2>Case 1.2.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 65536. Sent out data in chops of 997 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent). Clean close with normal code.</p> + <br/> + <a name="case_desc_2_1"></a> + <h2>Case 2.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send ping without payload.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Pong (with empty payload) is sent in reply to Ping. Clean close with normal code.</p> + <br/> + <a name="case_desc_2_2"></a> + <h2>Case 2.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send ping with small text payload.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Pong with payload echo'ed is sent in reply to Ping. Clean close with normal code.</p> + <br/> + <a name="case_desc_2_3"></a> + <h2>Case 2.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send ping with small binary (non UTF-8) payload.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Pong with payload echo'ed is sent in reply to Ping. Clean close with normal code.</p> + <br/> + <a name="case_desc_2_4"></a> + <h2>Case 2.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send ping with binary payload of 125 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Pong with payload echo'ed is sent in reply to Ping. Clean close with normal code.</p> + <br/> + <a name="case_desc_2_5"></a> + <h2>Case 2.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send ping with binary payload of 126 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Connection is failed immediately (1002/Protocol Error), since control frames are only allowed to have payload up to and including 125 octets..</p> + <br/> + <a name="case_desc_2_6"></a> + <h2>Case 2.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send ping with binary payload of 125 octets, send in octet-wise chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Pong with payload echo'ed is sent in reply to Ping. Implementations must be TCP clean. Clean close with normal code.</p> + <br/> + <a name="case_desc_2_7"></a> + <h2>Case 2.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send unsolicited pong without payload. Verify nothing is received. Clean close with normal code.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Nothing.</p> + <br/> + <a name="case_desc_2_8"></a> + <h2>Case 2.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send unsolicited pong with payload. Verify nothing is received. Clean close with normal code.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Nothing.</p> + <br/> + <a name="case_desc_2_9"></a> + <h2>Case 2.9</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send unsolicited pong with payload. Send ping with payload. Verify pong for ping is received.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Nothing in reply to own Pong, but Pong with payload echo'ed in reply to Ping. Clean close with normal code.</p> + <br/> + <a name="case_desc_2_10"></a> + <h2>Case 2.10</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 10 Pings with payload.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Pongs for our Pings with all the payloads. Note: This is not required by the Spec .. but we check for this behaviour anyway. Clean close with normal code.</p> + <br/> + <a name="case_desc_2_11"></a> + <h2>Case 2.11</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 10 Pings with payload. Send out octets in octet-wise chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Pongs for our Pings with all the payloads. Note: This is not required by the Spec .. but we check for this behaviour anyway. Clean close with normal code.</p> + <br/> + <a name="case_desc_3_1"></a> + <h2>Case 3.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message with <b>RSV = 1</b>.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately (1002/protocol error), since RSV must be 0, when no extension defining RSV meaning has been negoiated.</p> + <br/> + <a name="case_desc_3_2"></a> + <h2>Case 3.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message, then send again with <b>RSV = 2</b>, then send Ping.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echo for first message is received, but then connection is failed immediately, since RSV must be 0, when no extension defining RSV meaning has been negoiated. The Pong is not received.</p> + <br/> + <a name="case_desc_3_3"></a> + <h2>Case 3.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message, then send again with <b>RSV = 3</b>, then send Ping. Octets are sent in frame-wise chops. Octets are sent in octet-wise chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echo for first message is received, but then connection is failed immediately, since RSV must be 0, when no extension defining RSV meaning has been negoiated. The Pong is not received.</p> + <br/> + <a name="case_desc_3_4"></a> + <h2>Case 3.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message, then send again with <b>RSV = 4</b>, then send Ping. Octets are sent in octet-wise chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echo for first message is received, but then connection is failed immediately, since RSV must be 0, when no extension defining RSV meaning has been negoiated. The Pong is not received.</p> + <br/> + <a name="case_desc_3_5"></a> + <h2>Case 3.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small binary message with <b>RSV = 5</b>.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since RSV must be 0.</p> + <br/> + <a name="case_desc_3_6"></a> + <h2>Case 3.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send Ping with <b>RSV = 6</b>.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since RSV must be 0.</p> + <br/> + <a name="case_desc_3_7"></a> + <h2>Case 3.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send Close with <b>RSV = 7</b>.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since RSV must be 0.</p> + <br/> + <a name="case_desc_4_1_1"></a> + <h2>Case 4.1.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send frame with reserved non-control <b>Opcode = 3</b>.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately.</p> + <br/> + <a name="case_desc_4_1_2"></a> + <h2>Case 4.1.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send frame with reserved non-control <b>Opcode = 4</b> and non-empty payload.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately.</p> + <br/> + <a name="case_desc_4_1_3"></a> + <h2>Case 4.1.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message, then send frame with reserved non-control <b>Opcode = 5</b>, then send Ping.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.</p> + <br/> + <a name="case_desc_4_1_4"></a> + <h2>Case 4.1.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message, then send frame with reserved non-control <b>Opcode = 6</b> and non-empty payload, then send Ping.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.</p> + <br/> + <a name="case_desc_4_1_5"></a> + <h2>Case 4.1.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message, then send frame with reserved non-control <b>Opcode = 7</b> and non-empty payload, then send Ping.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.</p> + <br/> + <a name="case_desc_4_2_1"></a> + <h2>Case 4.2.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send frame with reserved control <b>Opcode = 11</b>.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately.</p> + <br/> + <a name="case_desc_4_2_2"></a> + <h2>Case 4.2.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send frame with reserved control <b>Opcode = 12</b> and non-empty payload.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately.</p> + <br/> + <a name="case_desc_4_2_3"></a> + <h2>Case 4.2.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message, then send frame with reserved control <b>Opcode = 13</b>, then send Ping.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.</p> + <br/> + <a name="case_desc_4_2_4"></a> + <h2>Case 4.2.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message, then send frame with reserved control <b>Opcode = 14</b> and non-empty payload, then send Ping.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.</p> + <br/> + <a name="case_desc_4_2_5"></a> + <h2>Case 4.2.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message, then send frame with reserved control <b>Opcode = 15</b> and non-empty payload, then send Ping.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.</p> + <br/> + <a name="case_desc_5_1"></a> + <h2>Case 5.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send Ping fragmented into 2 fragments.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Connection is failed immediately, since control message MUST NOT be fragmented.</p> + <br/> + <a name="case_desc_5_2"></a> + <h2>Case 5.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send Pong fragmented into 2 fragments.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Connection is failed immediately, since control message MUST NOT be fragmented.</p> + <br/> + <a name="case_desc_5_3"></a> + <h2>Case 5.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text Message fragmented into 2 fragments.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Message is processed and echo'ed back to us.</p> + <br/> + <a name="case_desc_5_4"></a> + <h2>Case 5.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text Message fragmented into 2 fragments, octets are sent in frame-wise chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Message is processed and echo'ed back to us.</p> + <br/> + <a name="case_desc_5_5"></a> + <h2>Case 5.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text Message fragmented into 2 fragments, octets are sent in octet-wise chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Message is processed and echo'ed back to us.</p> + <br/> + <a name="case_desc_5_6"></a> + <h2>Case 5.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text Message fragmented into 2 fragments, one ping with payload in-between.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>A pong is received, then the message is echo'ed back to us.</p> + <br/> + <a name="case_desc_5_7"></a> + <h2>Case 5.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text Message fragmented into 2 fragments, one ping with payload in-between. Octets are sent in frame-wise chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>A pong is received, then the message is echo'ed back to us.</p> + <br/> + <a name="case_desc_5_8"></a> + <h2>Case 5.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text Message fragmented into 2 fragments, one ping with payload in-between. Octets are sent in octet-wise chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>A pong is received, then the message is echo'ed back to us.</p> + <br/> + <a name="case_desc_5_9"></a> + <h2>Case 5.9</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send unfragmented Text Message after Continuation Frame with FIN = true, where there is nothing to continue, sent in one chop.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since there is no message to continue.</p> + <br/> + <a name="case_desc_5_10"></a> + <h2>Case 5.10</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send unfragmented Text Message after Continuation Frame with FIN = true, where there is nothing to continue, sent in per-frame chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since there is no message to continue.</p> + <br/> + <a name="case_desc_5_11"></a> + <h2>Case 5.11</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send unfragmented Text Message after Continuation Frame with FIN = true, where there is nothing to continue, sent in octet-wise chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since there is no message to continue.</p> + <br/> + <a name="case_desc_5_12"></a> + <h2>Case 5.12</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send unfragmented Text Message after Continuation Frame with FIN = false, where there is nothing to continue, sent in one chop.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since there is no message to continue.</p> + <br/> + <a name="case_desc_5_13"></a> + <h2>Case 5.13</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send unfragmented Text Message after Continuation Frame with FIN = false, where there is nothing to continue, sent in per-frame chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since there is no message to continue.</p> + <br/> + <a name="case_desc_5_14"></a> + <h2>Case 5.14</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send unfragmented Text Message after Continuation Frame with FIN = false, where there is nothing to continue, sent in octet-wise chops.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since there is no message to continue.</p> + <br/> + <a name="case_desc_5_15"></a> + <h2>Case 5.15</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text Message fragmented into 2 fragments, then Continuation Frame with FIN = false where there is nothing to continue, then unfragmented Text Message, all sent in one chop.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since there is no message to continue.</p> + <br/> + <a name="case_desc_5_16"></a> + <h2>Case 5.16</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Repeated 2x: Continuation Frame with FIN = false (where there is nothing to continue), then text Message fragmented into 2 fragments.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since there is no message to continue.</p> + <br/> + <a name="case_desc_5_17"></a> + <h2>Case 5.17</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Repeated 2x: Continuation Frame with FIN = true (where there is nothing to continue), then text Message fragmented into 2 fragments.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since there is no message to continue.</p> + <br/> + <a name="case_desc_5_18"></a> + <h2>Case 5.18</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text Message fragmented into 2 fragments, with both frame opcodes set to text, sent in one chop.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since all data frames after the initial data frame must have opcode 0.</p> + <br/> + <a name="case_desc_5_19"></a> + <h2>Case 5.19</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>A fragmented text message is sent in multiple frames. After + sending the first 2 frames of the text message, a Ping is sent. Then we wait 1s, + then we send 2 more text fragments, another Ping and then the final text fragment. + Everything is legal.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The peer immediately answers the first Ping before + it has received the last text message fragment. The peer pong's back the Ping's + payload exactly, and echo's the payload of the fragmented message back to us.</p> + <br/> + <a name="case_desc_5_20"></a> + <h2>Case 5.20</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Same as Case 5.19, but send all frames with SYNC = True. + Note, this does not change the octets sent in any way, only how the stream + is chopped up on the wire.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Same as Case 5.19. Implementations must be agnostic to how + octet stream is chopped up on wire (must be TCP clean).</p> + <br/> + <a name="case_desc_6_1_1"></a> + <h2>Case 6.1.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message of length 0.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>A message is echo'ed back to us (with empty payload).</p> + <br/> + <a name="case_desc_6_1_2"></a> + <h2>Case 6.1.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented text message, 3 fragments each of length 0.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>A message is echo'ed back to us (with empty payload).</p> + <br/> + <a name="case_desc_6_1_3"></a> + <h2>Case 6.1.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented text message, 3 fragments, first and last of length 0, middle non-empty.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>A message is echo'ed back to us (with payload = payload of middle fragment).</p> + <br/> + <a name="case_desc_6_2_1"></a> + <h2>Case 6.2.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a valid UTF-8 text message in one fragment.<br><br>MESSAGE:<br>Hello-µ@ßöäüàá-UTF-8!!<br>48656c6c6f2dc2b540c39fc3b6c3a4c3bcc3a0c3a12d5554462d382121</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_2_2"></a> + <h2>Case 6.2.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a valid UTF-8 text message in two fragments, fragmented on UTF-8 code point boundary.<br><br>MESSAGE FRAGMENT 1:<br>Hello-µ@ßöä<br>48656c6c6f2dc2b540c39fc3b6c3a4<br><br>MESSAGE FRAGMENT 2:<br>üàá-UTF-8!!<br>c3bcc3a0c3a12d5554462d382121</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_2_3"></a> + <h2>Case 6.2.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a valid UTF-8 text message in fragments of 1 octet, resulting in frames ending on positions which are not code point ends.<br><br>MESSAGE:<br>Hello-µ@ßöäüàá-UTF-8!!<br>48656c6c6f2dc2b540c39fc3b6c3a4c3bcc3a0c3a12d5554462d382121</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_2_4"></a> + <h2>Case 6.2.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a valid UTF-8 text message in fragments of 1 octet, resulting in frames ending on positions which are not code point ends.<br><br>MESSAGE:<br>κόσμε<br>cebae1bdb9cf83cebcceb5</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_3_1"></a> + <h2>Case 6.3.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send invalid UTF-8 text message unfragmented.<br><br>MESSAGE:<br>κόσμε���edited<br>cebae1bdb9cf83cebcceb5eda080656469746564</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_3_2"></a> + <h2>Case 6.3.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send invalid UTF-8 text message in fragments of 1 octet, resulting in frames ending on positions which are not code point ends.<br><br>MESSAGE:<br>κόσμε���edited<br>cebae1bdb9cf83cebcceb5eda080656469746564</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_4_1"></a> + <h2>Case 6.4.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send invalid UTF-8 text message in 3 fragments (frames). +First frame payload is valid, then wait, then 2nd frame which contains the payload making the sequence invalid, then wait, then 3rd frame with rest. +Note that PART1 and PART3 are valid UTF-8 in themselves, PART2 is a 0x11000 encoded as in the UTF-8 integer encoding scheme, but the codepoint is invalid (out of range). +<br><br>MESSAGE PARTS:<br> +PART1 = κόσμε (cebae1bdb9cf83cebcceb5)<br> +PART2 = ���� (f4908080)<br> +PART3 = edited (656469746564)<br> +</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The first frame is accepted, we expect to timeout on the first wait. The 2nd frame should be rejected immediately (fail fast on UTF-8). If we timeout, we expect the connection is failed at least then, since the complete message payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_4_2"></a> + <h2>Case 6.4.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Same as Case 6.4.1, but in 2nd frame, we send only up to and including the octet making the complete payload invalid. +<br><br>MESSAGE PARTS:<br> +PART1 = κόσμε� (cebae1bdb9cf83cebcceb5f4)<br> +PART2 = � (90)<br> +PART3 = ��edited (8080656469746564)<br> +</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The first frame is accepted, we expect to timeout on the first wait. The 2nd frame should be rejected immediately (fail fast on UTF-8). If we timeout, we expect the connection is failed at least then, since the complete message payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_4_3"></a> + <h2>Case 6.4.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Same as Case 6.4.1, but we send message not in 3 frames, but in 3 chops of the same message frame. +<br><br>MESSAGE PARTS:<br> +PART1 = κόσμε (cebae1bdb9cf83cebcceb5)<br> +PART2 = ���� (f4908080)<br> +PART3 = edited (656469746564)<br> +</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The first chop is accepted, we expect to timeout on the first wait. The 2nd chop should be rejected immediately (fail fast on UTF-8). If we timeout, we expect the connection is failed at least then, since the complete message payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_4_4"></a> + <h2>Case 6.4.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Same as Case 6.4.2, but we send message not in 3 frames, but in 3 chops of the same message frame. +<br><br>MESSAGE PARTS:<br> +PART1 = κόσμε� (cebae1bdb9cf83cebcceb5f4)<br> +PART2 = � (90)<br> +PART3 = ()<br> +</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The first chop is accepted, we expect to timeout on the first wait. The 2nd chop should be rejected immediately (fail fast on UTF-8). If we timeout, we expect the connection is failed at least then, since the complete message payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_5_1"></a> + <h2>Case 6.5.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>κόσμε<br>cebae1bdb9cf83cebcceb5</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_6_1"></a> + <h2>Case 6.6.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�<br>ce</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_6_2"></a> + <h2>Case 6.6.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>κ<br>ceba</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_6_3"></a> + <h2>Case 6.6.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>κ�<br>cebae1</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_6_4"></a> + <h2>Case 6.6.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>κ�<br>cebae1bd</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_6_5"></a> + <h2>Case 6.6.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>κό<br>cebae1bdb9</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_6_6"></a> + <h2>Case 6.6.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>κό�<br>cebae1bdb9cf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_6_7"></a> + <h2>Case 6.6.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>κόσ<br>cebae1bdb9cf83</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_6_8"></a> + <h2>Case 6.6.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>κόσ�<br>cebae1bdb9cf83ce</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_6_9"></a> + <h2>Case 6.6.9</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>κόσμ<br>cebae1bdb9cf83cebc</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_6_10"></a> + <h2>Case 6.6.10</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>κόσμ�<br>cebae1bdb9cf83cebcce</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_6_11"></a> + <h2>Case 6.6.11</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>κόσμε<br>cebae1bdb9cf83cebcceb5</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_7_1"></a> + <h2>Case 6.7.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�<br>00</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_7_2"></a> + <h2>Case 6.7.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>c280</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_7_3"></a> + <h2>Case 6.7.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>ࠀ<br>e0a080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_7_4"></a> + <h2>Case 6.7.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>𐀀<br>f0908080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_8_1"></a> + <h2>Case 6.8.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�����<br>f888808080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_8_2"></a> + <h2>Case 6.8.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>fc8480808080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_9_1"></a> + <h2>Case 6.9.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>7f</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_9_2"></a> + <h2>Case 6.9.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>߿<br>dfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_9_3"></a> + <h2>Case 6.9.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>efbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_9_4"></a> + <h2>Case 6.9.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f48fbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_10_1"></a> + <h2>Case 6.10.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>����<br>f7bfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_10_2"></a> + <h2>Case 6.10.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�����<br>fbbfbfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_10_3"></a> + <h2>Case 6.10.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>fdbfbfbfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_11_1"></a> + <h2>Case 6.11.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>ed9fbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_11_2"></a> + <h2>Case 6.11.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>ee8080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_11_3"></a> + <h2>Case 6.11.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�<br>efbfbd</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_11_4"></a> + <h2>Case 6.11.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f48fbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_11_5"></a> + <h2>Case 6.11.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>����<br>f4908080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_12_1"></a> + <h2>Case 6.12.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�<br>80</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_12_2"></a> + <h2>Case 6.12.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�<br>bf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_12_3"></a> + <h2>Case 6.12.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>��<br>80bf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_12_4"></a> + <h2>Case 6.12.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>80bf80</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_12_5"></a> + <h2>Case 6.12.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>����<br>80bf80bf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_12_6"></a> + <h2>Case 6.12.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�����<br>80bf80bf80</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_12_7"></a> + <h2>Case 6.12.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>80bf80bf80bf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_12_8"></a> + <h2>Case 6.12.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���������������������������������������������������������������<br>808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_13_1"></a> + <h2>Case 6.13.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>� � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � <br>c020c120c220c320c420c520c620c720c820c920ca20cb20cc20cd20ce20cf20d020d120d220d320d420d520d620d720d820d920da20db20dc20dd20de20</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_13_2"></a> + <h2>Case 6.13.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>� � � � � � � � � � � � � � � <br>e020e120e220e320e420e520e620e720e820e920ea20eb20ec20ed20ee20</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_13_3"></a> + <h2>Case 6.13.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>� � � � � � � <br>f020f120f220f320f420f520f620</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_13_4"></a> + <h2>Case 6.13.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>� � � <br>f820f920fa20</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_13_5"></a> + <h2>Case 6.13.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>� <br>fc20</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_14_1"></a> + <h2>Case 6.14.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�<br>c0</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_14_2"></a> + <h2>Case 6.14.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>��<br>e080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_14_3"></a> + <h2>Case 6.14.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>f08080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_14_4"></a> + <h2>Case 6.14.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>����<br>f8808080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_14_5"></a> + <h2>Case 6.14.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�����<br>fc80808080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_14_6"></a> + <h2>Case 6.14.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�<br>df</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_14_7"></a> + <h2>Case 6.14.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�<br>efbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_14_8"></a> + <h2>Case 6.14.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>f7bfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_14_9"></a> + <h2>Case 6.14.9</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>����<br>fbbfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_14_10"></a> + <h2>Case 6.14.10</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�����<br>fdbfbfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_15_1"></a> + <h2>Case 6.15.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�����������������������������<br>c0e080f08080f8808080fc80808080dfefbff7bfbffbbfbfbffdbfbfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_16_1"></a> + <h2>Case 6.16.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�<br>fe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_16_2"></a> + <h2>Case 6.16.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�<br>ff</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_16_3"></a> + <h2>Case 6.16.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>����<br>fefeffff</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_17_1"></a> + <h2>Case 6.17.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>��<br>c0af</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_17_2"></a> + <h2>Case 6.17.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>e080af</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_17_3"></a> + <h2>Case 6.17.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>����<br>f08080af</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_17_4"></a> + <h2>Case 6.17.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�����<br>f8808080af</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_17_5"></a> + <h2>Case 6.17.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>fc80808080af</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_18_1"></a> + <h2>Case 6.18.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>��<br>c1bf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_18_2"></a> + <h2>Case 6.18.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>e09fbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_18_3"></a> + <h2>Case 6.18.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>����<br>f08fbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_18_4"></a> + <h2>Case 6.18.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�����<br>f887bfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_18_5"></a> + <h2>Case 6.18.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>fc83bfbfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_19_1"></a> + <h2>Case 6.19.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>��<br>c080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_19_2"></a> + <h2>Case 6.19.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>e08080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_19_3"></a> + <h2>Case 6.19.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>����<br>f0808080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_19_4"></a> + <h2>Case 6.19.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�����<br>f880808080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_19_5"></a> + <h2>Case 6.19.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>fc8080808080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_20_1"></a> + <h2>Case 6.20.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>eda080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_20_2"></a> + <h2>Case 6.20.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>edadbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_20_3"></a> + <h2>Case 6.20.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>edae80</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_20_4"></a> + <h2>Case 6.20.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>edafbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_20_5"></a> + <h2>Case 6.20.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>edb080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_20_6"></a> + <h2>Case 6.20.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>edbe80</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_20_7"></a> + <h2>Case 6.20.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>edbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_21_1"></a> + <h2>Case 6.21.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>eda080edb080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_21_2"></a> + <h2>Case 6.21.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>eda080edbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_21_3"></a> + <h2>Case 6.21.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>edadbfedb080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_21_4"></a> + <h2>Case 6.21.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>edadbfedbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_21_5"></a> + <h2>Case 6.21.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>edae80edb080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_21_6"></a> + <h2>Case 6.21.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>edae80edbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_21_7"></a> + <h2>Case 6.21.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>edafbfedb080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_21_8"></a> + <h2>Case 6.21.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>edafbfedbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + <br/> + <a name="case_desc_6_22_1"></a> + <h2>Case 6.22.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>efbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_2"></a> + <h2>Case 6.22.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>efbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_3"></a> + <h2>Case 6.22.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f09fbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_4"></a> + <h2>Case 6.22.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f09fbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_5"></a> + <h2>Case 6.22.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f0afbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_6"></a> + <h2>Case 6.22.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f0afbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_7"></a> + <h2>Case 6.22.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f0bfbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_8"></a> + <h2>Case 6.22.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f0bfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_9"></a> + <h2>Case 6.22.9</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f18fbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_10"></a> + <h2>Case 6.22.10</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f18fbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_11"></a> + <h2>Case 6.22.11</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f19fbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_12"></a> + <h2>Case 6.22.12</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f19fbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_13"></a> + <h2>Case 6.22.13</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f1afbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_14"></a> + <h2>Case 6.22.14</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f1afbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_15"></a> + <h2>Case 6.22.15</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f1bfbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_16"></a> + <h2>Case 6.22.16</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f1bfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_17"></a> + <h2>Case 6.22.17</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f28fbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_18"></a> + <h2>Case 6.22.18</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f28fbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_19"></a> + <h2>Case 6.22.19</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f29fbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_20"></a> + <h2>Case 6.22.20</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f29fbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_21"></a> + <h2>Case 6.22.21</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f2afbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_22"></a> + <h2>Case 6.22.22</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f2afbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_23"></a> + <h2>Case 6.22.23</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f2bfbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_24"></a> + <h2>Case 6.22.24</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f2bfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_25"></a> + <h2>Case 6.22.25</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f38fbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_26"></a> + <h2>Case 6.22.26</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f38fbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_27"></a> + <h2>Case 6.22.27</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f39fbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_28"></a> + <h2>Case 6.22.28</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f39fbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_29"></a> + <h2>Case 6.22.29</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f3afbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_30"></a> + <h2>Case 6.22.30</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f3afbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_31"></a> + <h2>Case 6.22.31</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f3bfbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_32"></a> + <h2>Case 6.22.32</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f3bfbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_33"></a> + <h2>Case 6.22.33</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f48fbfbe</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_22_34"></a> + <h2>Case 6.22.34</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br><br>f48fbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_6_23_1"></a> + <h2>Case 6.23.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>�<br>efbfbd</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + <br/> + <a name="case_desc_7_1_1"></a> + <h2>Case 7.1.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a message followed by a close frame</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echoed message followed by clean close with normal code.</p> + <br/> + <a name="case_desc_7_1_2"></a> + <h2>Case 7.1.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send two close frames</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal code. Second close frame ignored.</p> + <br/> + <a name="case_desc_7_1_3"></a> + <h2>Case 7.1.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a ping after close message</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal code, no pong.</p> + <br/> + <a name="case_desc_7_1_4"></a> + <h2>Case 7.1.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message after sending a close frame.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal code. Text message ignored.</p> + <br/> + <a name="case_desc_7_1_5"></a> + <h2>Case 7.1.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send message fragment1 followed by close then fragment</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal code.</p> + <br/> + <a name="case_desc_7_1_6"></a> + <h2>Case 7.1.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 256K message followed by close then a ping</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Case outcome depends on implimentation defined close behavior. Message and close frame are sent back to back. If the close frame is processed before the text message write is complete (as can happen in asyncronous processing models) the close frame is processed first and the text message may not be recieved or may only be partially recieved.</p> + <br/> + <a name="case_desc_7_3_1"></a> + <h2>Case 7.3.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a close frame with payload length 0 (no close code, no close reason)</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal code.</p> + <br/> + <a name="case_desc_7_3_2"></a> + <h2>Case 7.3.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a close frame with payload length 1</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error or drop TCP.</p> + <br/> + <a name="case_desc_7_3_3"></a> + <h2>Case 7.3.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a close frame with payload length 2 (regular close with a code)</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal code.</p> + <br/> + <a name="case_desc_7_3_4"></a> + <h2>Case 7.3.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a close frame with close code and close reason</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal code.</p> + <br/> + <a name="case_desc_7_3_5"></a> + <h2>Case 7.3.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a close frame with close code and close reason of maximum length (123)</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal code.</p> + <br/> + <a name="case_desc_7_3_6"></a> + <h2>Case 7.3.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a close frame with close code and close reason which is too long (124) - total frame payload 126 octets</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or dropped TCP connection.</p> + <br/> + <a name="case_desc_7_5_1"></a> + <h2>Case 7.5.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a close frame with invalid UTF8 payload</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error or invalid utf8 code or dropped TCP.</p> + <br/> + <a name="case_desc_7_7_1"></a> + <h2>Case 7.7.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 1000</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_2"></a> + <h2>Case 7.7.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 1001</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_3"></a> + <h2>Case 7.7.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 1002</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_4"></a> + <h2>Case 7.7.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 1003</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_5"></a> + <h2>Case 7.7.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 1007</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_6"></a> + <h2>Case 7.7.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 1008</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_7"></a> + <h2>Case 7.7.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 1009</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_8"></a> + <h2>Case 7.7.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 1010</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_9"></a> + <h2>Case 7.7.9</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 1011</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_10"></a> + <h2>Case 7.7.10</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 3000</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_11"></a> + <h2>Case 7.7.11</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 3999</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_12"></a> + <h2>Case 7.7.12</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 4000</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_7_13"></a> + <h2>Case 7.7.13</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with valid close code 4999</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with normal or echoed code</p> + <br/> + <a name="case_desc_7_9_1"></a> + <h2>Case 7.9.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 0</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_2"></a> + <h2>Case 7.9.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 999</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_3"></a> + <h2>Case 7.9.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 1004</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_4"></a> + <h2>Case 7.9.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 1005</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_5"></a> + <h2>Case 7.9.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 1006</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_6"></a> + <h2>Case 7.9.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 1012</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_7"></a> + <h2>Case 7.9.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 1013</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_8"></a> + <h2>Case 7.9.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 1014</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_9"></a> + <h2>Case 7.9.9</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 1015</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_10"></a> + <h2>Case 7.9.10</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 1016</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_11"></a> + <h2>Case 7.9.11</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 1100</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_12"></a> + <h2>Case 7.9.12</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 2000</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_9_13"></a> + <h2>Case 7.9.13</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with invalid close code 2999</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Clean close with protocol error code or drop TCP</p> + <br/> + <a name="case_desc_7_13_1"></a> + <h2>Case 7.13.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with close code 5000</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Actual events are undefined by the spec.</p> + <br/> + <a name="case_desc_7_13_2"></a> + <h2>Case 7.13.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send close with close code 65536</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Actual events are undefined by the spec.</p> + <br/> + <a name="case_desc_9_1_1"></a> + <h2>Case 9.1.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 64 * 2**10 (64k).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_1_2"></a> + <h2>Case 9.1.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 256 * 2**10 (256k).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_1_3"></a> + <h2>Case 9.1.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 1 * 2**20 (1M).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_1_4"></a> + <h2>Case 9.1.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 4 * 2**20 (4M).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_1_5"></a> + <h2>Case 9.1.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 8 * 2**20 (8M).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_1_6"></a> + <h2>Case 9.1.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 16 * 2**20 (16M).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_2_1"></a> + <h2>Case 9.2.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 64 * 2**10 (64k).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_2_2"></a> + <h2>Case 9.2.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 256 * 2**10 (256k).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_2_3"></a> + <h2>Case 9.2.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 1 * 2**20 (1M).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_2_4"></a> + <h2>Case 9.2.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 4 * 2**20 (4M).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_2_5"></a> + <h2>Case 9.2.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 8 * 2**20 (16M).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_2_6"></a> + <h2>Case 9.2.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 16 * 2**20 (16M).</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_3_1"></a> + <h2>Case 9.3.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 64.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_3_2"></a> + <h2>Case 9.3.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 256.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_3_3"></a> + <h2>Case 9.3.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 1k.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_3_4"></a> + <h2>Case 9.3.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 4k.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_3_5"></a> + <h2>Case 9.3.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 16k.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_3_6"></a> + <h2>Case 9.3.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 64k.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_3_7"></a> + <h2>Case 9.3.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 256k.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_3_8"></a> + <h2>Case 9.3.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 1M.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_3_9"></a> + <h2>Case 9.3.9</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented text message message with message payload of length 4 * 2**20 (8M). Sent out in fragments of 4M.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_4_1"></a> + <h2>Case 9.4.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 64.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_4_2"></a> + <h2>Case 9.4.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 256.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_4_3"></a> + <h2>Case 9.4.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 1k.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_4_4"></a> + <h2>Case 9.4.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 4k.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_4_5"></a> + <h2>Case 9.4.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 16k.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_4_6"></a> + <h2>Case 9.4.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 64k.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_4_7"></a> + <h2>Case 9.4.7</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 256k.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_4_8"></a> + <h2>Case 9.4.8</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 1M.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_4_9"></a> + <h2>Case 9.4.9</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 4M.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_5_1"></a> + <h2>Case 9.5.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 64 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_5_2"></a> + <h2>Case 9.5.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 128 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_5_3"></a> + <h2>Case 9.5.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 256 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_5_4"></a> + <h2>Case 9.5.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 512 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_5_5"></a> + <h2>Case 9.5.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 1024 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_5_6"></a> + <h2>Case 9.5.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 2048 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_6_1"></a> + <h2>Case 9.6.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 64 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary message (with payload as sent).</p> + <br/> + <a name="case_desc_9_6_2"></a> + <h2>Case 9.6.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 128 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_6_3"></a> + <h2>Case 9.6.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 256 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_6_4"></a> + <h2>Case 9.6.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 512 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_6_5"></a> + <h2>Case 9.6.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 1024 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_6_6"></a> + <h2>Case 9.6.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send binary message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 2048 octets.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent).</p> + <br/> + <a name="case_desc_9_7_1"></a> + <h2>Case 9.7.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 text messages of payload size 0 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text messages (with payload as sent). Timeout case after 60 secs.</p> + <br/> + <a name="case_desc_9_7_2"></a> + <h2>Case 9.7.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 text messages of payload size 16 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text messages (with payload as sent). Timeout case after 60 secs.</p> + <br/> + <a name="case_desc_9_7_3"></a> + <h2>Case 9.7.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 text messages of payload size 64 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text messages (with payload as sent). Timeout case after 60 secs.</p> + <br/> + <a name="case_desc_9_7_4"></a> + <h2>Case 9.7.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 text messages of payload size 256 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text messages (with payload as sent). Timeout case after 120 secs.</p> + <br/> + <a name="case_desc_9_7_5"></a> + <h2>Case 9.7.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 text messages of payload size 1024 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text messages (with payload as sent). Timeout case after 240 secs.</p> + <br/> + <a name="case_desc_9_7_6"></a> + <h2>Case 9.7.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 text messages of payload size 4096 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text messages (with payload as sent). Timeout case after 480 secs.</p> + <br/> + <a name="case_desc_9_8_1"></a> + <h2>Case 9.8.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 binary messages of payload size 0 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary messages (with payload as sent). Timeout case after 60 secs.</p> + <br/> + <a name="case_desc_9_8_2"></a> + <h2>Case 9.8.2</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 binary messages of payload size 16 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary messages (with payload as sent). Timeout case after 60 secs.</p> + <br/> + <a name="case_desc_9_8_3"></a> + <h2>Case 9.8.3</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 binary messages of payload size 64 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary messages (with payload as sent). Timeout case after 60 secs.</p> + <br/> + <a name="case_desc_9_8_4"></a> + <h2>Case 9.8.4</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 binary messages of payload size 256 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary messages (with payload as sent). Timeout case after 120 secs.</p> + <br/> + <a name="case_desc_9_8_5"></a> + <h2>Case 9.8.5</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 binary messages of payload size 1024 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary messages (with payload as sent). Timeout case after 240 secs.</p> + <br/> + <a name="case_desc_9_8_6"></a> + <h2>Case 9.8.6</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send 1000 binary messages of payload size 4096 to measure implementation/network RTT (round trip time) / latency.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed binary messages (with payload as sent). Timeout case after 480 secs.</p> + <br/> + <a name="case_desc_10_1_1"></a> + <h2>Case 10.1.1</h2> + <a class="up" href="#top">Up</a> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send text message with payload of length 65536 and <b>autoFragmentSize = 1300</b>.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Receive echo'ed text message (with payload as sent and transmitted frame counts as expected). Clean close with normal code.</p> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_3_2.html b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_3_2.html new file mode 100644 index 00000000..81b697dd --- /dev/null +++ b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_3_2.html @@ -0,0 +1,305 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_non_strict">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 3.2</b></span> : Non-Strict - <span style="font-size: 0.9em;"><b>2</b> ms @ 2012-02-04T15:41:06Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message, then send again with <b>RSV = 2</b>, then send Ping.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echo for first message is received, but then connection is failed immediately, since RSV must be 0, when no extension defining RSV meaning has been negoiated. The Pong is not received.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events match at least one expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'NON-STRICT': [], 'OK': [('message', 'Hello, world!', False)]}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>Connection was properly closed (OK)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: naQpeQXM16oTsQGgFhzkCw== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: GWrmDJGkZekAGDtxltB9SFScv/0=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">False</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">False</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1000</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">None</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td><td>131</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>6</td><td>1</td><td>6</td></tr> + <tr class="stats_row"><td>8</td><td>1</td><td>8</td></tr> + <tr class="stats_row"><td>19</td><td>2</td><td>38</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>5</td><td>253</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>1</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_row"><td>9</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>4</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a206e615170</pre> + <pre class="wirelog_tx_octets"> 6551584d31366f547351476746687a6b43773d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a20475772</pre> + <pre class="wirelog_rx_octets"> 6d444a476b5a656b41474474786c74423953465363762f303d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=c8e7cd39, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> Hello, world!</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 818dc8e7cd398082a155a7cbed4ea795a15de9</pre> + <pre class="wirelog_tx_frame">004 TX FRAME : OPCODE=1, FIN=True, RSV=2, MASK=48cf9b10, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> Hello, world!</pre> + <pre class="wirelog_tx_octets">005 TX OCTETS: a18d48cf9b1000aaf77c27e3bb6727bdf77469</pre> + <pre class="wirelog_tx_frame">006 TX FRAME : OPCODE=9, FIN=True, RSV=0, MASK=ba7b6257, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_octets">007 TX OCTETS: 8980ba7b6257</pre> + <pre class="wirelog_kill_after">008 FAIL CONNECTION AFTER 1.000000 sec</pre> + <pre class="wirelog_rx_octets">009 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">010 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tx_frame">011 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=c2125d8a, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">012 TX OCTETS: 8882c2125d8ac1fa</pre> + <pre class="wirelog_tcp_closed_by_peer">013 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_4_1_3.html b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_4_1_3.html new file mode 100644 index 00000000..ed037118 --- /dev/null +++ b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_4_1_3.html @@ -0,0 +1,305 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_non_strict">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 4.1.3</b></span> : Non-Strict - <span style="font-size: 0.9em;"><b>2</b> ms @ 2012-02-04T15:41:06Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message, then send frame with reserved non-control <b>Opcode = 5</b>, then send Ping.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events match at least one expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'NON-STRICT': [], 'OK': [('message', 'Hello, world!', False)]}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>Connection was properly closed (OK)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: xsPC4lQvjb4PhpJeBNWHdQ== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: ZT0DfgjT9h2dF3tlZwQbiVc8r5s=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">False</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">False</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1000</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">None</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td><td>131</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>6</td><td>2</td><td>12</td></tr> + <tr class="stats_row"><td>8</td><td>1</td><td>8</td></tr> + <tr class="stats_row"><td>19</td><td>1</td><td>19</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>5</td><td>240</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>1</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>5</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_row"><td>9</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>4</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a2078735043</pre> + <pre class="wirelog_tx_octets"> 346c51766a62345068704a65424e574864513d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a205a5430</pre> + <pre class="wirelog_rx_octets"> 4466676a54396832644633746c5a775162695663387235733d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=c591fc05, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> Hello, world!</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 818dc591fc058df49069aabddc72aae39061e4</pre> + <pre class="wirelog_tx_frame">004 TX FRAME : OPCODE=5, FIN=True, RSV=0, MASK=245c9b85, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_octets">005 TX OCTETS: 8580245c9b85</pre> + <pre class="wirelog_tx_frame">006 TX FRAME : OPCODE=9, FIN=True, RSV=0, MASK=235abec4, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_octets">007 TX OCTETS: 8980235abec4</pre> + <pre class="wirelog_kill_after">008 FAIL CONNECTION AFTER 1.000000 sec</pre> + <pre class="wirelog_rx_octets">009 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">010 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tx_frame">011 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=3b961406, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">012 TX OCTETS: 88823b961406387e</pre> + <pre class="wirelog_tcp_closed_by_peer">013 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_4_1_4.html b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_4_1_4.html new file mode 100644 index 00000000..534afc19 --- /dev/null +++ b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_4_1_4.html @@ -0,0 +1,306 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_non_strict">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 4.1.4</b></span> : Non-Strict - <span style="font-size: 0.9em;"><b>2</b> ms @ 2012-02-04T15:41:06Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message, then send frame with reserved non-control <b>Opcode = 6</b> and non-empty payload, then send Ping.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events match at least one expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'NON-STRICT': [], 'OK': [('message', 'Hello, world!', False)]}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>Connection was properly closed (OK)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: lWjk0P7OM1q+JBUPL0HfZA== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: d7k1A19qGfNKwuf0DJJ9pJTFpII=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">False</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">False</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1000</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">None</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td><td>131</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>6</td><td>1</td><td>6</td></tr> + <tr class="stats_row"><td>8</td><td>1</td><td>8</td></tr> + <tr class="stats_row"><td>19</td><td>2</td><td>38</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>5</td><td>253</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>1</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>6</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_row"><td>9</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>4</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a206c576a6b</pre> + <pre class="wirelog_tx_octets"> 3050374f4d31712b4a4255504c3048665a413d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a2064376b</pre> + <pre class="wirelog_rx_octets"> 314131397147664e4b77756630444a4a39704a54467049493d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=45843e64, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> Hello, world!</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 818d45843e640de152082aa81e132af6520064</pre> + <pre class="wirelog_tx_frame">004 TX FRAME : OPCODE=6, FIN=True, RSV=0, MASK=fa380a4b, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> Hello, world!</pre> + <pre class="wirelog_tx_octets">005 TX OCTETS: 868dfa380a4bb25d662795142a3c954a662fdb</pre> + <pre class="wirelog_tx_frame">006 TX FRAME : OPCODE=9, FIN=True, RSV=0, MASK=cfe1af5c, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_octets">007 TX OCTETS: 8980cfe1af5c</pre> + <pre class="wirelog_kill_after">008 FAIL CONNECTION AFTER 1.000000 sec</pre> + <pre class="wirelog_rx_octets">009 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">010 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tx_frame">011 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=4f85959a, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">012 TX OCTETS: 88824f85959a4c6d</pre> + <pre class="wirelog_tcp_closed_by_peer">013 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_4_2_3.html b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_4_2_3.html new file mode 100644 index 00000000..815e8025 --- /dev/null +++ b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_4_2_3.html @@ -0,0 +1,305 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_non_strict">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 4.2.3</b></span> : Non-Strict - <span style="font-size: 0.9em;"><b>3</b> ms @ 2012-02-04T15:41:06Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message, then send frame with reserved control <b>Opcode = 13</b>, then send Ping.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events match at least one expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'NON-STRICT': [], 'OK': [('message', 'Hello, world!', False)]}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>Connection was properly closed (OK)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: QWLCYu7pAj1LS5HIqlz7mg== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: ctI9BeFJRgdrhJO7B0QsoaPVOkI=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">False</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">False</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1000</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">None</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td><td>131</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>6</td><td>2</td><td>12</td></tr> + <tr class="stats_row"><td>8</td><td>1</td><td>8</td></tr> + <tr class="stats_row"><td>19</td><td>1</td><td>19</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>5</td><td>240</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>1</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_row"><td>9</td><td>1</td></tr> + <tr class="stats_row"><td>13</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>4</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a2051574c43</pre> + <pre class="wirelog_tx_octets"> 59753770416a314c53354849716c7a376d673d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a20637449</pre> + <pre class="wirelog_rx_octets"> 394265464a52676472684a4f37423051736f6150564f6b493d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=24435521, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> Hello, world!</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 818d244355216c26394d4b6f75564b31394505</pre> + <pre class="wirelog_tx_frame">004 TX FRAME : OPCODE=13, FIN=True, RSV=0, MASK=b81daa6e, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_octets">005 TX OCTETS: 8d80b81daa6e</pre> + <pre class="wirelog_tx_frame">006 TX FRAME : OPCODE=9, FIN=True, RSV=0, MASK=a52d50b5, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_octets">007 TX OCTETS: 8980a52d50b5</pre> + <pre class="wirelog_kill_after">008 FAIL CONNECTION AFTER 1.000000 sec</pre> + <pre class="wirelog_rx_octets">009 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">010 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tx_frame">011 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=1564e876, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">012 TX OCTETS: 88821564e876168c</pre> + <pre class="wirelog_tcp_closed_by_peer">013 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_4_2_4.html b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_4_2_4.html new file mode 100644 index 00000000..212d2a92 --- /dev/null +++ b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_4_2_4.html @@ -0,0 +1,306 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_non_strict">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 4.2.4</b></span> : Non-Strict - <span style="font-size: 0.9em;"><b>3</b> ms @ 2012-02-04T15:41:06Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send small text message, then send frame with reserved control <b>Opcode = 14</b> and non-empty payload, then send Ping.</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events match at least one expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'NON-STRICT': [], 'OK': [('message', 'Hello, world!', False)]}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>Connection was properly closed (OK)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: Kh3b3ltcf0ADlsGo2EnkTg== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: 1v44L9ZzUJ6KHZJ5yJYvz+wLNC4=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">False</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">False</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1000</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">None</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td><td>131</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>6</td><td>1</td><td>6</td></tr> + <tr class="stats_row"><td>8</td><td>1</td><td>8</td></tr> + <tr class="stats_row"><td>19</td><td>2</td><td>38</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>5</td><td>253</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>1</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_row"><td>9</td><td>1</td></tr> + <tr class="stats_row"><td>14</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>4</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a204b683362</pre> + <pre class="wirelog_tx_octets"> 336c7463663041446c73476f32456e6b54673d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a20317634</pre> + <pre class="wirelog_rx_octets"> 344c395a7a554a364b485a4a35794a59767a2b774c4e43343d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=4a1e0f77, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> Hello, world!</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 818d4a1e0f77027b631b25322f00256c63136b</pre> + <pre class="wirelog_tx_frame">004 TX FRAME : OPCODE=14, FIN=True, RSV=0, MASK=8a729523, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> Hello, world!</pre> + <pre class="wirelog_tx_octets">005 TX OCTETS: 8e8d8a729523c217f94fe55eb554e500f947ab</pre> + <pre class="wirelog_tx_frame">006 TX FRAME : OPCODE=9, FIN=True, RSV=0, MASK=158f23ed, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_octets">007 TX OCTETS: 8980158f23ed</pre> + <pre class="wirelog_kill_after">008 FAIL CONNECTION AFTER 1.000000 sec</pre> + <pre class="wirelog_rx_octets">009 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">010 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tx_frame">011 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=9ff83713, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">012 TX OCTETS: 88829ff837139c10</pre> + <pre class="wirelog_tcp_closed_by_peer">013 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_1.html b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_1.html new file mode 100644 index 00000000..b368498d --- /dev/null +++ b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_1.html @@ -0,0 +1,304 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_failed">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 6.20.1</b></span> : Fail - <span style="font-size: 0.9em;"><b>501</b> ms @ 2012-02-04T15:41:19Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>eda080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events differ from any expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'OK': []}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[('message', '?', False)]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>The connection was failed by the wrong endpoint (FAILED)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: 8hQi20KtwNAl024fziMiZA== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: 2vQQWjYeEC/rYsn25QkOUP1J64E=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">True</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">True</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1001</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">Going Away</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>3</td><td>1</td><td>3</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>134</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>9</td><td>1</td><td>9</td></tr> + <tr class="stats_row"><td>18</td><td>1</td><td>18</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>228</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a2038685169</pre> + <pre class="wirelog_tx_octets"> 32304b74774e416c303234667a694d695a413d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a20327651</pre> + <pre class="wirelog_rx_octets"> 51576a596545432f7259736e3235516b4f5550314a3634453d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=09392f03, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> ���</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 818309392f03e499af</pre> + <pre class="wirelog_kill_after">004 FAIL CONNECTION AFTER 0.500000 sec</pre> + <pre class="wirelog_rx_octets">005 RX OCTETS: 81013f</pre> + <pre class="wirelog_rx_frame">006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_rx_frame"> ?</pre> + <pre class="wirelog_kill_after">007 FAILING CONNECTION</pre> + <pre class="wirelog_tx_frame">008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=1a762475, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �Going Away</pre> + <pre class="wirelog_tx_octets">009 TX OCTETS: 888c1a762475199f631a731843555b01450c</pre> + <pre class="wirelog_rx_octets">010 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tcp_closed_by_peer">012 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_2.html b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_2.html new file mode 100644 index 00000000..85a01254 --- /dev/null +++ b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_2.html @@ -0,0 +1,304 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_failed">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 6.20.2</b></span> : Fail - <span style="font-size: 0.9em;"><b>501</b> ms @ 2012-02-04T15:41:19Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>edadbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events differ from any expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'OK': []}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[('message', '?', False)]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>The connection was failed by the wrong endpoint (FAILED)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: TIQj7T90U3MBd7UDV/Tkeg== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: mKV6+d9BjSDvtA484tge96+03HE=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">True</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">True</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1001</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">Going Away</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>3</td><td>1</td><td>3</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>134</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>9</td><td>1</td><td>9</td></tr> + <tr class="stats_row"><td>18</td><td>1</td><td>18</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>228</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a205449516a</pre> + <pre class="wirelog_tx_octets"> 3754393055334d4264375544562f546b65673d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a206d4b56</pre> + <pre class="wirelog_rx_octets"> 362b6439426a534476744134383474676539362b303348453d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=5711da46, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> ���</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 81835711da46babc65</pre> + <pre class="wirelog_kill_after">004 FAIL CONNECTION AFTER 0.500000 sec</pre> + <pre class="wirelog_rx_octets">005 RX OCTETS: 81013f</pre> + <pre class="wirelog_rx_frame">006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_rx_frame"> ?</pre> + <pre class="wirelog_kill_after">007 FAILING CONNECTION</pre> + <pre class="wirelog_tx_frame">008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=d65c7a61, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �Going Away</pre> + <pre class="wirelog_tx_octets">009 TX OCTETS: 888cd65c7a61d5b53d0ebf321d41972b1b18</pre> + <pre class="wirelog_rx_octets">010 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tcp_closed_by_peer">012 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_3.html b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_3.html new file mode 100644 index 00000000..4ae15cf2 --- /dev/null +++ b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_3.html @@ -0,0 +1,304 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_failed">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 6.20.3</b></span> : Fail - <span style="font-size: 0.9em;"><b>501</b> ms @ 2012-02-04T15:41:20Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>edae80</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events differ from any expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'OK': []}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[('message', '?', False)]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>The connection was failed by the wrong endpoint (FAILED)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: 0CmjM4vLTi5KG77RquVjKQ== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: rsX/MnJlDY6UxbWeADBpCfev0IQ=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">True</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">True</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1001</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">Going Away</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>3</td><td>1</td><td>3</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>134</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>9</td><td>1</td><td>9</td></tr> + <tr class="stats_row"><td>18</td><td>1</td><td>18</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>228</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a2030436d6a</pre> + <pre class="wirelog_tx_octets"> 4d34764c5469354b473737527175566a4b513d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a20727358</pre> + <pre class="wirelog_rx_octets"> 2f4d6e4a6c445936557862576541444270436665763049513d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=ccfdb4e5, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> ���</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 8183ccfdb4e5215334</pre> + <pre class="wirelog_kill_after">004 FAIL CONNECTION AFTER 0.500000 sec</pre> + <pre class="wirelog_rx_octets">005 RX OCTETS: 81013f</pre> + <pre class="wirelog_rx_frame">006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_rx_frame"> ?</pre> + <pre class="wirelog_kill_after">007 FAILING CONNECTION</pre> + <pre class="wirelog_tx_frame">008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=459bc411, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �Going Away</pre> + <pre class="wirelog_tx_octets">009 TX OCTETS: 888c459bc4114672837e2cf5a33104eca568</pre> + <pre class="wirelog_rx_octets">010 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tcp_closed_by_peer">012 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_4.html b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_4.html new file mode 100644 index 00000000..f555f5ce --- /dev/null +++ b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_4.html @@ -0,0 +1,304 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_failed">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 6.20.4</b></span> : Fail - <span style="font-size: 0.9em;"><b>501</b> ms @ 2012-02-04T15:41:20Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>edafbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events differ from any expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'OK': []}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[('message', '?', False)]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>The connection was failed by the wrong endpoint (FAILED)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: 9Xx5xE9r30LfukV3JZQxMg== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: IsjbPBLW/m9/EBoONc42PJANrK0=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">True</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">True</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1001</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">Going Away</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>3</td><td>1</td><td>3</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>134</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>9</td><td>1</td><td>9</td></tr> + <tr class="stats_row"><td>18</td><td>1</td><td>18</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>228</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a2039587835</pre> + <pre class="wirelog_tx_octets"> 7845397233304c66756b56334a5a51784d673d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a2049736a</pre> + <pre class="wirelog_rx_octets"> 6250424c572f6d392f45426f4f4e633432504a414e724b303d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=a58665e4, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> ���</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 8183a58665e44829da</pre> + <pre class="wirelog_kill_after">004 FAIL CONNECTION AFTER 0.500000 sec</pre> + <pre class="wirelog_rx_octets">005 RX OCTETS: 81013f</pre> + <pre class="wirelog_rx_frame">006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_rx_frame"> ?</pre> + <pre class="wirelog_kill_after">007 FAILING CONNECTION</pre> + <pre class="wirelog_tx_frame">008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=3ec8dc0c, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �Going Away</pre> + <pre class="wirelog_tx_octets">009 TX OCTETS: 888c3ec8dc0c3d219b6357a6bb2c7fbfbd75</pre> + <pre class="wirelog_rx_octets">010 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tcp_closed_by_peer">012 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_5.html b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_5.html new file mode 100644 index 00000000..bdea9156 --- /dev/null +++ b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_5.html @@ -0,0 +1,304 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_failed">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 6.20.5</b></span> : Fail - <span style="font-size: 0.9em;"><b>501</b> ms @ 2012-02-04T15:41:21Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>edb080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events differ from any expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'OK': []}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[('message', '?', False)]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>The connection was failed by the wrong endpoint (FAILED)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: kaPj4eOJvtYO/p1+SUWjFA== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: gjAUZ7QN+QSE+ENjYD8MxlmR9fg=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">True</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">True</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1001</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">Going Away</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>3</td><td>1</td><td>3</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>134</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>9</td><td>1</td><td>9</td></tr> + <tr class="stats_row"><td>18</td><td>1</td><td>18</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>228</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a206b61506a</pre> + <pre class="wirelog_tx_octets"> 34654f4a7674594f2f70312b5355576a46413d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a20676a41</pre> + <pre class="wirelog_rx_octets"> 555a37514e2b5153452b454e6a5944384d786c6d523966673d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=a90dd396, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> ���</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 8183a90dd39644bd53</pre> + <pre class="wirelog_kill_after">004 FAIL CONNECTION AFTER 0.500000 sec</pre> + <pre class="wirelog_rx_octets">005 RX OCTETS: 81013f</pre> + <pre class="wirelog_rx_frame">006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_rx_frame"> ?</pre> + <pre class="wirelog_kill_after">007 FAILING CONNECTION</pre> + <pre class="wirelog_tx_frame">008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=e0ae2699, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �Going Away</pre> + <pre class="wirelog_tx_octets">009 TX OCTETS: 888ce0ae2699e34761f689c041b9a1d947e0</pre> + <pre class="wirelog_rx_octets">010 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tcp_closed_by_peer">012 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_6.html b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_6.html new file mode 100644 index 00000000..9b219d47 --- /dev/null +++ b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_6.html @@ -0,0 +1,304 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_failed">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 6.20.6</b></span> : Fail - <span style="font-size: 0.9em;"><b>501</b> ms @ 2012-02-04T15:41:21Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>edbe80</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events differ from any expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'OK': []}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[('message', '?', False)]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>The connection was failed by the wrong endpoint (FAILED)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: D3AO5HGDbIPBkfq4kMmnpA== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: hv3czFZ5FFyETGd0ABBqDf9iiMs=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">True</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">True</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1001</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">Going Away</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>3</td><td>1</td><td>3</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>134</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>9</td><td>1</td><td>9</td></tr> + <tr class="stats_row"><td>18</td><td>1</td><td>18</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>228</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a204433414f</pre> + <pre class="wirelog_tx_octets"> 35484744624950426b6671346b4d6d6e70413d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a20687633</pre> + <pre class="wirelog_rx_octets"> 637a465a3546467945544764304142427144663969694d733d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=5bd63f0e, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> ���</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 81835bd63f0eb668bf</pre> + <pre class="wirelog_kill_after">004 FAIL CONNECTION AFTER 0.500000 sec</pre> + <pre class="wirelog_rx_octets">005 RX OCTETS: 81013f</pre> + <pre class="wirelog_rx_frame">006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_rx_frame"> ?</pre> + <pre class="wirelog_kill_after">007 FAILING CONNECTION</pre> + <pre class="wirelog_tx_frame">008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=022b8ad0, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �Going Away</pre> + <pre class="wirelog_tx_octets">009 TX OCTETS: 888c022b8ad001c2cdbf6b45edf0435ceba9</pre> + <pre class="wirelog_rx_octets">010 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tcp_closed_by_peer">012 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_7.html b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_7.html new file mode 100644 index 00000000..f36f8858 --- /dev/null +++ b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_20_7.html @@ -0,0 +1,304 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_failed">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 6.20.7</b></span> : Fail - <span style="font-size: 0.9em;"><b>501</b> ms @ 2012-02-04T15:41:22Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>���<br>edbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events differ from any expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'OK': []}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[('message', '?', False)]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>The connection was failed by the wrong endpoint (FAILED)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: W6Be3rM7Egj4hNIscD5zEA== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: G36lsKHfg04ueUxN92iO/nJvKO8=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">True</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">True</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1001</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">Going Away</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>3</td><td>1</td><td>3</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>134</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>9</td><td>1</td><td>9</td></tr> + <tr class="stats_row"><td>18</td><td>1</td><td>18</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>228</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a2057364265</pre> + <pre class="wirelog_tx_octets"> 33724d3745676a34684e49736344357a45413d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a20473336</pre> + <pre class="wirelog_rx_octets"> 6c734b4866673034756555784e3932694f2f6e4a764b4f383d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=af65e8a8, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> ���</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 8183af65e8a842da57</pre> + <pre class="wirelog_kill_after">004 FAIL CONNECTION AFTER 0.500000 sec</pre> + <pre class="wirelog_rx_octets">005 RX OCTETS: 81013f</pre> + <pre class="wirelog_rx_frame">006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_rx_frame"> ?</pre> + <pre class="wirelog_kill_after">007 FAILING CONNECTION</pre> + <pre class="wirelog_tx_frame">008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=87dcfe76, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �Going Away</pre> + <pre class="wirelog_tx_octets">009 TX OCTETS: 888c87dcfe768435b919eeb29956c6ab9f0f</pre> + <pre class="wirelog_rx_octets">010 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tcp_closed_by_peer">012 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_1.html b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_1.html new file mode 100644 index 00000000..b23b374b --- /dev/null +++ b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_1.html @@ -0,0 +1,304 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_failed">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 6.21.1</b></span> : Fail - <span style="font-size: 0.9em;"><b>501</b> ms @ 2012-02-04T15:41:22Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>eda080edb080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events differ from any expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'OK': []}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[('message', '\xf0\x90\x80\x80', False)]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>The connection was failed by the wrong endpoint (FAILED)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: 4+A0RTBdoXG1pq8BzP6v7A== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: hOGfIW+mXTQpFxEAAzhJQZBq3D0=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">True</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">True</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1001</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">Going Away</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>6</td><td>1</td><td>6</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>137</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>12</td><td>1</td><td>12</td></tr> + <tr class="stats_row"><td>18</td><td>1</td><td>18</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>231</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a20342b4130</pre> + <pre class="wirelog_tx_octets"> 525442646f584731707138427a50367637413d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a20684f47</pre> + <pre class="wirelog_rx_octets"> 6649572b6d5854517046784541417a684a515a42713344303d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=fa43fe25, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> ������</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 8186fa43fe2517e37ec84ac3</pre> + <pre class="wirelog_kill_after">004 FAIL CONNECTION AFTER 0.500000 sec</pre> + <pre class="wirelog_rx_octets">005 RX OCTETS: 8104f0908080</pre> + <pre class="wirelog_rx_frame">006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_rx_frame"> 𐀀</pre> + <pre class="wirelog_kill_after">007 FAILING CONNECTION</pre> + <pre class="wirelog_tx_frame">008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=9d22e55b, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �Going Away</pre> + <pre class="wirelog_tx_octets">009 TX OCTETS: 888c9d22e55b9ecba234f44c827bdc558422</pre> + <pre class="wirelog_rx_octets">010 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tcp_closed_by_peer">012 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_2.html b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_2.html new file mode 100644 index 00000000..1df9c568 --- /dev/null +++ b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_2.html @@ -0,0 +1,304 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_failed">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 6.21.2</b></span> : Fail - <span style="font-size: 0.9em;"><b>501</b> ms @ 2012-02-04T15:41:23Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>eda080edbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events differ from any expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'OK': []}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[('message', '\xf0\x90\x8f\xbf', False)]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>The connection was failed by the wrong endpoint (FAILED)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: iPuOK+BgWhbmEfm05SYBXA== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: nFtyv6PfaFkNa7HnveKzhe3IZ30=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">True</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">True</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1001</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">Going Away</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>6</td><td>1</td><td>6</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>137</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>12</td><td>1</td><td>12</td></tr> + <tr class="stats_row"><td>18</td><td>1</td><td>18</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>231</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a206950754f</pre> + <pre class="wirelog_tx_octets"> 4b2b42675768626d45666d303553594258413d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a206e4674</pre> + <pre class="wirelog_rx_octets"> 797636506661466b4e6137486e76654b7a686533495a33303d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=3cf02dbc, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> ������</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 81863cf02dbcd150ad51834f</pre> + <pre class="wirelog_kill_after">004 FAIL CONNECTION AFTER 0.500000 sec</pre> + <pre class="wirelog_rx_octets">005 RX OCTETS: 8104f0908fbf</pre> + <pre class="wirelog_rx_frame">006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_rx_frame"> </pre> + <pre class="wirelog_kill_after">007 FAILING CONNECTION</pre> + <pre class="wirelog_tx_frame">008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=28c01a4b, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �Going Away</pre> + <pre class="wirelog_tx_octets">009 TX OCTETS: 888c28c01a4b2b295d2441ae7d6b69b77b32</pre> + <pre class="wirelog_rx_octets">010 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tcp_closed_by_peer">012 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_3.html b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_3.html new file mode 100644 index 00000000..604ab44f --- /dev/null +++ b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_3.html @@ -0,0 +1,304 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_failed">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 6.21.3</b></span> : Fail - <span style="font-size: 0.9em;"><b>501</b> ms @ 2012-02-04T15:41:23Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>edadbfedb080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events differ from any expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'OK': []}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[('message', '\xf3\xaf\xb0\x80', False)]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>The connection was failed by the wrong endpoint (FAILED)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: IxlOTYbZXdgzf0DqE51BTQ== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: /BBFWqSURVgKaSzwpkh/xBflPdw=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">True</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">True</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1001</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">Going Away</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>6</td><td>1</td><td>6</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>137</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>12</td><td>1</td><td>12</td></tr> + <tr class="stats_row"><td>18</td><td>1</td><td>18</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>231</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a2049786c4f</pre> + <pre class="wirelog_tx_octets"> 5459625a5864677a663044714535314254513d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a202f4242</pre> + <pre class="wirelog_rx_octets"> 46577153555256674b61537a77706b682f7842666c5064773d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=826ee366, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> ������</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 8186826ee3666fc35c8b32ee</pre> + <pre class="wirelog_kill_after">004 FAIL CONNECTION AFTER 0.500000 sec</pre> + <pre class="wirelog_rx_octets">005 RX OCTETS: 8104f3afb080</pre> + <pre class="wirelog_rx_frame">006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_rx_frame"> </pre> + <pre class="wirelog_kill_after">007 FAILING CONNECTION</pre> + <pre class="wirelog_tx_frame">008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=bebc7f7d, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �Going Away</pre> + <pre class="wirelog_tx_octets">009 TX OCTETS: 888cbebc7f7dbd553812d7d2185dffcb1e04</pre> + <pre class="wirelog_rx_octets">010 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tcp_closed_by_peer">012 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_4.html b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_4.html new file mode 100644 index 00000000..4e24ef1a --- /dev/null +++ b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_4.html @@ -0,0 +1,304 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_failed">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 6.21.4</b></span> : Fail - <span style="font-size: 0.9em;"><b>501</b> ms @ 2012-02-04T15:41:24Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>edadbfedbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events differ from any expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'OK': []}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[('message', '\xf3\xaf\xbf\xbf', False)]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>The connection was failed by the wrong endpoint (FAILED)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: 2dVkikWX/+l/pkvKSrgYMg== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: FZu8BS6bWe/A9bdToW/oxTah8fI=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">True</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">True</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1001</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">Going Away</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>6</td><td>1</td><td>6</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>137</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>12</td><td>1</td><td>12</td></tr> + <tr class="stats_row"><td>18</td><td>1</td><td>18</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>231</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a203264566b</pre> + <pre class="wirelog_tx_octets"> 696b57582f2b6c2f706b764b537267594d673d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a20465a75</pre> + <pre class="wirelog_rx_octets"> 384253366257652f41396264546f572f6f785461683866493d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=0a2a18c5, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> ������</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 81860a2a18c5e787a728b595</pre> + <pre class="wirelog_kill_after">004 FAIL CONNECTION AFTER 0.500000 sec</pre> + <pre class="wirelog_rx_octets">005 RX OCTETS: 8104f3afbfbf</pre> + <pre class="wirelog_rx_frame">006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_rx_frame"> </pre> + <pre class="wirelog_kill_after">007 FAILING CONNECTION</pre> + <pre class="wirelog_tx_frame">008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=16433ff0, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �Going Away</pre> + <pre class="wirelog_tx_octets">009 TX OCTETS: 888c16433ff015aa789f7f2d58d057345e89</pre> + <pre class="wirelog_rx_octets">010 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tcp_closed_by_peer">012 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_5.html b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_5.html new file mode 100644 index 00000000..1dde15cf --- /dev/null +++ b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_5.html @@ -0,0 +1,304 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_failed">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 6.21.5</b></span> : Fail - <span style="font-size: 0.9em;"><b>502</b> ms @ 2012-02-04T15:41:24Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>edae80edb080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events differ from any expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'OK': []}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[('message', '\xf3\xb0\x80\x80', False)]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>The connection was failed by the wrong endpoint (FAILED)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: PM0Ykka0trj5Ks54acrZRw== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: 0rB4RXL5G/swTTfmBlmWgyUkAZQ=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">True</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">True</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1001</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">Going Away</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>6</td><td>1</td><td>6</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>137</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>12</td><td>1</td><td>12</td></tr> + <tr class="stats_row"><td>18</td><td>1</td><td>18</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>231</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a20504d3059</pre> + <pre class="wirelog_tx_octets"> 6b6b613074726a354b7335346163725a52773d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a20307242</pre> + <pre class="wirelog_rx_octets"> 3452584c35472f73775454666d426c6d576779556b415a513d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=a4035bc2, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> ������</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 8186a4035bc249addb2f1483</pre> + <pre class="wirelog_kill_after">004 FAIL CONNECTION AFTER 0.500000 sec</pre> + <pre class="wirelog_rx_octets">005 RX OCTETS: 8104f3b08080</pre> + <pre class="wirelog_rx_frame">006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_rx_frame"> </pre> + <pre class="wirelog_kill_after">007 FAILING CONNECTION</pre> + <pre class="wirelog_tx_frame">008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=04cc48c4, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �Going Away</pre> + <pre class="wirelog_tx_octets">009 TX OCTETS: 888c04cc48c407250fab6da22fe445bb29bd</pre> + <pre class="wirelog_rx_octets">010 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tcp_closed_by_peer">012 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_6.html b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_6.html new file mode 100644 index 00000000..50ce3e2d --- /dev/null +++ b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_6.html @@ -0,0 +1,304 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_failed">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 6.21.6</b></span> : Fail - <span style="font-size: 0.9em;"><b>501</b> ms @ 2012-02-04T15:41:25Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>edae80edbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events differ from any expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'OK': []}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[('message', '\xf3\xb0\x8f\xbf', False)]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>The connection was failed by the wrong endpoint (FAILED)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: 3HjHLxTtDIUsMmTglsvCEQ== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: jlA618sXEEsPiOOzZdRjjkUAls8=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">True</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">True</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1001</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">Going Away</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>6</td><td>1</td><td>6</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>137</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>12</td><td>1</td><td>12</td></tr> + <tr class="stats_row"><td>18</td><td>1</td><td>18</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>231</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a2033486a48</pre> + <pre class="wirelog_tx_octets"> 4c785474444955734d6d54676c73764345513d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a206a6c41</pre> + <pre class="wirelog_rx_octets"> 363138735845457350694f4f7a5a64526a6a6b55416c73383d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=76c7ad31, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> ������</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 818676c7ad319b692ddcc978</pre> + <pre class="wirelog_kill_after">004 FAIL CONNECTION AFTER 0.500000 sec</pre> + <pre class="wirelog_rx_octets">005 RX OCTETS: 8104f3b08fbf</pre> + <pre class="wirelog_rx_frame">006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_rx_frame"> </pre> + <pre class="wirelog_kill_after">007 FAILING CONNECTION</pre> + <pre class="wirelog_tx_frame">008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=e1eb5d93, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �Going Away</pre> + <pre class="wirelog_tx_octets">009 TX OCTETS: 888ce1eb5d93e2021afc88853ab3a09c3cea</pre> + <pre class="wirelog_rx_octets">010 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tcp_closed_by_peer">012 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_7.html b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_7.html new file mode 100644 index 00000000..ddde6cbf --- /dev/null +++ b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_7.html @@ -0,0 +1,304 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_failed">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 6.21.7</b></span> : Fail - <span style="font-size: 0.9em;"><b>501</b> ms @ 2012-02-04T15:41:25Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>edafbfedb080</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events differ from any expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'OK': []}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[('message', '\xf4\x8f\xb0\x80', False)]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>The connection was failed by the wrong endpoint (FAILED)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: ZIp0mbBtxEyXUFyOCJ+3+Q== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: DwOv/co2P4JILRERO9r1utPeUz4=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">True</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">True</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1001</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">Going Away</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>6</td><td>1</td><td>6</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>137</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>12</td><td>1</td><td>12</td></tr> + <tr class="stats_row"><td>18</td><td>1</td><td>18</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>231</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a205a497030</pre> + <pre class="wirelog_tx_octets"> 6d624274784579585546794f434a2b332b513d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a2044774f</pre> + <pre class="wirelog_rx_octets"> 762f636f3250344a494c5245524f39723175745065557a343d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=2ae4c89f, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> ������</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 81862ae4c89fc74b77729a64</pre> + <pre class="wirelog_kill_after">004 FAIL CONNECTION AFTER 0.500000 sec</pre> + <pre class="wirelog_rx_octets">005 RX OCTETS: 8104f48fb080</pre> + <pre class="wirelog_rx_frame">006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_rx_frame"> </pre> + <pre class="wirelog_kill_after">007 FAILING CONNECTION</pre> + <pre class="wirelog_tx_frame">008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=367f674f, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �Going Away</pre> + <pre class="wirelog_tx_octets">009 TX OCTETS: 888c367f674f359620205f11006f77080636</pre> + <pre class="wirelog_rx_octets">010 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tcp_closed_by_peer">012 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_8.html b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_8.html new file mode 100644 index 00000000..dee22d7c --- /dev/null +++ b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_21_8.html @@ -0,0 +1,304 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_failed">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 6.21.8</b></span> : Fail - <span style="font-size: 0.9em;"><b>501</b> ms @ 2012-02-04T15:41:26Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is not valid UTF-8 in one fragment.<br><br>MESSAGE:<br>������<br>edafbfedbfbf</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events differ from any expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'OK': []}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[('message', '\xf4\x8f\xbf\xbf', False)]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>The connection was failed by the wrong endpoint (FAILED)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: OaUcFV4MSdP7nxBXK+S38Q== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: j0hDBbytdKrxduaM2lyuEGp5b+M=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">True</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">True</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1001</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">Going Away</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>6</td><td>1</td><td>6</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>137</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>12</td><td>1</td><td>12</td></tr> + <tr class="stats_row"><td>18</td><td>1</td><td>18</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>231</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a204f615563</pre> + <pre class="wirelog_tx_octets"> 4656344d536450376e7842584b2b533338513d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a206a3068</pre> + <pre class="wirelog_rx_octets"> 4442627974644b72786475614d326c797545477035622b4d3d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=7c269b4e, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> ������</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 81867c269b4e918924a3c399</pre> + <pre class="wirelog_kill_after">004 FAIL CONNECTION AFTER 0.500000 sec</pre> + <pre class="wirelog_rx_octets">005 RX OCTETS: 8104f48fbfbf</pre> + <pre class="wirelog_rx_frame">006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_rx_frame"> </pre> + <pre class="wirelog_kill_after">007 FAILING CONNECTION</pre> + <pre class="wirelog_tx_frame">008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=9603c3bf, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �Going Away</pre> + <pre class="wirelog_tx_octets">009 TX OCTETS: 888c9603c3bf95ea84d0ff6da49fd774a2c6</pre> + <pre class="wirelog_rx_octets">010 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tcp_closed_by_peer">012 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_3_1.html b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_3_1.html new file mode 100644 index 00000000..d7a1a806 --- /dev/null +++ b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_3_1.html @@ -0,0 +1,304 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_failed">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 6.3.1</b></span> : Fail - <span style="font-size: 0.9em;"><b>1001</b> ms @ 2012-02-04T15:41:08Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send invalid UTF-8 text message unfragmented.<br><br>MESSAGE:<br>κόσμε���edited<br>cebae1bdb9cf83cebcceb5eda080656469746564</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events differ from any expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'OK': []}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[('message', '\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5?edited', False)]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>The connection was failed by the wrong endpoint (FAILED)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: giRxaADMiudnciYu2X3Ntw== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: VbnTiF6FgXdncLIxXEj+z/bK0y8=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">True</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">True</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1001</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">Going Away</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>20</td><td>1</td><td>20</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>151</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>18</td><td>1</td><td>18</td></tr> + <tr class="stats_row"><td>26</td><td>1</td><td>26</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>245</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a2067695278</pre> + <pre class="wirelog_tx_octets"> 6141444d6975646e636959753258334e74773d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a2056626e</pre> + <pre class="wirelog_rx_octets"> 54694636466758646e634c497858456a2b7a2f624b3079383d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=35445993, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> κόσμε���edited</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 819435445993fbfeb82e8c8bda5d898aec7e95c43cf75c303cf7</pre> + <pre class="wirelog_kill_after">004 FAIL CONNECTION AFTER 1.000000 sec</pre> + <pre class="wirelog_rx_octets">005 RX OCTETS: 8112cebae1bdb9cf83cebcceb53f656469746564</pre> + <pre class="wirelog_rx_frame">006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_rx_frame"> κόσμε?edited</pre> + <pre class="wirelog_kill_after">007 FAILING CONNECTION</pre> + <pre class="wirelog_tx_frame">008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=79a473a8, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �Going Away</pre> + <pre class="wirelog_tx_octets">009 TX OCTETS: 888c79a473a87a4d34c710ca148838d312d1</pre> + <pre class="wirelog_rx_octets">010 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tcp_closed_by_peer">012 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_3_2.html b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_3_2.html new file mode 100644 index 00000000..ff7dfa5f --- /dev/null +++ b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_3_2.html @@ -0,0 +1,365 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_failed">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 6.3.2</b></span> : Fail - <span style="font-size: 0.9em;"><b>1002</b> ms @ 2012-02-04T15:41:09Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send invalid UTF-8 text message in fragments of 1 octet, resulting in frames ending on positions which are not code point ends.<br><br>MESSAGE:<br>κόσμε���edited<br>cebae1bdb9cf83cebcceb5eda080656469746564</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The connection is failed immediately, since the payload is not valid UTF-8.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events differ from any expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'OK': []}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[('message', '\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5?edited', False)]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>The connection was failed by the wrong endpoint (FAILED)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: +0fzXkyvkSjPhQlQQX66GA== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: LgO0/Am1g9xEfcTU2myHtnWvixQ=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">True</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">True</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1001</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">Going Away</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>20</td><td>1</td><td>20</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>151</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>6</td><td>1</td><td>6</td></tr> + <tr class="stats_row"><td>7</td><td>20</td><td>140</td></tr> + <tr class="stats_row"><td>18</td><td>1</td><td>18</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>23</td><td>365</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>0</td><td>20</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>22</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a202b30667a</pre> + <pre class="wirelog_tx_octets"> 586b79766b536a5068516c515158363647413d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a204c674f</pre> + <pre class="wirelog_rx_octets"> 302f416d316739784566635455326d7948746e57766978513d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=False, RSV=0, MASK=b2f15302, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 0181b2f153027c</pre> + <pre class="wirelog_tx_frame">004 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=c90d6517, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">005 TX OCTETS: 0081c90d651773</pre> + <pre class="wirelog_tx_frame">006 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=cd667264, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">007 TX OCTETS: 0081cd6672642c</pre> + <pre class="wirelog_tx_frame">008 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=024afe11, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">009 TX OCTETS: 0081024afe11bf</pre> + <pre class="wirelog_tx_frame">010 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=80b6c518, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">011 TX OCTETS: 008180b6c51839</pre> + <pre class="wirelog_tx_frame">012 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=62a7724e, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">013 TX OCTETS: 008162a7724ead</pre> + <pre class="wirelog_tx_frame">014 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=42f77b2b, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">015 TX OCTETS: 008142f77b2bc1</pre> + <pre class="wirelog_tx_frame">016 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=c1f0a0f1, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">017 TX OCTETS: 0081c1f0a0f10f</pre> + <pre class="wirelog_tx_frame">018 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=7f9e2838, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">019 TX OCTETS: 00817f9e2838c3</pre> + <pre class="wirelog_tx_frame">020 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=06f47c2c, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">021 TX OCTETS: 008106f47c2cc8</pre> + <pre class="wirelog_tx_frame">022 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=e139a1e1, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">023 TX OCTETS: 0081e139a1e154</pre> + <pre class="wirelog_tx_frame">024 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=d00820e1, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">025 TX OCTETS: 0081d00820e13d</pre> + <pre class="wirelog_tx_frame">026 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=a93ed31b, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">027 TX OCTETS: 0081a93ed31b09</pre> + <pre class="wirelog_tx_frame">028 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=86fe9fa7, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">029 TX OCTETS: 008186fe9fa706</pre> + <pre class="wirelog_tx_frame">030 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=da4f747e, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> e</pre> + <pre class="wirelog_tx_octets">031 TX OCTETS: 0081da4f747ebf</pre> + <pre class="wirelog_tx_frame">032 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=0906c2df, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> d</pre> + <pre class="wirelog_tx_octets">033 TX OCTETS: 00810906c2df6d</pre> + <pre class="wirelog_tx_frame">034 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=9c8128e5, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> i</pre> + <pre class="wirelog_tx_octets">035 TX OCTETS: 00819c8128e5f5</pre> + <pre class="wirelog_tx_frame">036 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=c0b043ed, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> t</pre> + <pre class="wirelog_tx_octets">037 TX OCTETS: 0081c0b043edb4</pre> + <pre class="wirelog_tx_frame">038 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=1723209b, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> e</pre> + <pre class="wirelog_tx_octets">039 TX OCTETS: 00811723209b72</pre> + <pre class="wirelog_tx_frame">040 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=a1194c70, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> d</pre> + <pre class="wirelog_tx_octets">041 TX OCTETS: 0081a1194c70c5</pre> + <pre class="wirelog_tx_frame">042 TX FRAME : OPCODE=0, FIN=True, RSV=0, MASK=73e98929, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_octets">043 TX OCTETS: 808073e98929</pre> + <pre class="wirelog_kill_after">044 FAIL CONNECTION AFTER 1.000000 sec</pre> + <pre class="wirelog_rx_octets">045 RX OCTETS: 8112cebae1bdb9cf83cebcceb53f656469746564</pre> + <pre class="wirelog_rx_frame">046 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_rx_frame"> κόσμε?edited</pre> + <pre class="wirelog_kill_after">047 FAILING CONNECTION</pre> + <pre class="wirelog_tx_frame">048 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=52a40872, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �Going Away</pre> + <pre class="wirelog_tx_octets">049 TX OCTETS: 888c52a40872514d4f1d3bca6f5213d3690b</pre> + <pre class="wirelog_rx_octets">050 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">051 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tcp_closed_by_peer">052 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_4_1.html b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_4_1.html new file mode 100644 index 00000000..a7168b71 --- /dev/null +++ b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_4_1.html @@ -0,0 +1,318 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_non_strict">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 6.4.1</b></span> : Non-Strict - <span style="font-size: 0.9em;"><b>2004</b> ms @ 2012-02-04T15:41:10Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send invalid UTF-8 text message in 3 fragments (frames). +First frame payload is valid, then wait, then 2nd frame which contains the payload making the sequence invalid, then wait, then 3rd frame with rest. +Note that PART1 and PART3 are valid UTF-8 in themselves, PART2 is a 0x11000 encoded as in the UTF-8 integer encoding scheme, but the codepoint is invalid (out of range). +<br><br>MESSAGE PARTS:<br> +PART1 = κόσμε (cebae1bdb9cf83cebcceb5)<br> +PART2 = ���� (f4908080)<br> +PART3 = edited (656469746564)<br> +</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The first frame is accepted, we expect to timeout on the first wait. The 2nd frame should be rejected immediately (fail fast on UTF-8). If we timeout, we expect the connection is failed at least then, since the complete message payload is not valid UTF-8.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events match at least one expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'NON-STRICT': [('timeout', 'A'), ('timeout', 'B')], 'OK': [('timeout', 'A')]}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[('timeout', 'A'), ('timeout', 'B')]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>Connection was properly closed (OK)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: UpAkZ//RWLrHfexyOMj3mw== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: HUMfuAwHoYZqlf78+bKZQrSw5t0=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">False</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">False</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1000</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">None</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td><td>131</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>8</td><td>1</td><td>8</td></tr> + <tr class="stats_row"><td>10</td><td>1</td><td>10</td></tr> + <tr class="stats_row"><td>12</td><td>1</td><td>12</td></tr> + <tr class="stats_row"><td>17</td><td>1</td><td>17</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>5</td><td>248</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>1</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>0</td><td>2</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>4</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a205570416b</pre> + <pre class="wirelog_tx_octets"> 5a2f2f52574c7248666578794f4d6a336d773d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a2048554d</pre> + <pre class="wirelog_rx_octets"> 66754177486f595a716c6637382b624b5a517253773574303d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=False, RSV=0, MASK=7ae1bfb5, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> κόσμε</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 018b7ae1bfb5b45b5e08c32e3c7bc62f0a</pre> + <pre class="wirelog_delay">004 DELAY 1.000000 sec for TAG A</pre> + <pre class="wirelog_delay">005 DELAY TIMEOUT on TAG A</pre> + <pre class="wirelog_tx_frame">006 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=98cbbec9, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> ����</pre> + <pre class="wirelog_tx_octets">007 TX OCTETS: 008498cbbec96c5b3e49</pre> + <pre class="wirelog_delay">008 DELAY 1.000000 sec for TAG B</pre> + <pre class="wirelog_delay">009 DELAY TIMEOUT on TAG B</pre> + <pre class="wirelog_tx_frame">010 TX FRAME : OPCODE=0, FIN=True, RSV=0, MASK=61ebbee3, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> edited</pre> + <pre class="wirelog_tx_octets">011 TX OCTETS: 808661ebbee3048fd797048f</pre> + <pre class="wirelog_kill_after">012 FAIL CONNECTION AFTER 1.000000 sec</pre> + <pre class="wirelog_rx_octets">013 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">014 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tx_frame">015 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=6bde347c, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">016 TX OCTETS: 88826bde347c6836</pre> + <pre class="wirelog_tcp_closed_by_peer">017 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_4_2.html b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_4_2.html new file mode 100644 index 00000000..8f4a7f7b --- /dev/null +++ b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_4_2.html @@ -0,0 +1,316 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_non_strict">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 6.4.2</b></span> : Non-Strict - <span style="font-size: 0.9em;"><b>2002</b> ms @ 2012-02-04T15:41:12Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Same as Case 6.4.1, but in 2nd frame, we send only up to and including the octet making the complete payload invalid. +<br><br>MESSAGE PARTS:<br> +PART1 = κόσμε� (cebae1bdb9cf83cebcceb5f4)<br> +PART2 = � (90)<br> +PART3 = ��edited (8080656469746564)<br> +</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The first frame is accepted, we expect to timeout on the first wait. The 2nd frame should be rejected immediately (fail fast on UTF-8). If we timeout, we expect the connection is failed at least then, since the complete message payload is not valid UTF-8.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events match at least one expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'NON-STRICT': [('timeout', 'A'), ('timeout', 'B')], 'OK': [('timeout', 'A')]}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[('timeout', 'A'), ('timeout', 'B')]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>Connection was properly closed (OK)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: Otomp9wsvzxBkXQi+T/5PQ== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: ATAg9VNP6aeymyNC0M0XC/eIdaU=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">False</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">False</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1000</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">None</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td><td>131</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>7</td><td>1</td><td>7</td></tr> + <tr class="stats_row"><td>8</td><td>1</td><td>8</td></tr> + <tr class="stats_row"><td>14</td><td>1</td><td>14</td></tr> + <tr class="stats_row"><td>18</td><td>1</td><td>18</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>5</td><td>248</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>1</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>0</td><td>2</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>4</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a204f746f6d</pre> + <pre class="wirelog_tx_octets"> 70397773767a78426b5851692b542f3550513d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a20415441</pre> + <pre class="wirelog_rx_octets"> 6739564e50366165796d794e43304d3058432f65496461553d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=False, RSV=0, MASK=a71060dd, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> κόσμε�</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 018ca71060dd69aa81601edfe3131bded529</pre> + <pre class="wirelog_delay">004 DELAY 1.000000 sec for TAG A</pre> + <pre class="wirelog_delay">005 DELAY TIMEOUT on TAG A</pre> + <pre class="wirelog_tx_frame">006 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=0dd9f64a, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">007 TX OCTETS: 00810dd9f64a9d</pre> + <pre class="wirelog_delay">008 DELAY 1.000000 sec for TAG B</pre> + <pre class="wirelog_delay">009 DELAY TIMEOUT on TAG B</pre> + <pre class="wirelog_tx_frame">010 TX FRAME : OPCODE=0, FIN=True, RSV=0, MASK=ac34c7f6, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> ��edited</pre> + <pre class="wirelog_tx_octets">011 TX OCTETS: 8088ac34c7f62cb4a292c540a292</pre> + <pre class="wirelog_kill_after">012 FAIL CONNECTION AFTER 1.000000 sec</pre> + <pre class="wirelog_rx_octets">013 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">014 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tx_frame">015 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=a38e00e8, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">016 TX OCTETS: 8882a38e00e8a066</pre> + <pre class="wirelog_tcp_closed_by_peer">017 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_4_3.html b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_4_3.html new file mode 100644 index 00000000..9ffeeeb2 --- /dev/null +++ b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_4_3.html @@ -0,0 +1,312 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_non_strict">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 6.4.3</b></span> : Non-Strict - <span style="font-size: 0.9em;"><b>2003</b> ms @ 2012-02-04T15:41:14Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Same as Case 6.4.1, but we send message not in 3 frames, but in 3 chops of the same message frame. +<br><br>MESSAGE PARTS:<br> +PART1 = κόσμε (cebae1bdb9cf83cebcceb5)<br> +PART2 = ���� (f4908080)<br> +PART3 = edited (656469746564)<br> +</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The first chop is accepted, we expect to timeout on the first wait. The 2nd chop should be rejected immediately (fail fast on UTF-8). If we timeout, we expect the connection is failed at least then, since the complete message payload is not valid UTF-8.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events match at least one expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'NON-STRICT': [('timeout', 'A'), ('timeout', 'B')], 'OK': [('timeout', 'A')]}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[('timeout', 'A'), ('timeout', 'B')]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>Connection was properly closed (OK)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: m117Mgw+RaqCkrbu2OKKcg== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: gsC/JxGAgNUyZuw4sE8CAkazaoY=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">False</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">False</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1000</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">None</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td><td>131</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>4</td><td>1</td><td>4</td></tr> + <tr class="stats_row"><td>6</td><td>3</td><td>18</td></tr> + <tr class="stats_row"><td>8</td><td>1</td><td>8</td></tr> + <tr class="stats_row"><td>11</td><td>1</td><td>11</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>7</td><td>242</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>1</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>0</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a206d313137</pre> + <pre class="wirelog_tx_octets"> 4d67772b526171436b726275324f4b4b63673d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a20677343</pre> + <pre class="wirelog_rx_octets"> 2f4a784741674e55795a75773473453843416b617a616f593d0d0a0d0a</pre> + <pre class="wirelog_tx_octets">002 TX OCTETS: 0195fffcbd31</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 31465c8c46333eff433208</pre> + <pre class="wirelog_delay">004 DELAY 1.000000 sec for TAG A</pre> + <pre class="wirelog_delay">005 DELAY TIMEOUT on TAG A</pre> + <pre class="wirelog_tx_octets">006 TX OCTETS: c56f7c3d</pre> + <pre class="wirelog_delay">007 DELAY 1.000000 sec for TAG B</pre> + <pre class="wirelog_delay">008 DELAY TIMEOUT on TAG B</pre> + <pre class="wirelog_tx_octets">009 TX OCTETS: 549b95c9549b</pre> + <pre class="wirelog_tx_frame">010 TX FRAME : OPCODE=0, FIN=True, RSV=0, MASK=3da3495d, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_octets">011 TX OCTETS: 80803da3495d</pre> + <pre class="wirelog_kill_after">012 FAIL CONNECTION AFTER 1.000000 sec</pre> + <pre class="wirelog_rx_octets">013 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">014 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tx_frame">015 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=c041f88e, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">016 TX OCTETS: 8882c041f88ec3a9</pre> + <pre class="wirelog_tcp_closed_by_peer">017 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_4_4.html b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_4_4.html new file mode 100644 index 00000000..e5344a36 --- /dev/null +++ b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_4_4.html @@ -0,0 +1,312 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_non_strict">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 6.4.4</b></span> : Non-Strict - <span style="font-size: 0.9em;"><b>2003</b> ms @ 2012-02-04T15:41:16Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Same as Case 6.4.2, but we send message not in 3 frames, but in 3 chops of the same message frame. +<br><br>MESSAGE PARTS:<br> +PART1 = κόσμε� (cebae1bdb9cf83cebcceb5f4)<br> +PART2 = � (90)<br> +PART3 = ()<br> +</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The first chop is accepted, we expect to timeout on the first wait. The 2nd chop should be rejected immediately (fail fast on UTF-8). If we timeout, we expect the connection is failed at least then, since the complete message payload is not valid UTF-8.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events match at least one expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'NON-STRICT': [('timeout', 'A'), ('timeout', 'B')], 'OK': [('timeout', 'A')]}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[('timeout', 'A'), ('timeout', 'B')]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>Connection was properly closed (OK)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: XEU6Kj9j1CXrVXerZqPZug== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: m+kDAJTxrTpxSNSgA+UFnbv9xr8=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">False</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">False</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1000</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">None</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td><td>131</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>1</td><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>6</td><td>2</td><td>12</td></tr> + <tr class="stats_row"><td>8</td><td>2</td><td>16</td></tr> + <tr class="stats_row"><td>12</td><td>1</td><td>12</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>7</td><td>242</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>1</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>0</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a2058455536</pre> + <pre class="wirelog_tx_octets"> 4b6a396a31435872565865725a71505a75673d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a206d2b6b</pre> + <pre class="wirelog_rx_octets"> 44414a547872547078534e5367412b55466e6276397872383d0d0a0d0a</pre> + <pre class="wirelog_tx_octets">002 TX OCTETS: 019564bd3c28</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: aa07dd95dd72bfe6d87389dc</pre> + <pre class="wirelog_delay">004 DELAY 1.000000 sec for TAG A</pre> + <pre class="wirelog_delay">005 DELAY TIMEOUT on TAG A</pre> + <pre class="wirelog_tx_octets">006 TX OCTETS: f4</pre> + <pre class="wirelog_delay">007 DELAY 1.000000 sec for TAG B</pre> + <pre class="wirelog_delay">008 DELAY TIMEOUT on TAG B</pre> + <pre class="wirelog_tx_octets">009 TX OCTETS: 3dbc4d00d4484d00</pre> + <pre class="wirelog_tx_frame">010 TX FRAME : OPCODE=0, FIN=True, RSV=0, MASK=3a12500f, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_octets">011 TX OCTETS: 80803a12500f</pre> + <pre class="wirelog_kill_after">012 FAIL CONNECTION AFTER 1.000000 sec</pre> + <pre class="wirelog_rx_octets">013 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">014 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tx_frame">015 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=b230610d, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">016 TX OCTETS: 8882b230610db1d8</pre> + <pre class="wirelog_tcp_closed_by_peer">017 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_5_1.html b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_5_1.html new file mode 100644 index 00000000..a75d90b7 --- /dev/null +++ b/cb-tools/java-source/java-source/autobahn reports/servers/tootallnate_websocket_case_6_5_1.html @@ -0,0 +1,303 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <style lang="css"> +body { + background-color: #F4F4F4; + color: #333; + font-family: Segoe UI,Tahoma,Arial,Verdana,sans-serif; +} + +p#intro { + font-family: Cambria,serif; + font-size: 1.1em; + color: #444; +} + +p#intro a { + color: #444; +} + +p#intro a:visited { + color: #444; +} + +.block { + background-color: #e0e0e0; + padding: 16px; + margin: 20px; +} + +p.case_text_block { + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; + color: #444; +} + +p.case_desc { +} + +p.case_expect { +} + +p.case_outcome { +} + +p.case_closing_beh { +} + +pre.http_dump { + font-family: Consolas, "Courier New", monospace; + font-size: 0.8em; + color: #333; + border-radius: 10px; + border: 1px solid #aaa; + padding: 16px; + margin: 4px 20px; +} + +span.case_pickle { + font-family: Consolas, "Courier New", monospace; + font-size: 0.7em; + color: #000; +} + +p#case_result,p#close_result { + border-radius: 10px; + background-color: #e8e2d1; + padding: 20px; + margin: 20px; +} + +h1 { + margin-left: 60px; +} + +h2 { + margin-left: 30px; +} + +h3 { + margin-left: 50px; +} + +a.up { + float: right; + border-radius: 16px; + margin-top: 16px; + margin-bottom: 10px; + + margin-right: 30px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + padding-top: 2px; + background-color: #666; + color: #fff; + text-decoration: none; + font-size: 0.8em; +} + +a.up:visited { +} + +a.up:hover { + background-color: #028ec9; +} +</style> + <style lang="css"> +p.case { + color: #fff; + border-radius: 10px; + padding: 20px; + margin: 12px 20px; + font-size: 1.2em; +} + +p.case_ok { + background-color: #0a0; +} + +p.case_non_strict, p.case_no_close { + background-color: #9a0; +} + +p.case_info { + background-color: #4095BF; +} + +p.case_failed { + background-color: #900; +} + +table { + border-collapse: collapse; + border-spacing: 0px; + margin-left: 80px; + margin-bottom: 12px; + margin-top: 0px; +} + +td +{ + margin: 0; + font-size: 0.8em; + border: 1px #fff solid; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 16px; + padding-right: 16px; + text-align: right; +} + +td.right { + text-align: right; +} + +td.left { + text-align: left; +} + +tr.stats_header { + color: #eee; + background-color: #000; +} + +tr.stats_row { + color: #000; + background-color: #fc3; +} + +tr.stats_total { + color: #fff; + background-color: #888; +} + +div#wirelog { + margin-top: 20px; + margin-bottom: 80px; +} + +pre.wirelog_rx_octets {color: #aaa; margin: 0; background-color: #060; padding: 2px;} +pre.wirelog_tx_octets {color: #aaa; margin: 0; background-color: #600; padding: 2px;} +pre.wirelog_tx_octets_sync {color: #aaa; margin: 0; background-color: #606; padding: 2px;} + +pre.wirelog_rx_frame {color: #fff; margin: 0; background-color: #0a0; padding: 2px;} +pre.wirelog_tx_frame {color: #fff; margin: 0; background-color: #a00; padding: 2px;} +pre.wirelog_tx_frame_sync {color: #fff; margin: 0; background-color: #a0a; padding: 2px;} + +pre.wirelog_delay {color: #fff; margin: 0; background-color: #000; padding: 2px;} +pre.wirelog_kill_after {color: #fff; margin: 0; background-color: #000; padding: 2px;} + +pre.wirelog_tcp_closed_by_me {color: #fff; margin: 0; background-color: #008; padding: 2px;} +pre.wirelog_tcp_closed_by_peer {color: #fff; margin: 0; background-color: #000; padding: 2px;} +</style> + </head> + <body> + <a name="top"></a> + <br/> + <center><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report.png" border="0" width="820" height="46" alt="WebSockets Protocol Test Report"></img></a></center> + <center><a href="http://www.tavendo.de/autobahn" title="Autobahn WebSockets"><img src="http://www.tavendo.de/static/autobahn/ws_protocol_test_report_autobahn.png" border="0" width="300" height="68" alt="Autobahn WebSockets"></img></a></center> + <br/> + <p class="case case_ok">tootallnate/websocket - <span style="font-size: 1.3em;"><b>Case 6.5.1</b></span> : Pass - <span style="font-size: 0.9em;"><b>2</b> ms @ 2012-02-04T15:41:18Z</a></p> + <p class="case_text_block case_desc"><b>Case Description</b><br/><br/>Send a text message with payload which is valid UTF-8 in one fragment.<br><br>MESSAGE:<br>κόσμε<br>cebae1bdb9cf83cebcceb5</p> + <p class="case_text_block case_expect"><b>Case Expectation</b><br/><br/>The message is echo'ed back to us.</p> + + <p class="case_text_block case_outcome"> + <b>Case Outcome</b><br/><br/>Actual events match at least one expected.<br/><br/> + <i>Expected:</i><br/><span class="case_pickle">{'OK': [('message', '\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5', False)]}</span><br/><br/> + <i>Observed:</i><br><span class="case_pickle">[('message', '\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5', False)]</span> + </p> + <p class="case_text_block case_closing_beh"><b>Case Closing Behavior</b><br/><br/>Connection was properly closed (OK)</p> + <br/><hr/> + <h2>Opening Handshake</h2> + <pre class="http_dump">GET / HTTP/1.1 +User-Agent: AutobahnWebSocketsTestSuite/0.4.10 +Host: localhost:9003 +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: Ntl0ifPncXw3kcnMyqYI6A== +Sec-WebSocket-Version: 13</pre> + <pre class="http_dump">HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: EOTzOwYD8bhNnxS8IX/Rcx+8Ess=</pre> + <br/><hr/> + <h2>Closing Behavior</h2> + <table> + <tr class="stats_header"><td>Key</td><td class="left">Value</td><td class="left">Description</td></tr> + <tr class="stats_row"><td>isServer</td><td class="left">False</td><td class="left">True, iff I (the fuzzer) am a server, and the peer is a client.</td></tr> + <tr class="stats_row"><td>closedByMe</td><td class="left">True</td><td class="left">True, iff I have initiated closing handshake (that is, did send close first).</td></tr> + <tr class="stats_row"><td>failedByMe</td><td class="left">False</td><td class="left">True, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.</td></tr> + <tr class="stats_row"><td>droppedByMe</td><td class="left">False</td><td class="left">True, iff I dropped the TCP connection.</td></tr> + <tr class="stats_row"><td>wasClean</td><td class="left">True</td><td class="left">True, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).</td></tr> + <tr class="stats_row"><td>wasNotCleanReason</td><td class="left">None</td><td class="left">When wasClean == False, the reason what happened.</td></tr> + <tr class="stats_row"><td>wasServerConnectionDropTimeout</td><td class="left">False</td><td class="left">When we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.</td></tr> + <tr class="stats_row"><td>wasCloseHandshakeTimeout</td><td class="left">False</td><td class="left">When we initiated a closing handshake, but the peer did not respond in time, this gets True.</td></tr> + <tr class="stats_row"><td>localCloseCode</td><td class="left">1000</td><td class="left">The close code I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>localCloseReason</td><td class="left">None</td><td class="left">The close reason I sent in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseCode</td><td class="left">None</td><td class="left">The close code the peer sent me in close frame (if any).</td></tr> + <tr class="stats_row"><td>remoteCloseReason</td><td class="left">None</td><td class="left">The close reason the peer sent me in close frame (if any).</td></tr> + </table> <br/><hr/> + <h2>Wire Statistics</h2> + <h3>Octets Received by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>2</td><td>1</td><td>2</td></tr> + <tr class="stats_row"><td>13</td><td>1</td><td>13</td></tr> + <tr class="stats_row"><td>129</td><td>1</td><td>129</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>144</td></tr> + </table> + <h3>Octets Transmitted by Chop Size</h3> + <table> + <tr class="stats_header"><td>Chop Size</td><td>Count</td><td>Octets</td></tr> + <tr class="stats_row"><td>8</td><td>1</td><td>8</td></tr> + <tr class="stats_row"><td>17</td><td>1</td><td>17</td></tr> + <tr class="stats_row"><td>201</td><td>1</td><td>201</td></tr> + <tr class="stats_total"><td>Total</td><td>3</td><td>226</td></tr> + </table> + <h3>Frames Received by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <h3>Frames Transmitted by Opcode</h3> + <table> + <tr class="stats_header"><td>Opcode</td><td>Count</td></tr> + <tr class="stats_row"><td>1</td><td>1</td></tr> + <tr class="stats_row"><td>8</td><td>1</td></tr> + <tr class="stats_total"><td>Total</td><td>2</td></tr> + </table> + <br/><hr/> + <h2>Wire Log</h2> + <div id="wirelog"> + <pre class="wirelog_tx_octets">000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374</pre> + <pre class="wirelog_tx_octets"> 53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f</pre> + <pre class="wirelog_tx_octets"> 636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a204e746c30</pre> + <pre class="wirelog_tx_octets"> 6966506e635877336b636e4d7971594936413d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d</pre> + <pre class="wirelog_tx_octets"> 0a</pre> + <pre class="wirelog_rx_octets">001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b</pre> + <pre class="wirelog_rx_octets"> 65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a20454f54</pre> + <pre class="wirelog_rx_octets"> 7a4f7759443862684e6e78533849582f5263782b384573733d0d0a0d0a</pre> + <pre class="wirelog_tx_frame">002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=a3eccd2d, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> κόσμε</pre> + <pre class="wirelog_tx_octets">003 TX OCTETS: 818ba3eccd2d6d562c901a234ee31f2278</pre> + <pre class="wirelog_kill_after">004 FAIL CONNECTION AFTER 0.500000 sec</pre> + <pre class="wirelog_rx_octets">005 RX OCTETS: 810bcebae1bdb9cf83cebcceb5</pre> + <pre class="wirelog_rx_frame">006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_rx_frame"> κόσμε</pre> + <pre class="wirelog_tx_frame">007 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=77c74274, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False</pre> + <pre class="wirelog_tx_frame"> �</pre> + <pre class="wirelog_tx_octets">008 TX OCTETS: 888277c74274742f</pre> + <pre class="wirelog_rx_octets">009 RX OCTETS: 8800</pre> + <pre class="wirelog_rx_frame">010 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None</pre> + <pre class="wirelog_tcp_closed_by_peer">011 TCP DROPPED BY PEER</pre> + </div> + <br/><hr/> + </body> +</html> diff --git a/cb-tools/java-source/java-source/build.gradle b/cb-tools/java-source/java-source/build.gradle new file mode 100644 index 00000000..a87d144f --- /dev/null +++ b/cb-tools/java-source/java-source/build.gradle @@ -0,0 +1,44 @@ +apply plugin: 'java' +apply plugin: 'idea' +apply plugin: 'maven' + + +repositories { + mavenLocal() + mavenCentral() +} + +group = 'org.java_websocket' +version = '1.2.1-SNAPSHOT' +sourceCompatibility = 1.6 +targetCompatibility = 1.6 + +configurations { + deployerJars +} + +configure(install.repositories.mavenInstaller) { + pom.version = project.version + pom.groupId = "org.java_websocket" + pom.artifactId = 'Java-WebSocket' +} + +dependencies { + deployerJars "org.apache.maven.wagon:wagon-webdav:1.0-beta-2" +} + + +//deploy to maven repository +//uploadArchives { +// repositories.mavenDeployer { +// configuration = configurations.deployerJars +// repository(url: repositoryUrl) { +// authentication(userName: repositoryUsername, password: repositoryPassword) +// } +// } +//} + +task wrapper(type: Wrapper) { + gradleVersion = '1.4' +} + diff --git a/cb-tools/java-source/java-source/build.xml b/cb-tools/java-source/java-source/build.xml new file mode 100644 index 00000000..3d33c274 --- /dev/null +++ b/cb-tools/java-source/java-source/build.xml @@ -0,0 +1,29 @@ +<project default="all"> + + <target name="all" depends="doc,jar" /> + + <target name="compile"> + <mkdir dir="build/classes" /> + <mkdir dir="build/examples" /> + <javac includeantruntime="false" debug="on" srcdir="src/main/java" + destdir="build/classes" target="1.5" /> + <javac includeantruntime="false" srcdir="src/main/example/" + classpath="build/classes" destdir="build/examples" /> + </target> + + <target name="jar" depends="compile"> + <jar destfile="dist/java_websocket.jar"> + <fileset dir="build/classes" includes="**/*.class" /> + </jar> + </target> + + <target name="doc"> + <delete dir="doc" /> + <javadoc sourcepath="src/main/java" destdir="doc" /> + </target> + + <target name="clean"> + <delete dir="build" /> + </target> + +</project> diff --git a/cb-tools/java-source/java-source/dist/java_websocket.jar b/cb-tools/java-source/java-source/dist/java_websocket.jar new file mode 100644 index 0000000000000000000000000000000000000000..bb5caeb4e80a53b60a83868128ac5fde26d7ac64 GIT binary patch literal 88934 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 Be2@SD literal 0 HcmV?d00001 diff --git a/cb-tools/java-source/java-source/gradle/wrapper/gradle-wrapper.jar b/cb-tools/java-source/java-source/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..0087cd3b18659b5577cf6ad3ef61f8eb9416ebba GIT binary patch literal 51348 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 Vhu^-h=$~}~qQo@;l019%{{a3e65;>= literal 0 HcmV?d00001 diff --git a/cb-tools/java-source/java-source/gradle/wrapper/gradle-wrapper.properties b/cb-tools/java-source/java-source/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..7dfc31e8 --- /dev/null +++ b/cb-tools/java-source/java-source/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Jul 02 20:43:27 CEST 2014 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-1.4-bin.zip diff --git a/cb-tools/java-source/java-source/gradlew b/cb-tools/java-source/java-source/gradlew new file mode 100644 index 00000000..91a7e269 --- /dev/null +++ b/cb-tools/java-source/java-source/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/cb-tools/java-source/java-source/gradlew.bat b/cb-tools/java-source/java-source/gradlew.bat new file mode 100644 index 00000000..8a0b282a --- /dev/null +++ b/cb-tools/java-source/java-source/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/cb-tools/java-source/java-source/index.html b/cb-tools/java-source/java-source/index.html new file mode 100644 index 00000000..7e46cbb1 --- /dev/null +++ b/cb-tools/java-source/java-source/index.html @@ -0,0 +1,88 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset='utf-8'> + + <title>TooTallNate/Java-WebSocket @ GitHub</title> + + <style type="text/css"> + body { + margin-top: 1.0em; + background-color: #3d84ba; + font-family: Helvetica, Arial, FreeSans, san-serif; + color: #ffffff; + } + #container { + margin: 0 auto; + width: 700px; + } + h1 { font-size: 3.8em; color: #c27b45; margin-bottom: 3px; } + h1 .small { font-size: 0.4em; } + h1 a { text-decoration: none } + h2 { font-size: 1.5em; color: #c27b45; } + h3 { text-align: center; color: #c27b45; } + a { color: #c27b45; } + .description { font-size: 1.2em; margin-bottom: 30px; margin-top: 30px; font-style: italic;} + .download { float: right; } + pre { background: #000; color: #fff; padding: 15px;} + hr { border: 0; width: 80%; border-bottom: 1px solid #aaa} + .footer { text-align:center; padding-top:30px; font-style: italic; } + </style> +</head> + +<body> + <a href="https://github.com/TooTallNate/Java-WebSocket"><img style="position: absolute; top: 0; right: 0; border: 0;" src="http://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on GitHub" /></a> + + <div id="container"> + + <div class="download"> + <a href="https://github.com/TooTallNate/Java-WebSocket/zipball/master"> + <img border="0" width="90" src="https://github.com/images/modules/download/zip.png"></a> + <a href="https://github.com/TooTallNate/Java-WebSocket/tarball/master"> + <img border="0" width="90" src="https://github.com/images/modules/download/tar.png"></a> + </div> + + <h1><a href="https://github.com/TooTallNate/Java-WebSocket">Java-WebSocket</a> + <span class="small">by <a href="https://github.com/TooTallNate">TooTallNate</a></span></h1> + + <div class="description"> + A barebones WebSocket client and server implementation written in 100% Java. + </div> + + + + + + + + + + + <h2>Authors</h2> + <p>Davidiusdadi (DavidRohmer@web.de) <br/>Bob Corsaro (bob@embed.ly) <br/>Don Park (don@donpark.org) <br/>David Rohmer (firstlastname@web.de) <br/>swax (john.m.marshall@gmail.com) <br/>Jarrod Ribble (jribble@netiq.com) <br/>Julian Gautier (julian.gautier@alumni.neumont.edu) <br/>Kristijan Sedlak (k.sedlak@mandatela.si) <br/>morkai (lukasz@walukiewicz.eu) <br/>Nathaniel Michael (natecmichael@gmail.com) <br/>Nathan Rajlich (nathan@tootallnate.net) <br/>sippykup (nobody@nowhere.com) <br/> </p> + + + + <h2>Contact</h2> + <p>Nathan Rajlich (nathan@tootallnate.net) <br/> </p> + + + <h2>Download</h2> + <p> + You can download this project in either + <a href="https://github.com/TooTallNate/Java-WebSocket/zipball/master">zip</a> or + <a href="https://github.com/TooTallNate/Java-WebSocket/tarball/master">tar formats. + </p> + <p>You can also clone the project with <a href="http://git-scm.com">Git</a> + by running: + <pre>$ git clone git://github.com/TooTallNate/Java-WebSocket</pre> + </p> + + <div class="footer"> + get the source code on GitHub : <a href="https://github.com/TooTallNate/Java-WebSocket">TooTallNate/Java-WebSocket</a> + </div> + + </div> + +</body> +</html> diff --git a/cb-tools/java-source/java-source/pom.xml b/cb-tools/java-source/java-source/pom.xml new file mode 100644 index 00000000..9189fba8 --- /dev/null +++ b/cb-tools/java-source/java-source/pom.xml @@ -0,0 +1,119 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <parent> + <groupId>org.sonatype.oss</groupId> + <artifactId>oss-parent</artifactId> + <version>7</version> + </parent> + <scm> + <connection>scm:git:git@github.com:TooTallNate/Java-WebSocket.git</connection> + <developerConnection>scm:git:git@github.com:TooTallNate/Java-WebSocket.git</developerConnection> + <url>git@github.com:TooTallNate/Java-WebSocket.git</url> + </scm> + <modelVersion>4.0.0</modelVersion> + <groupId>org.java-websocket</groupId> + <artifactId>Java-WebSocket</artifactId> + <version>1.3.1-SNAPSHOT</version> + <packaging>jar</packaging> + <name>Java WebSocket</name> + <url>http://java-websocket.org/</url> + <description>A barebones WebSocket client and server implementation written in 100% Java</description> + <properties> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <java.version>1.6</java.version> + </properties> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <version>2.5.1</version> + <configuration> + <source>${java.version}</source> + <target>${java.version}</target> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-source-plugin</artifactId> + <version>2.2.1</version> + <executions> + <execution> + <id>attach-sources</id> + <goals> + <goal>jar</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-javadoc-plugin</artifactId> + <version>2.9</version> + <executions> + <execution> + <id>attach-javadocs</id> + <goals> + <goal>jar</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + <developers> + <developer> + <id>TooTallNate</id> + <name>Nathan Rajlich</name> + <email>nathan@tootallnate.net</email> + <url>https://github.com/TooTallNate</url> + <roles> + <role>founder</role> + </roles> + </developer> + <developer> + <id>Davidiusdadi</id> + <name>David Rohmer</name> + <email>rohmer.david@gmail.com</email> + <url>https://github.com/Davidiusdadi</url> + <roles> + <role>maintainer</role> + </roles> + </developer> + </developers> + <licenses> + <license> + <name>MIT License</name> + <url>http://github.com/TooTallNate/Java-WebSocket/blob/master/LICENSE</url> + </license> + </licenses> + <profiles> + <profile> + <id>release-sign-artifacts</id> + <activation> + <property><name>performRelease</name><value>true</value></property> + </activation> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-gpg-plugin</artifactId> + <version>1.1</version> + <executions> + <execution> + <id>sign-artifacts</id> + <phase>verify</phase> + <goals> + <goal>sign</goal> + </goals> + </execution> + </executions> + <configuration> + <keyname>rohmer.david@gmail.com</keyname> + </configuration> + </plugin> + </plugins> + </build> + </profile> + </profiles> +</project> diff --git a/cb-tools/java-source/java-source/src/main/example/ChatClient.java b/cb-tools/java-source/java-source/src/main/example/ChatClient.java new file mode 100644 index 00000000..96a19100 --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/example/ChatClient.java @@ -0,0 +1,163 @@ +import java.awt.Container; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.WindowEvent; +import java.net.URI; +import java.net.URISyntaxException; + +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JFrame; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.JTextField; + +import org.java_websocket.WebSocketImpl; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.drafts.Draft; +import org.java_websocket.drafts.Draft_10; +import org.java_websocket.drafts.Draft_17; +import org.java_websocket.drafts.Draft_75; +import org.java_websocket.drafts.Draft_76; +import org.java_websocket.handshake.ServerHandshake; + +public class ChatClient extends JFrame implements ActionListener { + private static final long serialVersionUID = -6056260699202978657L; + + private final JTextField uriField; + private final JButton connect; + private final JButton close; + private final JTextArea ta; + private final JTextField chatField; + private final JComboBox draft; + private WebSocketClient cc; + + public ChatClient( String defaultlocation ) { + super( "WebSocket Chat Client" ); + Container c = getContentPane(); + GridLayout layout = new GridLayout(); + layout.setColumns( 1 ); + layout.setRows( 6 ); + c.setLayout( layout ); + + Draft[] drafts = { new Draft_17(), new Draft_10(), new Draft_76(), new Draft_75() }; + draft = new JComboBox( drafts ); + c.add( draft ); + + uriField = new JTextField(); + uriField.setText( defaultlocation ); + c.add( uriField ); + + connect = new JButton( "Connect" ); + connect.addActionListener( this ); + c.add( connect ); + + close = new JButton( "Close" ); + close.addActionListener( this ); + close.setEnabled( false ); + c.add( close ); + + JScrollPane scroll = new JScrollPane(); + ta = new JTextArea(); + scroll.setViewportView( ta ); + c.add( scroll ); + + chatField = new JTextField(); + chatField.setText( "" ); + chatField.addActionListener( this ); + c.add( chatField ); + + java.awt.Dimension d = new java.awt.Dimension( 300, 400 ); + setPreferredSize( d ); + setSize( d ); + + addWindowListener( new java.awt.event.WindowAdapter() { + @Override + public void windowClosing( WindowEvent e ) { + if( cc != null ) { + cc.close(); + } + dispose(); + } + } ); + + setLocationRelativeTo( null ); + setVisible( true ); + } + + public void actionPerformed( ActionEvent e ) { + + if( e.getSource() == chatField ) { + if( cc != null ) { + cc.send( chatField.getText() ); + chatField.setText( "" ); + chatField.requestFocus(); + } + + } else if( e.getSource() == connect ) { + try { + // cc = new ChatClient(new URI(uriField.getText()), area, ( Draft ) draft.getSelectedItem() ); + cc = new WebSocketClient( new URI( uriField.getText() ), (Draft) draft.getSelectedItem() ) { + + @Override + public void onMessage( String message ) { + ta.append( "got: " + message + "\n" ); + ta.setCaretPosition( ta.getDocument().getLength() ); + } + + @Override + public void onOpen( ServerHandshake handshake ) { + ta.append( "You are connected to ChatServer: " + getURI() + "\n" ); + ta.setCaretPosition( ta.getDocument().getLength() ); + } + + @Override + public void onClose( int code, String reason, boolean remote ) { + ta.append( "You have been disconnected from: " + getURI() + "; Code: " + code + " " + reason + "\n" ); + ta.setCaretPosition( ta.getDocument().getLength() ); + connect.setEnabled( true ); + uriField.setEditable( true ); + draft.setEditable( true ); + close.setEnabled( false ); + } + + @Override + public void onError( Exception ex ) { + ta.append( "Exception occured ...\n" + ex + "\n" ); + ta.setCaretPosition( ta.getDocument().getLength() ); + ex.printStackTrace(); + connect.setEnabled( true ); + uriField.setEditable( true ); + draft.setEditable( true ); + close.setEnabled( false ); + } + }; + + close.setEnabled( true ); + connect.setEnabled( false ); + uriField.setEditable( false ); + draft.setEditable( false ); + cc.connect(); + } catch ( URISyntaxException ex ) { + ta.append( uriField.getText() + " is not a valid WebSocket URI\n" ); + } + } else if( e.getSource() == close ) { + cc.close(); + } + } + + public static void main( String[] args ) { + WebSocketImpl.DEBUG = true; + String location; + if( args.length != 0 ) { + location = args[ 0 ]; + System.out.println( "Default server url specified: \'" + location + "\'" ); + } else { + location = "ws://localhost:8887"; + System.out.println( "Default server url not specified: defaulting to \'" + location + "\'" ); + } + new ChatClient( location ); + } + +} diff --git a/cb-tools/java-source/java-source/src/main/example/ChatServer.java b/cb-tools/java-source/java-source/src/main/example/ChatServer.java new file mode 100644 index 00000000..a27cb0ee --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/example/ChatServer.java @@ -0,0 +1,99 @@ +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; +import java.util.Collection; + +import org.java_websocket.WebSocket; +import org.java_websocket.WebSocketImpl; +import org.java_websocket.framing.Framedata; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.server.WebSocketServer; + +/** + * A simple WebSocketServer implementation. Keeps track of a "chatroom". + */ +public class ChatServer extends WebSocketServer { + + public ChatServer( int port ) throws UnknownHostException { + super( new InetSocketAddress( port ) ); + } + + public ChatServer( InetSocketAddress address ) { + super( address ); + } + + @Override + public void onOpen( WebSocket conn, ClientHandshake handshake ) { + this.sendToAll( "new connection: " + handshake.getResourceDescriptor() ); + System.out.println( conn.getRemoteSocketAddress().getAddress().getHostAddress() + " entered the room!" ); + } + + @Override + public void onClose( WebSocket conn, int code, String reason, boolean remote ) { + this.sendToAll( conn + " has left the room!" ); + System.out.println( conn + " has left the room!" ); + } + + @Override + public void onMessage( WebSocket conn, String message ) { + this.sendToAll( message ); + System.out.println( conn + ": " + message ); + } + + @Override + public void onFragment( WebSocket conn, Framedata fragment ) { + System.out.println( "received fragment: " + fragment ); + } + + public static void main( String[] args ) throws InterruptedException , IOException { + WebSocketImpl.DEBUG = true; + int port = 8887; // 843 flash policy port + try { + port = Integer.parseInt( args[ 0 ] ); + } catch ( Exception ex ) { + } + ChatServer s = new ChatServer( port ); + s.start(); + System.out.println( "ChatServer started on port: " + s.getPort() ); + + BufferedReader sysin = new BufferedReader( new InputStreamReader( System.in ) ); + while ( true ) { + String in = sysin.readLine(); + s.sendToAll( in ); + if( in.equals( "exit" ) ) { + s.stop(); + break; + } else if( in.equals( "restart" ) ) { + s.stop(); + s.start(); + break; + } + } + } + @Override + public void onError( WebSocket conn, Exception ex ) { + ex.printStackTrace(); + if( conn != null ) { + // some errors like port binding failed may not be assignable to a specific websocket + } + } + + /** + * Sends <var>text</var> to all currently connected WebSocket clients. + * + * @param text + * The String to send across the network. + * @throws InterruptedException + * When socket related I/O errors occur. + */ + public void sendToAll( String text ) { + Collection<WebSocket> con = connections(); + synchronized ( con ) { + for( WebSocket c : con ) { + c.send( text ); + } + } + } +} diff --git a/cb-tools/java-source/java-source/src/main/example/ExampleClient.java b/cb-tools/java-source/java-source/src/main/example/ExampleClient.java new file mode 100644 index 00000000..3cf68377 --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/example/ExampleClient.java @@ -0,0 +1,54 @@ +import java.net.URI; +import java.net.URISyntaxException; + +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.drafts.Draft; +import org.java_websocket.drafts.Draft_10; +import org.java_websocket.framing.Framedata; +import org.java_websocket.handshake.ServerHandshake; + +/** This example demonstrates how to create a websocket connection to a server. Only the most important callbacks are overloaded. */ +public class ExampleClient extends WebSocketClient { + + public ExampleClient( URI serverUri , Draft draft ) { + super( serverUri, draft ); + } + + public ExampleClient( URI serverURI ) { + super( serverURI ); + } + + @Override + public void onOpen( ServerHandshake handshakedata ) { + System.out.println( "opened connection" ); + // if you plan to refuse connection based on ip or httpfields overload: onWebsocketHandshakeReceivedAsClient + } + + @Override + public void onMessage( String message ) { + System.out.println( "received: " + message ); + } + + @Override + public void onFragment( Framedata fragment ) { + System.out.println( "received fragment: " + new String( fragment.getPayloadData().array() ) ); + } + + @Override + public void onClose( int code, String reason, boolean remote ) { + // The codecodes are documented in class org.java_websocket.framing.CloseFrame + System.out.println( "Connection closed by " + ( remote ? "remote peer" : "us" ) ); + } + + @Override + public void onError( Exception ex ) { + ex.printStackTrace(); + // if the error is fatal then onClose will be called additionally + } + + public static void main( String[] args ) throws URISyntaxException { + ExampleClient c = new ExampleClient( new URI( "ws://localhost:8887" ), new Draft_10() ); // more about drafts here: http://github.com/TooTallNate/Java-WebSocket/wiki/Drafts + c.connect(); + } + +} \ No newline at end of file diff --git a/cb-tools/java-source/java-source/src/main/example/FragmentedFramesExample.java b/cb-tools/java-source/java-source/src/main/example/FragmentedFramesExample.java new file mode 100644 index 00000000..df3c929d --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/example/FragmentedFramesExample.java @@ -0,0 +1,57 @@ +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.ByteBuffer; + +import org.java_websocket.WebSocket; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.drafts.Draft_17; +import org.java_websocket.framing.Framedata.Opcode; + +/** + * This example shows how to send fragmented frames.<br> + * For information on when to used fragmented frames see http://tools.ietf.org/html/rfc6455#section-5.4<br> + * Fragmented and normal messages can not be mixed. + * One is however allowed to mix them with control messages like ping/pong. + * + * @see WebSocket#sendFragmentedFrame(Opcode, ByteBuffer, boolean) + **/ +public class FragmentedFramesExample { + public static void main( String[] args ) throws URISyntaxException , IOException , InterruptedException { + // WebSocketImpl.DEBUG = true; // will give extra output + + WebSocketClient websocket = new ExampleClient( new URI( "ws://localhost:8887" ), new Draft_17() ); // Draft_17 is implementation of rfc6455 + if( !websocket.connectBlocking() ) { + System.err.println( "Could not connect to the server." ); + return; + } + + System.out.println( "This example shows how to send fragmented(continuous) messages." ); + + BufferedReader stdin = new BufferedReader( new InputStreamReader( System.in ) ); + while ( websocket.isOpen() ) { + System.out.println( "Please type in a loooooong line(which then will be send in 2 byte fragments):" ); + String longline = stdin.readLine(); + ByteBuffer longelinebuffer = ByteBuffer.wrap( longline.getBytes() ); + longelinebuffer.rewind(); + + for( int position = 2 ; ; position += 2 ) { + if( position < longelinebuffer.capacity() ) { + longelinebuffer.limit( position ); + websocket.sendFragmentedFrame( Opcode.TEXT, longelinebuffer, false );// when sending binary data one should use Opcode.BINARY + assert ( longelinebuffer.remaining() == 0 ); + // after calling sendFragmentedFrame one may reuse the buffer given to the method immediately + } else { + longelinebuffer.limit( longelinebuffer.capacity() ); + websocket.sendFragmentedFrame( Opcode.TEXT, longelinebuffer, true );// sending the last frame + break; + } + + } + System.out.println( "You can not type in the next long message or press Ctr-C to exit." ); + } + System.out.println( "FragmentedFramesExample terminated" ); + } +} diff --git a/cb-tools/java-source/java-source/src/main/example/ProxyClientExample.java b/cb-tools/java-source/java-source/src/main/example/ProxyClientExample.java new file mode 100644 index 00000000..ddff0ea0 --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/example/ProxyClientExample.java @@ -0,0 +1,12 @@ +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.URI; +import java.net.URISyntaxException; + +public class ProxyClientExample { + public static void main( String[] args ) throws URISyntaxException { + ExampleClient c = new ExampleClient( new URI( "ws://echo.websocket.org" ) ); + c.setProxy( new Proxy( Proxy.Type.HTTP, new InetSocketAddress( "proxyaddress", 80 ) ) ); + c.connect(); + } +} diff --git a/cb-tools/java-source/java-source/src/main/example/SSLClientExample.java b/cb-tools/java-source/java-source/src/main/example/SSLClientExample.java new file mode 100644 index 00000000..e740c9c5 --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/example/SSLClientExample.java @@ -0,0 +1,99 @@ +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStreamReader; +import java.net.URI; +import java.security.KeyStore; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManagerFactory; + +import org.java_websocket.WebSocketImpl; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.handshake.ServerHandshake; + +class WebSocketChatClient extends WebSocketClient { + + public WebSocketChatClient( URI serverUri ) { + super( serverUri ); + } + + @Override + public void onOpen( ServerHandshake handshakedata ) { + System.out.println( "Connected" ); + + } + + @Override + public void onMessage( String message ) { + System.out.println( "got: " + message ); + + } + + @Override + public void onClose( int code, String reason, boolean remote ) { + System.out.println( "Disconnected" ); + System.exit( 0 ); + + } + + @Override + public void onError( Exception ex ) { + ex.printStackTrace(); + + } + +} + +public class SSLClientExample { + + /* + * Keystore with certificate created like so (in JKS format): + * + *keytool -genkey -validity 3650 -keystore "keystore.jks" -storepass "storepassword" -keypass "keypassword" -alias "default" -dname "CN=127.0.0.1, OU=MyOrgUnit, O=MyOrg, L=MyCity, S=MyRegion, C=MyCountry" + */ + public static void main( String[] args ) throws Exception { + WebSocketImpl.DEBUG = true; + + WebSocketChatClient chatclient = new WebSocketChatClient( new URI( "wss://localhost:8887" ) ); + + // load up the key store + String STORETYPE = "JKS"; + String KEYSTORE = "keystore.jks"; + String STOREPASSWORD = "storepassword"; + String KEYPASSWORD = "keypassword"; + + KeyStore ks = KeyStore.getInstance( STORETYPE ); + File kf = new File( KEYSTORE ); + ks.load( new FileInputStream( kf ), STOREPASSWORD.toCharArray() ); + + KeyManagerFactory kmf = KeyManagerFactory.getInstance( "SunX509" ); + kmf.init( ks, KEYPASSWORD.toCharArray() ); + TrustManagerFactory tmf = TrustManagerFactory.getInstance( "SunX509" ); + tmf.init( ks ); + + SSLContext sslContext = null; + sslContext = SSLContext.getInstance( "TLS" ); + sslContext.init( kmf.getKeyManagers(), tmf.getTrustManagers(), null ); + // sslContext.init( null, null, null ); // will use java's default key and trust store which is sufficient unless you deal with self-signed certificates + + SSLSocketFactory factory = sslContext.getSocketFactory();// (SSLSocketFactory) SSLSocketFactory.getDefault(); + + chatclient.setSocket( factory.createSocket() ); + + chatclient.connectBlocking(); + + BufferedReader reader = new BufferedReader( new InputStreamReader( System.in ) ); + while ( true ) { + String line = reader.readLine(); + if( line.equals( "close" ) ) { + chatclient.close(); + } else { + chatclient.send( line ); + } + } + + } +} diff --git a/cb-tools/java-source/java-source/src/main/example/SSLServerExample.java b/cb-tools/java-source/java-source/src/main/example/SSLServerExample.java new file mode 100644 index 00000000..56339540 --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/example/SSLServerExample.java @@ -0,0 +1,50 @@ + + +import java.io.File; +import java.io.FileInputStream; +import java.security.KeyStore; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; + +import org.java_websocket.WebSocketImpl; +import org.java_websocket.server.DefaultSSLWebSocketServerFactory; + +public class SSLServerExample { + + /* + * Keystore with certificate created like so (in JKS format): + * + *keytool -genkey -validity 3650 -keystore "keystore.jks" -storepass "storepassword" -keypass "keypassword" -alias "default" -dname "CN=127.0.0.1, OU=MyOrgUnit, O=MyOrg, L=MyCity, S=MyRegion, C=MyCountry" + */ + public static void main( String[] args ) throws Exception { + WebSocketImpl.DEBUG = true; + + ChatServer chatserver = new ChatServer( 8887 ); // Firefox does allow multible ssl connection only via port 443 //tested on FF16 + + // load up the key store + String STORETYPE = "JKS"; + String KEYSTORE = "keystore.jks"; + String STOREPASSWORD = "storepassword"; + String KEYPASSWORD = "keypassword"; + + KeyStore ks = KeyStore.getInstance( STORETYPE ); + File kf = new File( KEYSTORE ); + ks.load( new FileInputStream( kf ), STOREPASSWORD.toCharArray() ); + + KeyManagerFactory kmf = KeyManagerFactory.getInstance( "SunX509" ); + kmf.init( ks, KEYPASSWORD.toCharArray() ); + TrustManagerFactory tmf = TrustManagerFactory.getInstance( "SunX509" ); + tmf.init( ks ); + + SSLContext sslContext = null; + sslContext = SSLContext.getInstance( "TLS" ); + sslContext.init( kmf.getKeyManagers(), tmf.getTrustManagers(), null ); + + chatserver.setWebSocketFactory( new DefaultSSLWebSocketServerFactory( sslContext ) ); + + chatserver.start(); + + } +} diff --git a/cb-tools/java-source/java-source/src/main/example/ServerStressTest.java b/cb-tools/java-source/java-source/src/main/example/ServerStressTest.java new file mode 100644 index 00000000..8fe2e97a --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/example/ServerStressTest.java @@ -0,0 +1,217 @@ +import java.awt.FlowLayout; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.channels.NotYetConnectedException; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; + +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSlider; +import javax.swing.JTextArea; +import javax.swing.JTextField; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.java_websocket.client.WebSocketClient; + +public class ServerStressTest extends JFrame { + private JSlider clients; + private JSlider interval; + private JSlider joinrate; + private JButton start, stop, reset; + private JLabel joinratelabel = new JLabel(); + private JLabel clientslabel = new JLabel(); + private JLabel intervallabel = new JLabel(); + private JTextField uriinput = new JTextField( "ws://localhost:8887" ); + private JTextArea text = new JTextArea( "payload" ); + private Timer timer = new Timer( true ); + private Thread adjustthread; + + private int notyetconnected = 0; + + public ServerStressTest() { + setTitle( "ServerStressTest" ); + setDefaultCloseOperation( EXIT_ON_CLOSE ); + start = new JButton( "Start" ); + start.addActionListener( new ActionListener() { + + @Override + public void actionPerformed( ActionEvent e ) { + start.setEnabled( false ); + stop.setEnabled( true ); + reset.setEnabled( false ); + interval.setEnabled( false ); + clients.setEnabled( false ); + + stopAdjust(); + adjustthread = new Thread( new Runnable() { + @Override + public void run() { + try { + adjust(); + } catch ( InterruptedException e ) { + System.out.println( "adjust chanced" ); + } + } + } ); + adjustthread.start(); + + } + } ); + stop = new JButton( "Stop" ); + stop.setEnabled( false ); + stop.addActionListener( new ActionListener() { + + @Override + public void actionPerformed( ActionEvent e ) { + timer.cancel(); + stopAdjust(); + start.setEnabled( true ); + stop.setEnabled( false ); + reset.setEnabled( true ); + joinrate.setEnabled( true ); + interval.setEnabled( true ); + clients.setEnabled( true ); + } + } ); + reset = new JButton( "reset" ); + reset.setEnabled( true ); + reset.addActionListener( new ActionListener() { + + @Override + public void actionPerformed( ActionEvent e ) { + while ( !websockets.isEmpty() ) + websockets.remove( 0 ).close(); + + } + } ); + joinrate = new JSlider( 0, 5000 ); + joinrate.addChangeListener( new ChangeListener() { + @Override + public void stateChanged( ChangeEvent e ) { + joinratelabel.setText( "Joinrate: " + joinrate.getValue() + " ms " ); + } + } ); + clients = new JSlider( 0, 10000 ); + clients.addChangeListener( new ChangeListener() { + + @Override + public void stateChanged( ChangeEvent e ) { + clientslabel.setText( "Clients: " + clients.getValue() ); + + } + } ); + interval = new JSlider( 0, 5000 ); + interval.addChangeListener( new ChangeListener() { + + @Override + public void stateChanged( ChangeEvent e ) { + intervallabel.setText( "Interval: " + interval.getValue() + " ms " ); + + } + } ); + + setSize( 300, 400 ); + setLayout( new GridLayout( 10, 1, 10, 10 ) ); + add( new JLabel( "URI" ) ); + add( uriinput ); + add( joinratelabel ); + add( joinrate ); + add( clientslabel ); + add( clients ); + add( intervallabel ); + add( interval ); + JPanel south = new JPanel( new FlowLayout( FlowLayout.CENTER ) ); + add( text ); + add( south ); + + south.add( start ); + south.add( stop ); + south.add( reset ); + + joinrate.setValue( 200 ); + interval.setValue( 1000 ); + clients.setValue( 1 ); + + } + + List<WebSocketClient> websockets = Collections.synchronizedList( new LinkedList<WebSocketClient>() ); + URI uri; + public void adjust() throws InterruptedException { + System.out.println( "Adjust" ); + try { + uri = new URI( uriinput.getText() ); + } catch ( URISyntaxException e ) { + e.printStackTrace(); + } + int totalclients = clients.getValue(); + while ( websockets.size() < totalclients ) { + WebSocketClient cl = new ExampleClient( uri ) { + @Override + public void onClose( int code, String reason, boolean remote ) { + System.out.println( "Closed duo " + code + " " + reason ); + clients.setValue( websockets.size() ); + websockets.remove( this ); + } + }; + + cl.connect(); + clients.setValue( websockets.size() ); + websockets.add( cl ); + Thread.sleep( joinrate.getValue() ); + } + while ( websockets.size() > clients.getValue() ) { + websockets.remove( 0 ).close(); + } + timer = new Timer( true ); + timer.scheduleAtFixedRate( new TimerTask() { + @Override + public void run() { + send(); + } + }, 0, interval.getValue() ); + + } + + public void stopAdjust() { + if( adjustthread != null ) { + adjustthread.interrupt(); + try { + adjustthread.join(); + } catch ( InterruptedException e ) { + e.printStackTrace(); + } + } + } + public void send() { + notyetconnected = 0; + String payload = text.getText(); + long time1 = System.currentTimeMillis(); + synchronized ( websockets ) { + for( WebSocketClient cl : websockets ) { + try { + cl.send( payload ); + } catch ( NotYetConnectedException e ) { + notyetconnected++; + } + } + } + System.out.println( websockets.size() + "/" + notyetconnected + " clients sent \"" + payload + "\"" + ( System.currentTimeMillis() - time1 ) ); + } + /** + * @param args + */ + public static void main( String[] args ) { + new ServerStressTest().setVisible( true ); + } + +} diff --git a/cb-tools/java-source/java-source/src/main/example/chat.html b/cb-tools/java-source/java-source/src/main/example/chat.html new file mode 100644 index 00000000..4a1327e8 --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/example/chat.html @@ -0,0 +1,66 @@ +<!DOCTYPE html> +<html> + <head> + <title>WebSocket Chat Client</title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> + + <script type="text/javascript" src="prototype.js"></script> + + <script type="text/javascript"> + document.observe("dom:loaded", function() { + function log(text) { + $("log").innerHTML = (new Date).getTime() + ": " + (!Object.isUndefined(text) && text !== null ? text.escapeHTML() : "null") + $("log").innerHTML; + } + + if (!window.WebSocket) { + alert("FATAL: WebSocket not natively supported. This demo will not work!"); + } + + var ws; + + $("uriForm").observe("submit", function(e) { + e.stop(); + ws = new WebSocket($F("uri")); + ws.onopen = function() { + log("[WebSocket#onopen]\n"); + } + ws.onmessage = function(e) { + log("[WebSocket#onmessage] Message: '" + e.data + "'\n"); + } + ws.onclose = function() { + log("[WebSocket#onclose]\n"); + $("uri", "connect").invoke("enable"); + $("disconnect").disable(); + ws = null; + } + $("uri", "connect").invoke("disable"); + $("disconnect").enable(); + }); + + $("sendForm").observe("submit", function(e) { + e.stop(); + if (ws) { + var textField = $("textField"); + ws.send(textField.value); + log("[WebSocket#send] Send: '" + textField.value + "'\n"); + textField.value = ""; + textField.focus(); + } + }); + + $("disconnect").observe("click", function(e) { + e.stop(); + if (ws) { + ws.close(); + ws = null; + } + }); + }); + </script> + </head> + <body> + <form id="uriForm"><input type="text" id="uri" value="ws://localhost:8887" style="width:200px;"> <input type="submit" id="connect" value="Connect"><input type="button" id="disconnect" value="Disconnect" disabled="disabled"></form><br> + <form id="sendForm"><input type="text" id="textField" value="" style="width:200px;"> <input type="submit" value="Send"></form><br> + <form><textarea id="log" rows="30" cols="100" style="font-family:monospace; color:red;"></textarea></form><br> + </body> +</html> diff --git a/cb-tools/java-source/java-source/src/main/example/prototype.js b/cb-tools/java-source/java-source/src/main/example/prototype.js new file mode 100644 index 00000000..9fe6e124 --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/example/prototype.js @@ -0,0 +1,4874 @@ +/* Prototype JavaScript framework, version 1.6.1 + * (c) 2005-2009 Sam Stephenson + * + * Prototype is freely distributable under the terms of an MIT-style license. + * For details, see the Prototype web site: http://www.prototypejs.org/ + * + *--------------------------------------------------------------------------*/ + +var Prototype = { + Version: '1.6.1', + + Browser: (function(){ + var ua = navigator.userAgent; + var isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]'; + return { + IE: !!window.attachEvent && !isOpera, + Opera: isOpera, + WebKit: ua.indexOf('AppleWebKit/') > -1, + Gecko: ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') === -1, + MobileSafari: /Apple.*Mobile.*Safari/.test(ua) + } + })(), + + BrowserFeatures: { + XPath: !!document.evaluate, + SelectorsAPI: !!document.querySelector, + ElementExtensions: (function() { + var constructor = window.Element || window.HTMLElement; + return !!(constructor && constructor.prototype); + })(), + SpecificElementExtensions: (function() { + if (typeof window.HTMLDivElement !== 'undefined') + return true; + + var div = document.createElement('div'); + var form = document.createElement('form'); + var isSupported = false; + + if (div['__proto__'] && (div['__proto__'] !== form['__proto__'])) { + isSupported = true; + } + + div = form = null; + + return isSupported; + })() + }, + + ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>', + JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/, + + emptyFunction: function() { }, + K: function(x) { return x } +}; + +if (Prototype.Browser.MobileSafari) + Prototype.BrowserFeatures.SpecificElementExtensions = false; + + +var Abstract = { }; + + +var Try = { + these: function() { + var returnValue; + + for (var i = 0, length = arguments.length; i < length; i++) { + var lambda = arguments[i]; + try { + returnValue = lambda(); + break; + } catch (e) { } + } + + return returnValue; + } +}; + +/* Based on Alex Arnell's inheritance implementation. */ + +var Class = (function() { + function subclass() {}; + function create() { + var parent = null, properties = $A(arguments); + if (Object.isFunction(properties[0])) + parent = properties.shift(); + + function klass() { + this.initialize.apply(this, arguments); + } + + Object.extend(klass, Class.Methods); + klass.superclass = parent; + klass.subclasses = []; + + if (parent) { + subclass.prototype = parent.prototype; + klass.prototype = new subclass; + parent.subclasses.push(klass); + } + + for (var i = 0; i < properties.length; i++) + klass.addMethods(properties[i]); + + if (!klass.prototype.initialize) + klass.prototype.initialize = Prototype.emptyFunction; + + klass.prototype.constructor = klass; + return klass; + } + + function addMethods(source) { + var ancestor = this.superclass && this.superclass.prototype; + var properties = Object.keys(source); + + if (!Object.keys({ toString: true }).length) { + if (source.toString != Object.prototype.toString) + properties.push("toString"); + if (source.valueOf != Object.prototype.valueOf) + properties.push("valueOf"); + } + + for (var i = 0, length = properties.length; i < length; i++) { + var property = properties[i], value = source[property]; + if (ancestor && Object.isFunction(value) && + value.argumentNames().first() == "$super") { + var method = value; + value = (function(m) { + return function() { return ancestor[m].apply(this, arguments); }; + })(property).wrap(method); + + value.valueOf = method.valueOf.bind(method); + value.toString = method.toString.bind(method); + } + this.prototype[property] = value; + } + + return this; + } + + return { + create: create, + Methods: { + addMethods: addMethods + } + }; +})(); +(function() { + + var _toString = Object.prototype.toString; + + function extend(destination, source) { + for (var property in source) + destination[property] = source[property]; + return destination; + } + + function inspect(object) { + try { + if (isUndefined(object)) return 'undefined'; + if (object === null) return 'null'; + return object.inspect ? object.inspect() : String(object); + } catch (e) { + if (e instanceof RangeError) return '...'; + throw e; + } + } + + function toJSON(object) { + var type = typeof object; + switch (type) { + case 'undefined': + case 'function': + case 'unknown': return; + case 'boolean': return object.toString(); + } + + if (object === null) return 'null'; + if (object.toJSON) return object.toJSON(); + if (isElement(object)) return; + + var results = []; + for (var property in object) { + var value = toJSON(object[property]); + if (!isUndefined(value)) + results.push(property.toJSON() + ': ' + value); + } + + return '{' + results.join(', ') + '}'; + } + + function toQueryString(object) { + return $H(object).toQueryString(); + } + + function toHTML(object) { + return object && object.toHTML ? object.toHTML() : String.interpret(object); + } + + function keys(object) { + var results = []; + for (var property in object) + results.push(property); + return results; + } + + function values(object) { + var results = []; + for (var property in object) + results.push(object[property]); + return results; + } + + function clone(object) { + return extend({ }, object); + } + + function isElement(object) { + return !!(object && object.nodeType == 1); + } + + function isArray(object) { + return _toString.call(object) == "[object Array]"; + } + + + function isHash(object) { + return object instanceof Hash; + } + + function isFunction(object) { + return typeof object === "function"; + } + + function isString(object) { + return _toString.call(object) == "[object String]"; + } + + function isNumber(object) { + return _toString.call(object) == "[object Number]"; + } + + function isUndefined(object) { + return typeof object === "undefined"; + } + + extend(Object, { + extend: extend, + inspect: inspect, + toJSON: toJSON, + toQueryString: toQueryString, + toHTML: toHTML, + keys: keys, + values: values, + clone: clone, + isElement: isElement, + isArray: isArray, + isHash: isHash, + isFunction: isFunction, + isString: isString, + isNumber: isNumber, + isUndefined: isUndefined + }); +})(); +Object.extend(Function.prototype, (function() { + var slice = Array.prototype.slice; + + function update(array, args) { + var arrayLength = array.length, length = args.length; + while (length--) array[arrayLength + length] = args[length]; + return array; + } + + function merge(array, args) { + array = slice.call(array, 0); + return update(array, args); + } + + function argumentNames() { + var names = this.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1] + .replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, '') + .replace(/\s+/g, '').split(','); + return names.length == 1 && !names[0] ? [] : names; + } + + function bind(context) { + if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this; + var __method = this, args = slice.call(arguments, 1); + return function() { + var a = merge(args, arguments); + return __method.apply(context, a); + } + } + + function bindAsEventListener(context) { + var __method = this, args = slice.call(arguments, 1); + return function(event) { + var a = update([event || window.event], args); + return __method.apply(context, a); + } + } + + function curry() { + if (!arguments.length) return this; + var __method = this, args = slice.call(arguments, 0); + return function() { + var a = merge(args, arguments); + return __method.apply(this, a); + } + } + + function delay(timeout) { + var __method = this, args = slice.call(arguments, 1); + timeout = timeout * 1000 + return window.setTimeout(function() { + return __method.apply(__method, args); + }, timeout); + } + + function defer() { + var args = update([0.01], arguments); + return this.delay.apply(this, args); + } + + function wrap(wrapper) { + var __method = this; + return function() { + var a = update([__method.bind(this)], arguments); + return wrapper.apply(this, a); + } + } + + function methodize() { + if (this._methodized) return this._methodized; + var __method = this; + return this._methodized = function() { + var a = update([this], arguments); + return __method.apply(null, a); + }; + } + + return { + argumentNames: argumentNames, + bind: bind, + bindAsEventListener: bindAsEventListener, + curry: curry, + delay: delay, + defer: defer, + wrap: wrap, + methodize: methodize + } +})()); + + +Date.prototype.toJSON = function() { + return '"' + this.getUTCFullYear() + '-' + + (this.getUTCMonth() + 1).toPaddedString(2) + '-' + + this.getUTCDate().toPaddedString(2) + 'T' + + this.getUTCHours().toPaddedString(2) + ':' + + this.getUTCMinutes().toPaddedString(2) + ':' + + this.getUTCSeconds().toPaddedString(2) + 'Z"'; +}; + + +RegExp.prototype.match = RegExp.prototype.test; + +RegExp.escape = function(str) { + return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); +}; +var PeriodicalExecuter = Class.create({ + initialize: function(callback, frequency) { + this.callback = callback; + this.frequency = frequency; + this.currentlyExecuting = false; + + this.registerCallback(); + }, + + registerCallback: function() { + this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + execute: function() { + this.callback(this); + }, + + stop: function() { + if (!this.timer) return; + clearInterval(this.timer); + this.timer = null; + }, + + onTimerEvent: function() { + if (!this.currentlyExecuting) { + try { + this.currentlyExecuting = true; + this.execute(); + this.currentlyExecuting = false; + } catch(e) { + this.currentlyExecuting = false; + throw e; + } + } + } +}); +Object.extend(String, { + interpret: function(value) { + return value == null ? '' : String(value); + }, + specialChar: { + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '\\': '\\\\' + } +}); + +Object.extend(String.prototype, (function() { + + function prepareReplacement(replacement) { + if (Object.isFunction(replacement)) return replacement; + var template = new Template(replacement); + return function(match) { return template.evaluate(match) }; + } + + function gsub(pattern, replacement) { + var result = '', source = this, match; + replacement = prepareReplacement(replacement); + + if (Object.isString(pattern)) + pattern = RegExp.escape(pattern); + + if (!(pattern.length || pattern.source)) { + replacement = replacement(''); + return replacement + source.split('').join(replacement) + replacement; + } + + while (source.length > 0) { + if (match = source.match(pattern)) { + result += source.slice(0, match.index); + result += String.interpret(replacement(match)); + source = source.slice(match.index + match[0].length); + } else { + result += source, source = ''; + } + } + return result; + } + + function sub(pattern, replacement, count) { + replacement = prepareReplacement(replacement); + count = Object.isUndefined(count) ? 1 : count; + + return this.gsub(pattern, function(match) { + if (--count < 0) return match[0]; + return replacement(match); + }); + } + + function scan(pattern, iterator) { + this.gsub(pattern, iterator); + return String(this); + } + + function truncate(length, truncation) { + length = length || 30; + truncation = Object.isUndefined(truncation) ? '...' : truncation; + return this.length > length ? + this.slice(0, length - truncation.length) + truncation : String(this); + } + + function strip() { + return this.replace(/^\s+/, '').replace(/\s+$/, ''); + } + + function stripTags() { + return this.replace(/<\w+(\s+("[^"]*"|'[^']*'|[^>])+)?>|<\/\w+>/gi, ''); + } + + function stripScripts() { + return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); + } + + function extractScripts() { + var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); + var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); + return (this.match(matchAll) || []).map(function(scriptTag) { + return (scriptTag.match(matchOne) || ['', ''])[1]; + }); + } + + function evalScripts() { + return this.extractScripts().map(function(script) { return eval(script) }); + } + + function escapeHTML() { + return this.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); + } + + function unescapeHTML() { + return this.stripTags().replace(/</g,'<').replace(/>/g,'>').replace(/&/g,'&'); + } + + + function toQueryParams(separator) { + var match = this.strip().match(/([^?#]*)(#.*)?$/); + if (!match) return { }; + + return match[1].split(separator || '&').inject({ }, function(hash, pair) { + if ((pair = pair.split('='))[0]) { + var key = decodeURIComponent(pair.shift()); + var value = pair.length > 1 ? pair.join('=') : pair[0]; + if (value != undefined) value = decodeURIComponent(value); + + if (key in hash) { + if (!Object.isArray(hash[key])) hash[key] = [hash[key]]; + hash[key].push(value); + } + else hash[key] = value; + } + return hash; + }); + } + + function toArray() { + return this.split(''); + } + + function succ() { + return this.slice(0, this.length - 1) + + String.fromCharCode(this.charCodeAt(this.length - 1) + 1); + } + + function times(count) { + return count < 1 ? '' : new Array(count + 1).join(this); + } + + function camelize() { + var parts = this.split('-'), len = parts.length; + if (len == 1) return parts[0]; + + var camelized = this.charAt(0) == '-' + ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1) + : parts[0]; + + for (var i = 1; i < len; i++) + camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1); + + return camelized; + } + + function capitalize() { + return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase(); + } + + function underscore() { + return this.replace(/::/g, '/') + .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2') + .replace(/([a-z\d])([A-Z])/g, '$1_$2') + .replace(/-/g, '_') + .toLowerCase(); + } + + function dasherize() { + return this.replace(/_/g, '-'); + } + + function inspect(useDoubleQuotes) { + var escapedString = this.replace(/[\x00-\x1f\\]/g, function(character) { + if (character in String.specialChar) { + return String.specialChar[character]; + } + return '\\u00' + character.charCodeAt().toPaddedString(2, 16); + }); + if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"'; + return "'" + escapedString.replace(/'/g, '\\\'') + "'"; + } + + function toJSON() { + return this.inspect(true); + } + + function unfilterJSON(filter) { + return this.replace(filter || Prototype.JSONFilter, '$1'); + } + + function isJSON() { + var str = this; + if (str.blank()) return false; + str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''); + return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str); + } + + function evalJSON(sanitize) { + var json = this.unfilterJSON(); + try { + if (!sanitize || json.isJSON()) return eval('(' + json + ')'); + } catch (e) { } + throw new SyntaxError('Badly formed JSON string: ' + this.inspect()); + } + + function include(pattern) { + return this.indexOf(pattern) > -1; + } + + function startsWith(pattern) { + return this.indexOf(pattern) === 0; + } + + function endsWith(pattern) { + var d = this.length - pattern.length; + return d >= 0 && this.lastIndexOf(pattern) === d; + } + + function empty() { + return this == ''; + } + + function blank() { + return /^\s*$/.test(this); + } + + function interpolate(object, pattern) { + return new Template(this, pattern).evaluate(object); + } + + return { + gsub: gsub, + sub: sub, + scan: scan, + truncate: truncate, + strip: String.prototype.trim ? String.prototype.trim : strip, + stripTags: stripTags, + stripScripts: stripScripts, + extractScripts: extractScripts, + evalScripts: evalScripts, + escapeHTML: escapeHTML, + unescapeHTML: unescapeHTML, + toQueryParams: toQueryParams, + parseQuery: toQueryParams, + toArray: toArray, + succ: succ, + times: times, + camelize: camelize, + capitalize: capitalize, + underscore: underscore, + dasherize: dasherize, + inspect: inspect, + toJSON: toJSON, + unfilterJSON: unfilterJSON, + isJSON: isJSON, + evalJSON: evalJSON, + include: include, + startsWith: startsWith, + endsWith: endsWith, + empty: empty, + blank: blank, + interpolate: interpolate + }; +})()); + +var Template = Class.create({ + initialize: function(template, pattern) { + this.template = template.toString(); + this.pattern = pattern || Template.Pattern; + }, + + evaluate: function(object) { + if (object && Object.isFunction(object.toTemplateReplacements)) + object = object.toTemplateReplacements(); + + return this.template.gsub(this.pattern, function(match) { + if (object == null) return (match[1] + ''); + + var before = match[1] || ''; + if (before == '\\') return match[2]; + + var ctx = object, expr = match[3]; + var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/; + match = pattern.exec(expr); + if (match == null) return before; + + while (match != null) { + var comp = match[1].startsWith('[') ? match[2].replace(/\\\\]/g, ']') : match[1]; + ctx = ctx[comp]; + if (null == ctx || '' == match[3]) break; + expr = expr.substring('[' == match[3] ? match[1].length : match[0].length); + match = pattern.exec(expr); + } + + return before + String.interpret(ctx); + }); + } +}); +Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; + +var $break = { }; + +var Enumerable = (function() { + function each(iterator, context) { + var index = 0; + try { + this._each(function(value) { + iterator.call(context, value, index++); + }); + } catch (e) { + if (e != $break) throw e; + } + return this; + } + + function eachSlice(number, iterator, context) { + var index = -number, slices = [], array = this.toArray(); + if (number < 1) return array; + while ((index += number) < array.length) + slices.push(array.slice(index, index+number)); + return slices.collect(iterator, context); + } + + function all(iterator, context) { + iterator = iterator || Prototype.K; + var result = true; + this.each(function(value, index) { + result = result && !!iterator.call(context, value, index); + if (!result) throw $break; + }); + return result; + } + + function any(iterator, context) { + iterator = iterator || Prototype.K; + var result = false; + this.each(function(value, index) { + if (result = !!iterator.call(context, value, index)) + throw $break; + }); + return result; + } + + function collect(iterator, context) { + iterator = iterator || Prototype.K; + var results = []; + this.each(function(value, index) { + results.push(iterator.call(context, value, index)); + }); + return results; + } + + function detect(iterator, context) { + var result; + this.each(function(value, index) { + if (iterator.call(context, value, index)) { + result = value; + throw $break; + } + }); + return result; + } + + function findAll(iterator, context) { + var results = []; + this.each(function(value, index) { + if (iterator.call(context, value, index)) + results.push(value); + }); + return results; + } + + function grep(filter, iterator, context) { + iterator = iterator || Prototype.K; + var results = []; + + if (Object.isString(filter)) + filter = new RegExp(RegExp.escape(filter)); + + this.each(function(value, index) { + if (filter.match(value)) + results.push(iterator.call(context, value, index)); + }); + return results; + } + + function include(object) { + if (Object.isFunction(this.indexOf)) + if (this.indexOf(object) != -1) return true; + + var found = false; + this.each(function(value) { + if (value == object) { + found = true; + throw $break; + } + }); + return found; + } + + function inGroupsOf(number, fillWith) { + fillWith = Object.isUndefined(fillWith) ? null : fillWith; + return this.eachSlice(number, function(slice) { + while(slice.length < number) slice.push(fillWith); + return slice; + }); + } + + function inject(memo, iterator, context) { + this.each(function(value, index) { + memo = iterator.call(context, memo, value, index); + }); + return memo; + } + + function invoke(method) { + var args = $A(arguments).slice(1); + return this.map(function(value) { + return value[method].apply(value, args); + }); + } + + function max(iterator, context) { + iterator = iterator || Prototype.K; + var result; + this.each(function(value, index) { + value = iterator.call(context, value, index); + if (result == null || value >= result) + result = value; + }); + return result; + } + + function min(iterator, context) { + iterator = iterator || Prototype.K; + var result; + this.each(function(value, index) { + value = iterator.call(context, value, index); + if (result == null || value < result) + result = value; + }); + return result; + } + + function partition(iterator, context) { + iterator = iterator || Prototype.K; + var trues = [], falses = []; + this.each(function(value, index) { + (iterator.call(context, value, index) ? + trues : falses).push(value); + }); + return [trues, falses]; + } + + function pluck(property) { + var results = []; + this.each(function(value) { + results.push(value[property]); + }); + return results; + } + + function reject(iterator, context) { + var results = []; + this.each(function(value, index) { + if (!iterator.call(context, value, index)) + results.push(value); + }); + return results; + } + + function sortBy(iterator, context) { + return this.map(function(value, index) { + return { + value: value, + criteria: iterator.call(context, value, index) + }; + }).sort(function(left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + }).pluck('value'); + } + + function toArray() { + return this.map(); + } + + function zip() { + var iterator = Prototype.K, args = $A(arguments); + if (Object.isFunction(args.last())) + iterator = args.pop(); + + var collections = [this].concat(args).map($A); + return this.map(function(value, index) { + return iterator(collections.pluck(index)); + }); + } + + function size() { + return this.toArray().length; + } + + function inspect() { + return '#<Enumerable:' + this.toArray().inspect() + '>'; + } + + + + + + + + + + return { + each: each, + eachSlice: eachSlice, + all: all, + every: all, + any: any, + some: any, + collect: collect, + map: collect, + detect: detect, + findAll: findAll, + select: findAll, + filter: findAll, + grep: grep, + include: include, + member: include, + inGroupsOf: inGroupsOf, + inject: inject, + invoke: invoke, + max: max, + min: min, + partition: partition, + pluck: pluck, + reject: reject, + sortBy: sortBy, + toArray: toArray, + entries: toArray, + zip: zip, + size: size, + inspect: inspect, + find: detect + }; +})(); +function $A(iterable) { + if (!iterable) return []; + if ('toArray' in Object(iterable)) return iterable.toArray(); + var length = iterable.length || 0, results = new Array(length); + while (length--) results[length] = iterable[length]; + return results; +} + +function $w(string) { + if (!Object.isString(string)) return []; + string = string.strip(); + return string ? string.split(/\s+/) : []; +} + +Array.from = $A; + + +(function() { + var arrayProto = Array.prototype, + slice = arrayProto.slice, + _each = arrayProto.forEach; // use native browser JS 1.6 implementation if available + + function each(iterator) { + for (var i = 0, length = this.length; i < length; i++) + iterator(this[i]); + } + if (!_each) _each = each; + + function clear() { + this.length = 0; + return this; + } + + function first() { + return this[0]; + } + + function last() { + return this[this.length - 1]; + } + + function compact() { + return this.select(function(value) { + return value != null; + }); + } + + function flatten() { + return this.inject([], function(array, value) { + if (Object.isArray(value)) + return array.concat(value.flatten()); + array.push(value); + return array; + }); + } + + function without() { + var values = slice.call(arguments, 0); + return this.select(function(value) { + return !values.include(value); + }); + } + + function reverse(inline) { + return (inline !== false ? this : this.toArray())._reverse(); + } + + function uniq(sorted) { + return this.inject([], function(array, value, index) { + if (0 == index || (sorted ? array.last() != value : !array.include(value))) + array.push(value); + return array; + }); + } + + function intersect(array) { + return this.uniq().findAll(function(item) { + return array.detect(function(value) { return item === value }); + }); + } + + + function clone() { + return slice.call(this, 0); + } + + function size() { + return this.length; + } + + function inspect() { + return '[' + this.map(Object.inspect).join(', ') + ']'; + } + + function toJSON() { + var results = []; + this.each(function(object) { + var value = Object.toJSON(object); + if (!Object.isUndefined(value)) results.push(value); + }); + return '[' + results.join(', ') + ']'; + } + + function indexOf(item, i) { + i || (i = 0); + var length = this.length; + if (i < 0) i = length + i; + for (; i < length; i++) + if (this[i] === item) return i; + return -1; + } + + function lastIndexOf(item, i) { + i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1; + var n = this.slice(0, i).reverse().indexOf(item); + return (n < 0) ? n : i - n - 1; + } + + function concat() { + var array = slice.call(this, 0), item; + for (var i = 0, length = arguments.length; i < length; i++) { + item = arguments[i]; + if (Object.isArray(item) && !('callee' in item)) { + for (var j = 0, arrayLength = item.length; j < arrayLength; j++) + array.push(item[j]); + } else { + array.push(item); + } + } + return array; + } + + Object.extend(arrayProto, Enumerable); + + if (!arrayProto._reverse) + arrayProto._reverse = arrayProto.reverse; + + Object.extend(arrayProto, { + _each: _each, + clear: clear, + first: first, + last: last, + compact: compact, + flatten: flatten, + without: without, + reverse: reverse, + uniq: uniq, + intersect: intersect, + clone: clone, + toArray: clone, + size: size, + inspect: inspect, + toJSON: toJSON + }); + + var CONCAT_ARGUMENTS_BUGGY = (function() { + return [].concat(arguments)[0][0] !== 1; + })(1,2) + + if (CONCAT_ARGUMENTS_BUGGY) arrayProto.concat = concat; + + if (!arrayProto.indexOf) arrayProto.indexOf = indexOf; + if (!arrayProto.lastIndexOf) arrayProto.lastIndexOf = lastIndexOf; +})(); +function $H(object) { + return new Hash(object); +}; + +var Hash = Class.create(Enumerable, (function() { + function initialize(object) { + this._object = Object.isHash(object) ? object.toObject() : Object.clone(object); + } + + function _each(iterator) { + for (var key in this._object) { + var value = this._object[key], pair = [key, value]; + pair.key = key; + pair.value = value; + iterator(pair); + } + } + + function set(key, value) { + return this._object[key] = value; + } + + function get(key) { + if (this._object[key] !== Object.prototype[key]) + return this._object[key]; + } + + function unset(key) { + var value = this._object[key]; + delete this._object[key]; + return value; + } + + function toObject() { + return Object.clone(this._object); + } + + function keys() { + return this.pluck('key'); + } + + function values() { + return this.pluck('value'); + } + + function index(value) { + var match = this.detect(function(pair) { + return pair.value === value; + }); + return match && match.key; + } + + function merge(object) { + return this.clone().update(object); + } + + function update(object) { + return new Hash(object).inject(this, function(result, pair) { + result.set(pair.key, pair.value); + return result; + }); + } + + function toQueryPair(key, value) { + if (Object.isUndefined(value)) return key; + return key + '=' + encodeURIComponent(String.interpret(value)); + } + + function toQueryString() { + return this.inject([], function(results, pair) { + var key = encodeURIComponent(pair.key), values = pair.value; + + if (values && typeof values == 'object') { + if (Object.isArray(values)) + return results.concat(values.map(toQueryPair.curry(key))); + } else results.push(toQueryPair(key, values)); + return results; + }).join('&'); + } + + function inspect() { + return '#<Hash:{' + this.map(function(pair) { + return pair.map(Object.inspect).join(': '); + }).join(', ') + '}>'; + } + + function toJSON() { + return Object.toJSON(this.toObject()); + } + + function clone() { + return new Hash(this); + } + + return { + initialize: initialize, + _each: _each, + set: set, + get: get, + unset: unset, + toObject: toObject, + toTemplateReplacements: toObject, + keys: keys, + values: values, + index: index, + merge: merge, + update: update, + toQueryString: toQueryString, + inspect: inspect, + toJSON: toJSON, + clone: clone + }; +})()); + +Hash.from = $H; +Object.extend(Number.prototype, (function() { + function toColorPart() { + return this.toPaddedString(2, 16); + } + + function succ() { + return this + 1; + } + + function times(iterator, context) { + $R(0, this, true).each(iterator, context); + return this; + } + + function toPaddedString(length, radix) { + var string = this.toString(radix || 10); + return '0'.times(length - string.length) + string; + } + + function toJSON() { + return isFinite(this) ? this.toString() : 'null'; + } + + function abs() { + return Math.abs(this); + } + + function round() { + return Math.round(this); + } + + function ceil() { + return Math.ceil(this); + } + + function floor() { + return Math.floor(this); + } + + return { + toColorPart: toColorPart, + succ: succ, + times: times, + toPaddedString: toPaddedString, + toJSON: toJSON, + abs: abs, + round: round, + ceil: ceil, + floor: floor + }; +})()); + +function $R(start, end, exclusive) { + return new ObjectRange(start, end, exclusive); +} + +var ObjectRange = Class.create(Enumerable, (function() { + function initialize(start, end, exclusive) { + this.start = start; + this.end = end; + this.exclusive = exclusive; + } + + function _each(iterator) { + var value = this.start; + while (this.include(value)) { + iterator(value); + value = value.succ(); + } + } + + function include(value) { + if (value < this.start) + return false; + if (this.exclusive) + return value < this.end; + return value <= this.end; + } + + return { + initialize: initialize, + _each: _each, + include: include + }; +})()); + + + +var Ajax = { + getTransport: function() { + return Try.these( + function() {return new XMLHttpRequest()}, + function() {return new ActiveXObject('Msxml2.XMLHTTP')}, + function() {return new ActiveXObject('Microsoft.XMLHTTP')} + ) || false; + }, + + activeRequestCount: 0 +}; + +Ajax.Responders = { + responders: [], + + _each: function(iterator) { + this.responders._each(iterator); + }, + + register: function(responder) { + if (!this.include(responder)) + this.responders.push(responder); + }, + + unregister: function(responder) { + this.responders = this.responders.without(responder); + }, + + dispatch: function(callback, request, transport, json) { + this.each(function(responder) { + if (Object.isFunction(responder[callback])) { + try { + responder[callback].apply(responder, [request, transport, json]); + } catch (e) { } + } + }); + } +}; + +Object.extend(Ajax.Responders, Enumerable); + +Ajax.Responders.register({ + onCreate: function() { Ajax.activeRequestCount++ }, + onComplete: function() { Ajax.activeRequestCount-- } +}); +Ajax.Base = Class.create({ + initialize: function(options) { + this.options = { + method: 'post', + asynchronous: true, + contentType: 'application/x-www-form-urlencoded', + encoding: 'UTF-8', + parameters: '', + evalJSON: true, + evalJS: true + }; + Object.extend(this.options, options || { }); + + this.options.method = this.options.method.toLowerCase(); + + if (Object.isString(this.options.parameters)) + this.options.parameters = this.options.parameters.toQueryParams(); + else if (Object.isHash(this.options.parameters)) + this.options.parameters = this.options.parameters.toObject(); + } +}); +Ajax.Request = Class.create(Ajax.Base, { + _complete: false, + + initialize: function($super, url, options) { + $super(options); + this.transport = Ajax.getTransport(); + this.request(url); + }, + + request: function(url) { + this.url = url; + this.method = this.options.method; + var params = Object.clone(this.options.parameters); + + if (!['get', 'post'].include(this.method)) { + params['_method'] = this.method; + this.method = 'post'; + } + + this.parameters = params; + + if (params = Object.toQueryString(params)) { + if (this.method == 'get') + this.url += (this.url.include('?') ? '&' : '?') + params; + else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) + params += '&_='; + } + + try { + var response = new Ajax.Response(this); + if (this.options.onCreate) this.options.onCreate(response); + Ajax.Responders.dispatch('onCreate', this, response); + + this.transport.open(this.method.toUpperCase(), this.url, + this.options.asynchronous); + + if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1); + + this.transport.onreadystatechange = this.onStateChange.bind(this); + this.setRequestHeaders(); + + this.body = this.method == 'post' ? (this.options.postBody || params) : null; + this.transport.send(this.body); + + /* Force Firefox to handle ready state 4 for synchronous requests */ + if (!this.options.asynchronous && this.transport.overrideMimeType) + this.onStateChange(); + + } + catch (e) { + this.dispatchException(e); + } + }, + + onStateChange: function() { + var readyState = this.transport.readyState; + if (readyState > 1 && !((readyState == 4) && this._complete)) + this.respondToReadyState(this.transport.readyState); + }, + + setRequestHeaders: function() { + var headers = { + 'X-Requested-With': 'XMLHttpRequest', + 'X-Prototype-Version': Prototype.Version, + 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' + }; + + if (this.method == 'post') { + headers['Content-type'] = this.options.contentType + + (this.options.encoding ? '; charset=' + this.options.encoding : ''); + + /* Force "Connection: close" for older Mozilla browsers to work + * around a bug where XMLHttpRequest sends an incorrect + * Content-length header. See Mozilla Bugzilla #246651. + */ + if (this.transport.overrideMimeType && + (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) + headers['Connection'] = 'close'; + } + + if (typeof this.options.requestHeaders == 'object') { + var extras = this.options.requestHeaders; + + if (Object.isFunction(extras.push)) + for (var i = 0, length = extras.length; i < length; i += 2) + headers[extras[i]] = extras[i+1]; + else + $H(extras).each(function(pair) { headers[pair.key] = pair.value }); + } + + for (var name in headers) + this.transport.setRequestHeader(name, headers[name]); + }, + + success: function() { + var status = this.getStatus(); + return !status || (status >= 200 && status < 300); + }, + + getStatus: function() { + try { + return this.transport.status || 0; + } catch (e) { return 0 } + }, + + respondToReadyState: function(readyState) { + var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this); + + if (state == 'Complete') { + try { + this._complete = true; + (this.options['on' + response.status] + || this.options['on' + (this.success() ? 'Success' : 'Failure')] + || Prototype.emptyFunction)(response, response.headerJSON); + } catch (e) { + this.dispatchException(e); + } + + var contentType = response.getHeader('Content-type'); + if (this.options.evalJS == 'force' + || (this.options.evalJS && this.isSameOrigin() && contentType + && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i))) + this.evalResponse(); + } + + try { + (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON); + Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON); + } catch (e) { + this.dispatchException(e); + } + + if (state == 'Complete') { + this.transport.onreadystatechange = Prototype.emptyFunction; + } + }, + + isSameOrigin: function() { + var m = this.url.match(/^\s*https?:\/\/[^\/]*/); + return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({ + protocol: location.protocol, + domain: document.domain, + port: location.port ? ':' + location.port : '' + })); + }, + + getHeader: function(name) { + try { + return this.transport.getResponseHeader(name) || null; + } catch (e) { return null; } + }, + + evalResponse: function() { + try { + return eval((this.transport.responseText || '').unfilterJSON()); + } catch (e) { + this.dispatchException(e); + } + }, + + dispatchException: function(exception) { + (this.options.onException || Prototype.emptyFunction)(this, exception); + Ajax.Responders.dispatch('onException', this, exception); + } +}); + +Ajax.Request.Events = + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; + + + + + + + + +Ajax.Response = Class.create({ + initialize: function(request){ + this.request = request; + var transport = this.transport = request.transport, + readyState = this.readyState = transport.readyState; + + if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) { + this.status = this.getStatus(); + this.statusText = this.getStatusText(); + this.responseText = String.interpret(transport.responseText); + this.headerJSON = this._getHeaderJSON(); + } + + if(readyState == 4) { + var xml = transport.responseXML; + this.responseXML = Object.isUndefined(xml) ? null : xml; + this.responseJSON = this._getResponseJSON(); + } + }, + + status: 0, + + statusText: '', + + getStatus: Ajax.Request.prototype.getStatus, + + getStatusText: function() { + try { + return this.transport.statusText || ''; + } catch (e) { return '' } + }, + + getHeader: Ajax.Request.prototype.getHeader, + + getAllHeaders: function() { + try { + return this.getAllResponseHeaders(); + } catch (e) { return null } + }, + + getResponseHeader: function(name) { + return this.transport.getResponseHeader(name); + }, + + getAllResponseHeaders: function() { + return this.transport.getAllResponseHeaders(); + }, + + _getHeaderJSON: function() { + var json = this.getHeader('X-JSON'); + if (!json) return null; + json = decodeURIComponent(escape(json)); + try { + return json.evalJSON(this.request.options.sanitizeJSON || + !this.request.isSameOrigin()); + } catch (e) { + this.request.dispatchException(e); + } + }, + + _getResponseJSON: function() { + var options = this.request.options; + if (!options.evalJSON || (options.evalJSON != 'force' && + !(this.getHeader('Content-type') || '').include('application/json')) || + this.responseText.blank()) + return null; + try { + return this.responseText.evalJSON(options.sanitizeJSON || + !this.request.isSameOrigin()); + } catch (e) { + this.request.dispatchException(e); + } + } +}); + +Ajax.Updater = Class.create(Ajax.Request, { + initialize: function($super, container, url, options) { + this.container = { + success: (container.success || container), + failure: (container.failure || (container.success ? null : container)) + }; + + options = Object.clone(options); + var onComplete = options.onComplete; + options.onComplete = (function(response, json) { + this.updateContent(response.responseText); + if (Object.isFunction(onComplete)) onComplete(response, json); + }).bind(this); + + $super(url, options); + }, + + updateContent: function(responseText) { + var receiver = this.container[this.success() ? 'success' : 'failure'], + options = this.options; + + if (!options.evalScripts) responseText = responseText.stripScripts(); + + if (receiver = $(receiver)) { + if (options.insertion) { + if (Object.isString(options.insertion)) { + var insertion = { }; insertion[options.insertion] = responseText; + receiver.insert(insertion); + } + else options.insertion(receiver, responseText); + } + else receiver.update(responseText); + } + } +}); + +Ajax.PeriodicalUpdater = Class.create(Ajax.Base, { + initialize: function($super, container, url, options) { + $super(options); + this.onComplete = this.options.onComplete; + + this.frequency = (this.options.frequency || 2); + this.decay = (this.options.decay || 1); + + this.updater = { }; + this.container = container; + this.url = url; + + this.start(); + }, + + start: function() { + this.options.onComplete = this.updateComplete.bind(this); + this.onTimerEvent(); + }, + + stop: function() { + this.updater.options.onComplete = undefined; + clearTimeout(this.timer); + (this.onComplete || Prototype.emptyFunction).apply(this, arguments); + }, + + updateComplete: function(response) { + if (this.options.decay) { + this.decay = (response.responseText == this.lastText ? + this.decay * this.options.decay : 1); + + this.lastText = response.responseText; + } + this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency); + }, + + onTimerEvent: function() { + this.updater = new Ajax.Updater(this.container, this.url, this.options); + } +}); + + + +function $(element) { + if (arguments.length > 1) { + for (var i = 0, elements = [], length = arguments.length; i < length; i++) + elements.push($(arguments[i])); + return elements; + } + if (Object.isString(element)) + element = document.getElementById(element); + return Element.extend(element); +} + +if (Prototype.BrowserFeatures.XPath) { + document._getElementsByXPath = function(expression, parentElement) { + var results = []; + var query = document.evaluate(expression, $(parentElement) || document, + null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + for (var i = 0, length = query.snapshotLength; i < length; i++) + results.push(Element.extend(query.snapshotItem(i))); + return results; + }; +} + +/*--------------------------------------------------------------------------*/ + +if (!window.Node) var Node = { }; + +if (!Node.ELEMENT_NODE) { + Object.extend(Node, { + ELEMENT_NODE: 1, + ATTRIBUTE_NODE: 2, + TEXT_NODE: 3, + CDATA_SECTION_NODE: 4, + ENTITY_REFERENCE_NODE: 5, + ENTITY_NODE: 6, + PROCESSING_INSTRUCTION_NODE: 7, + COMMENT_NODE: 8, + DOCUMENT_NODE: 9, + DOCUMENT_TYPE_NODE: 10, + DOCUMENT_FRAGMENT_NODE: 11, + NOTATION_NODE: 12 + }); +} + + +(function(global) { + + var SETATTRIBUTE_IGNORES_NAME = (function(){ + var elForm = document.createElement("form"); + var elInput = document.createElement("input"); + var root = document.documentElement; + elInput.setAttribute("name", "test"); + elForm.appendChild(elInput); + root.appendChild(elForm); + var isBuggy = elForm.elements + ? (typeof elForm.elements.test == "undefined") + : null; + root.removeChild(elForm); + elForm = elInput = null; + return isBuggy; + })(); + + var element = global.Element; + global.Element = function(tagName, attributes) { + attributes = attributes || { }; + tagName = tagName.toLowerCase(); + var cache = Element.cache; + if (SETATTRIBUTE_IGNORES_NAME && attributes.name) { + tagName = '<' + tagName + ' name="' + attributes.name + '">'; + delete attributes.name; + return Element.writeAttribute(document.createElement(tagName), attributes); + } + if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName)); + return Element.writeAttribute(cache[tagName].cloneNode(false), attributes); + }; + Object.extend(global.Element, element || { }); + if (element) global.Element.prototype = element.prototype; +})(this); + +Element.cache = { }; +Element.idCounter = 1; + +Element.Methods = { + visible: function(element) { + return $(element).style.display != 'none'; + }, + + toggle: function(element) { + element = $(element); + Element[Element.visible(element) ? 'hide' : 'show'](element); + return element; + }, + + + hide: function(element) { + element = $(element); + element.style.display = 'none'; + return element; + }, + + show: function(element) { + element = $(element); + element.style.display = ''; + return element; + }, + + remove: function(element) { + element = $(element); + element.parentNode.removeChild(element); + return element; + }, + + update: (function(){ + + var SELECT_ELEMENT_INNERHTML_BUGGY = (function(){ + var el = document.createElement("select"), + isBuggy = true; + el.innerHTML = "<option value=\"test\">test</option>"; + if (el.options && el.options[0]) { + isBuggy = el.options[0].nodeName.toUpperCase() !== "OPTION"; + } + el = null; + return isBuggy; + })(); + + var TABLE_ELEMENT_INNERHTML_BUGGY = (function(){ + try { + var el = document.createElement("table"); + if (el && el.tBodies) { + el.innerHTML = "<tbody><tr><td>test</td></tr></tbody>"; + var isBuggy = typeof el.tBodies[0] == "undefined"; + el = null; + return isBuggy; + } + } catch (e) { + return true; + } + })(); + + var SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING = (function () { + var s = document.createElement("script"), + isBuggy = false; + try { + s.appendChild(document.createTextNode("")); + isBuggy = !s.firstChild || + s.firstChild && s.firstChild.nodeType !== 3; + } catch (e) { + isBuggy = true; + } + s = null; + return isBuggy; + })(); + + function update(element, content) { + element = $(element); + + if (content && content.toElement) + content = content.toElement(); + + if (Object.isElement(content)) + return element.update().insert(content); + + content = Object.toHTML(content); + + var tagName = element.tagName.toUpperCase(); + + if (tagName === 'SCRIPT' && SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING) { + element.text = content; + return element; + } + + if (SELECT_ELEMENT_INNERHTML_BUGGY || TABLE_ELEMENT_INNERHTML_BUGGY) { + if (tagName in Element._insertionTranslations.tags) { + while (element.firstChild) { + element.removeChild(element.firstChild); + } + Element._getContentFromAnonymousElement(tagName, content.stripScripts()) + .each(function(node) { + element.appendChild(node) + }); + } + else { + element.innerHTML = content.stripScripts(); + } + } + else { + element.innerHTML = content.stripScripts(); + } + + content.evalScripts.bind(content).defer(); + return element; + } + + return update; + })(), + + replace: function(element, content) { + element = $(element); + if (content && content.toElement) content = content.toElement(); + else if (!Object.isElement(content)) { + content = Object.toHTML(content); + var range = element.ownerDocument.createRange(); + range.selectNode(element); + content.evalScripts.bind(content).defer(); + content = range.createContextualFragment(content.stripScripts()); + } + element.parentNode.replaceChild(content, element); + return element; + }, + + insert: function(element, insertions) { + element = $(element); + + if (Object.isString(insertions) || Object.isNumber(insertions) || + Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML))) + insertions = {bottom:insertions}; + + var content, insert, tagName, childNodes; + + for (var position in insertions) { + content = insertions[position]; + position = position.toLowerCase(); + insert = Element._insertionTranslations[position]; + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) { + insert(element, content); + continue; + } + + content = Object.toHTML(content); + + tagName = ((position == 'before' || position == 'after') + ? element.parentNode : element).tagName.toUpperCase(); + + childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); + + if (position == 'top' || position == 'after') childNodes.reverse(); + childNodes.each(insert.curry(element)); + + content.evalScripts.bind(content).defer(); + } + + return element; + }, + + wrap: function(element, wrapper, attributes) { + element = $(element); + if (Object.isElement(wrapper)) + $(wrapper).writeAttribute(attributes || { }); + else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes); + else wrapper = new Element('div', wrapper); + if (element.parentNode) + element.parentNode.replaceChild(wrapper, element); + wrapper.appendChild(element); + return wrapper; + }, + + inspect: function(element) { + element = $(element); + var result = '<' + element.tagName.toLowerCase(); + $H({'id': 'id', 'className': 'class'}).each(function(pair) { + var property = pair.first(), attribute = pair.last(); + var value = (element[property] || '').toString(); + if (value) result += ' ' + attribute + '=' + value.inspect(true); + }); + return result + '>'; + }, + + recursivelyCollect: function(element, property) { + element = $(element); + var elements = []; + while (element = element[property]) + if (element.nodeType == 1) + elements.push(Element.extend(element)); + return elements; + }, + + ancestors: function(element) { + return Element.recursivelyCollect(element, 'parentNode'); + }, + + descendants: function(element) { + return Element.select(element, "*"); + }, + + firstDescendant: function(element) { + element = $(element).firstChild; + while (element && element.nodeType != 1) element = element.nextSibling; + return $(element); + }, + + immediateDescendants: function(element) { + if (!(element = $(element).firstChild)) return []; + while (element && element.nodeType != 1) element = element.nextSibling; + if (element) return [element].concat($(element).nextSiblings()); + return []; + }, + + previousSiblings: function(element) { + return Element.recursivelyCollect(element, 'previousSibling'); + }, + + nextSiblings: function(element) { + return Element.recursivelyCollect(element, 'nextSibling'); + }, + + siblings: function(element) { + element = $(element); + return Element.previousSiblings(element).reverse() + .concat(Element.nextSiblings(element)); + }, + + match: function(element, selector) { + if (Object.isString(selector)) + selector = new Selector(selector); + return selector.match($(element)); + }, + + up: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return $(element.parentNode); + var ancestors = Element.ancestors(element); + return Object.isNumber(expression) ? ancestors[expression] : + Selector.findElement(ancestors, expression, index); + }, + + down: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return Element.firstDescendant(element); + return Object.isNumber(expression) ? Element.descendants(element)[expression] : + Element.select(element, expression)[index || 0]; + }, + + previous: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element)); + var previousSiblings = Element.previousSiblings(element); + return Object.isNumber(expression) ? previousSiblings[expression] : + Selector.findElement(previousSiblings, expression, index); + }, + + next: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element)); + var nextSiblings = Element.nextSiblings(element); + return Object.isNumber(expression) ? nextSiblings[expression] : + Selector.findElement(nextSiblings, expression, index); + }, + + + select: function(element) { + var args = Array.prototype.slice.call(arguments, 1); + return Selector.findChildElements(element, args); + }, + + adjacent: function(element) { + var args = Array.prototype.slice.call(arguments, 1); + return Selector.findChildElements(element.parentNode, args).without(element); + }, + + identify: function(element) { + element = $(element); + var id = Element.readAttribute(element, 'id'); + if (id) return id; + do { id = 'anonymous_element_' + Element.idCounter++ } while ($(id)); + Element.writeAttribute(element, 'id', id); + return id; + }, + + readAttribute: function(element, name) { + element = $(element); + if (Prototype.Browser.IE) { + var t = Element._attributeTranslations.read; + if (t.values[name]) return t.values[name](element, name); + if (t.names[name]) name = t.names[name]; + if (name.include(':')) { + return (!element.attributes || !element.attributes[name]) ? null : + element.attributes[name].value; + } + } + return element.getAttribute(name); + }, + + writeAttribute: function(element, name, value) { + element = $(element); + var attributes = { }, t = Element._attributeTranslations.write; + + if (typeof name == 'object') attributes = name; + else attributes[name] = Object.isUndefined(value) ? true : value; + + for (var attr in attributes) { + name = t.names[attr] || attr; + value = attributes[attr]; + if (t.values[attr]) name = t.values[attr](element, value); + if (value === false || value === null) + element.removeAttribute(name); + else if (value === true) + element.setAttribute(name, name); + else element.setAttribute(name, value); + } + return element; + }, + + getHeight: function(element) { + return Element.getDimensions(element).height; + }, + + getWidth: function(element) { + return Element.getDimensions(element).width; + }, + + classNames: function(element) { + return new Element.ClassNames(element); + }, + + hasClassName: function(element, className) { + if (!(element = $(element))) return; + var elementClassName = element.className; + return (elementClassName.length > 0 && (elementClassName == className || + new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName))); + }, + + addClassName: function(element, className) { + if (!(element = $(element))) return; + if (!Element.hasClassName(element, className)) + element.className += (element.className ? ' ' : '') + className; + return element; + }, + + removeClassName: function(element, className) { + if (!(element = $(element))) return; + element.className = element.className.replace( + new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip(); + return element; + }, + + toggleClassName: function(element, className) { + if (!(element = $(element))) return; + return Element[Element.hasClassName(element, className) ? + 'removeClassName' : 'addClassName'](element, className); + }, + + cleanWhitespace: function(element) { + element = $(element); + var node = element.firstChild; + while (node) { + var nextNode = node.nextSibling; + if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) + element.removeChild(node); + node = nextNode; + } + return element; + }, + + empty: function(element) { + return $(element).innerHTML.blank(); + }, + + descendantOf: function(element, ancestor) { + element = $(element), ancestor = $(ancestor); + + if (element.compareDocumentPosition) + return (element.compareDocumentPosition(ancestor) & 8) === 8; + + if (ancestor.contains) + return ancestor.contains(element) && ancestor !== element; + + while (element = element.parentNode) + if (element == ancestor) return true; + + return false; + }, + + scrollTo: function(element) { + element = $(element); + var pos = Element.cumulativeOffset(element); + window.scrollTo(pos[0], pos[1]); + return element; + }, + + getStyle: function(element, style) { + element = $(element); + style = style == 'float' ? 'cssFloat' : style.camelize(); + var value = element.style[style]; + if (!value || value == 'auto') { + var css = document.defaultView.getComputedStyle(element, null); + value = css ? css[style] : null; + } + if (style == 'opacity') return value ? parseFloat(value) : 1.0; + return value == 'auto' ? null : value; + }, + + getOpacity: function(element) { + return $(element).getStyle('opacity'); + }, + + setStyle: function(element, styles) { + element = $(element); + var elementStyle = element.style, match; + if (Object.isString(styles)) { + element.style.cssText += ';' + styles; + return styles.include('opacity') ? + element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element; + } + for (var property in styles) + if (property == 'opacity') element.setOpacity(styles[property]); + else + elementStyle[(property == 'float' || property == 'cssFloat') ? + (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') : + property] = styles[property]; + + return element; + }, + + setOpacity: function(element, value) { + element = $(element); + element.style.opacity = (value == 1 || value === '') ? '' : + (value < 0.00001) ? 0 : value; + return element; + }, + + getDimensions: function(element) { + element = $(element); + var display = Element.getStyle(element, 'display'); + if (display != 'none' && display != null) // Safari bug + return {width: element.offsetWidth, height: element.offsetHeight}; + + var els = element.style; + var originalVisibility = els.visibility; + var originalPosition = els.position; + var originalDisplay = els.display; + els.visibility = 'hidden'; + if (originalPosition != 'fixed') // Switching fixed to absolute causes issues in Safari + els.position = 'absolute'; + els.display = 'block'; + var originalWidth = element.clientWidth; + var originalHeight = element.clientHeight; + els.display = originalDisplay; + els.position = originalPosition; + els.visibility = originalVisibility; + return {width: originalWidth, height: originalHeight}; + }, + + makePositioned: function(element) { + element = $(element); + var pos = Element.getStyle(element, 'position'); + if (pos == 'static' || !pos) { + element._madePositioned = true; + element.style.position = 'relative'; + if (Prototype.Browser.Opera) { + element.style.top = 0; + element.style.left = 0; + } + } + return element; + }, + + undoPositioned: function(element) { + element = $(element); + if (element._madePositioned) { + element._madePositioned = undefined; + element.style.position = + element.style.top = + element.style.left = + element.style.bottom = + element.style.right = ''; + } + return element; + }, + + makeClipping: function(element) { + element = $(element); + if (element._overflow) return element; + element._overflow = Element.getStyle(element, 'overflow') || 'auto'; + if (element._overflow !== 'hidden') + element.style.overflow = 'hidden'; + return element; + }, + + undoClipping: function(element) { + element = $(element); + if (!element._overflow) return element; + element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; + element._overflow = null; + return element; + }, + + cumulativeOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + positionedOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + if (element.tagName.toUpperCase() == 'BODY') break; + var p = Element.getStyle(element, 'position'); + if (p !== 'static') break; + } + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + absolutize: function(element) { + element = $(element); + if (Element.getStyle(element, 'position') == 'absolute') return element; + + var offsets = Element.positionedOffset(element); + var top = offsets[1]; + var left = offsets[0]; + var width = element.clientWidth; + var height = element.clientHeight; + + element._originalLeft = left - parseFloat(element.style.left || 0); + element._originalTop = top - parseFloat(element.style.top || 0); + element._originalWidth = element.style.width; + element._originalHeight = element.style.height; + + element.style.position = 'absolute'; + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.width = width + 'px'; + element.style.height = height + 'px'; + return element; + }, + + relativize: function(element) { + element = $(element); + if (Element.getStyle(element, 'position') == 'relative') return element; + + element.style.position = 'relative'; + var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); + var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); + + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.height = element._originalHeight; + element.style.width = element._originalWidth; + return element; + }, + + cumulativeScrollOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + getOffsetParent: function(element) { + if (element.offsetParent) return $(element.offsetParent); + if (element == document.body) return $(element); + + while ((element = element.parentNode) && element != document.body) + if (Element.getStyle(element, 'position') != 'static') + return $(element); + + return $(document.body); + }, + + viewportOffset: function(forElement) { + var valueT = 0, valueL = 0; + + var element = forElement; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + + if (element.offsetParent == document.body && + Element.getStyle(element, 'position') == 'absolute') break; + + } while (element = element.offsetParent); + + element = forElement; + do { + if (!Prototype.Browser.Opera || (element.tagName && (element.tagName.toUpperCase() == 'BODY'))) { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; + } + } while (element = element.parentNode); + + return Element._returnOffset(valueL, valueT); + }, + + clonePosition: function(element, source) { + var options = Object.extend({ + setLeft: true, + setTop: true, + setWidth: true, + setHeight: true, + offsetTop: 0, + offsetLeft: 0 + }, arguments[2] || { }); + + source = $(source); + var p = Element.viewportOffset(source); + + element = $(element); + var delta = [0, 0]; + var parent = null; + if (Element.getStyle(element, 'position') == 'absolute') { + parent = Element.getOffsetParent(element); + delta = Element.viewportOffset(parent); + } + + if (parent == document.body) { + delta[0] -= document.body.offsetLeft; + delta[1] -= document.body.offsetTop; + } + + if (options.setLeft) element.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; + if (options.setTop) element.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; + if (options.setWidth) element.style.width = source.offsetWidth + 'px'; + if (options.setHeight) element.style.height = source.offsetHeight + 'px'; + return element; + } +}; + +Object.extend(Element.Methods, { + getElementsBySelector: Element.Methods.select, + + childElements: Element.Methods.immediateDescendants +}); + +Element._attributeTranslations = { + write: { + names: { + className: 'class', + htmlFor: 'for' + }, + values: { } + } +}; + +if (Prototype.Browser.Opera) { + Element.Methods.getStyle = Element.Methods.getStyle.wrap( + function(proceed, element, style) { + switch (style) { + case 'left': case 'top': case 'right': case 'bottom': + if (proceed(element, 'position') === 'static') return null; + case 'height': case 'width': + if (!Element.visible(element)) return null; + + var dim = parseInt(proceed(element, style), 10); + + if (dim !== element['offset' + style.capitalize()]) + return dim + 'px'; + + var properties; + if (style === 'height') { + properties = ['border-top-width', 'padding-top', + 'padding-bottom', 'border-bottom-width']; + } + else { + properties = ['border-left-width', 'padding-left', + 'padding-right', 'border-right-width']; + } + return properties.inject(dim, function(memo, property) { + var val = proceed(element, property); + return val === null ? memo : memo - parseInt(val, 10); + }) + 'px'; + default: return proceed(element, style); + } + } + ); + + Element.Methods.readAttribute = Element.Methods.readAttribute.wrap( + function(proceed, element, attribute) { + if (attribute === 'title') return element.title; + return proceed(element, attribute); + } + ); +} + +else if (Prototype.Browser.IE) { + Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap( + function(proceed, element) { + element = $(element); + try { element.offsetParent } + catch(e) { return $(document.body) } + var position = element.getStyle('position'); + if (position !== 'static') return proceed(element); + element.setStyle({ position: 'relative' }); + var value = proceed(element); + element.setStyle({ position: position }); + return value; + } + ); + + $w('positionedOffset viewportOffset').each(function(method) { + Element.Methods[method] = Element.Methods[method].wrap( + function(proceed, element) { + element = $(element); + try { element.offsetParent } + catch(e) { return Element._returnOffset(0,0) } + var position = element.getStyle('position'); + if (position !== 'static') return proceed(element); + var offsetParent = element.getOffsetParent(); + if (offsetParent && offsetParent.getStyle('position') === 'fixed') + offsetParent.setStyle({ zoom: 1 }); + element.setStyle({ position: 'relative' }); + var value = proceed(element); + element.setStyle({ position: position }); + return value; + } + ); + }); + + Element.Methods.cumulativeOffset = Element.Methods.cumulativeOffset.wrap( + function(proceed, element) { + try { element.offsetParent } + catch(e) { return Element._returnOffset(0,0) } + return proceed(element); + } + ); + + Element.Methods.getStyle = function(element, style) { + element = $(element); + style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize(); + var value = element.style[style]; + if (!value && element.currentStyle) value = element.currentStyle[style]; + + if (style == 'opacity') { + if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) + if (value[1]) return parseFloat(value[1]) / 100; + return 1.0; + } + + if (value == 'auto') { + if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none')) + return element['offset' + style.capitalize()] + 'px'; + return null; + } + return value; + }; + + Element.Methods.setOpacity = function(element, value) { + function stripAlpha(filter){ + return filter.replace(/alpha\([^\)]*\)/gi,''); + } + element = $(element); + var currentStyle = element.currentStyle; + if ((currentStyle && !currentStyle.hasLayout) || + (!currentStyle && element.style.zoom == 'normal')) + element.style.zoom = 1; + + var filter = element.getStyle('filter'), style = element.style; + if (value == 1 || value === '') { + (filter = stripAlpha(filter)) ? + style.filter = filter : style.removeAttribute('filter'); + return element; + } else if (value < 0.00001) value = 0; + style.filter = stripAlpha(filter) + + 'alpha(opacity=' + (value * 100) + ')'; + return element; + }; + + Element._attributeTranslations = (function(){ + + var classProp = 'className'; + var forProp = 'for'; + + var el = document.createElement('div'); + + el.setAttribute(classProp, 'x'); + + if (el.className !== 'x') { + el.setAttribute('class', 'x'); + if (el.className === 'x') { + classProp = 'class'; + } + } + el = null; + + el = document.createElement('label'); + el.setAttribute(forProp, 'x'); + if (el.htmlFor !== 'x') { + el.setAttribute('htmlFor', 'x'); + if (el.htmlFor === 'x') { + forProp = 'htmlFor'; + } + } + el = null; + + return { + read: { + names: { + 'class': classProp, + 'className': classProp, + 'for': forProp, + 'htmlFor': forProp + }, + values: { + _getAttr: function(element, attribute) { + return element.getAttribute(attribute); + }, + _getAttr2: function(element, attribute) { + return element.getAttribute(attribute, 2); + }, + _getAttrNode: function(element, attribute) { + var node = element.getAttributeNode(attribute); + return node ? node.value : ""; + }, + _getEv: (function(){ + + var el = document.createElement('div'); + el.onclick = Prototype.emptyFunction; + var value = el.getAttribute('onclick'); + var f; + + if (String(value).indexOf('{') > -1) { + f = function(element, attribute) { + attribute = element.getAttribute(attribute); + if (!attribute) return null; + attribute = attribute.toString(); + attribute = attribute.split('{')[1]; + attribute = attribute.split('}')[0]; + return attribute.strip(); + }; + } + else if (value === '') { + f = function(element, attribute) { + attribute = element.getAttribute(attribute); + if (!attribute) return null; + return attribute.strip(); + }; + } + el = null; + return f; + })(), + _flag: function(element, attribute) { + return $(element).hasAttribute(attribute) ? attribute : null; + }, + style: function(element) { + return element.style.cssText.toLowerCase(); + }, + title: function(element) { + return element.title; + } + } + } + } + })(); + + Element._attributeTranslations.write = { + names: Object.extend({ + cellpadding: 'cellPadding', + cellspacing: 'cellSpacing' + }, Element._attributeTranslations.read.names), + values: { + checked: function(element, value) { + element.checked = !!value; + }, + + style: function(element, value) { + element.style.cssText = value ? value : ''; + } + } + }; + + Element._attributeTranslations.has = {}; + + $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' + + 'encType maxLength readOnly longDesc frameBorder').each(function(attr) { + Element._attributeTranslations.write.names[attr.toLowerCase()] = attr; + Element._attributeTranslations.has[attr.toLowerCase()] = attr; + }); + + (function(v) { + Object.extend(v, { + href: v._getAttr2, + src: v._getAttr2, + type: v._getAttr, + action: v._getAttrNode, + disabled: v._flag, + checked: v._flag, + readonly: v._flag, + multiple: v._flag, + onload: v._getEv, + onunload: v._getEv, + onclick: v._getEv, + ondblclick: v._getEv, + onmousedown: v._getEv, + onmouseup: v._getEv, + onmouseover: v._getEv, + onmousemove: v._getEv, + onmouseout: v._getEv, + onfocus: v._getEv, + onblur: v._getEv, + onkeypress: v._getEv, + onkeydown: v._getEv, + onkeyup: v._getEv, + onsubmit: v._getEv, + onreset: v._getEv, + onselect: v._getEv, + onchange: v._getEv + }); + })(Element._attributeTranslations.read.values); + + if (Prototype.BrowserFeatures.ElementExtensions) { + (function() { + function _descendants(element) { + var nodes = element.getElementsByTagName('*'), results = []; + for (var i = 0, node; node = nodes[i]; i++) + if (node.tagName !== "!") // Filter out comment nodes. + results.push(node); + return results; + } + + Element.Methods.down = function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return element.firstDescendant(); + return Object.isNumber(expression) ? _descendants(element)[expression] : + Element.select(element, expression)[index || 0]; + } + })(); + } + +} + +else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) { + Element.Methods.setOpacity = function(element, value) { + element = $(element); + element.style.opacity = (value == 1) ? 0.999999 : + (value === '') ? '' : (value < 0.00001) ? 0 : value; + return element; + }; +} + +else if (Prototype.Browser.WebKit) { + Element.Methods.setOpacity = function(element, value) { + element = $(element); + element.style.opacity = (value == 1 || value === '') ? '' : + (value < 0.00001) ? 0 : value; + + if (value == 1) + if(element.tagName.toUpperCase() == 'IMG' && element.width) { + element.width++; element.width--; + } else try { + var n = document.createTextNode(' '); + element.appendChild(n); + element.removeChild(n); + } catch (e) { } + + return element; + }; + + Element.Methods.cumulativeOffset = function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == document.body) + if (Element.getStyle(element, 'position') == 'absolute') break; + + element = element.offsetParent; + } while (element); + + return Element._returnOffset(valueL, valueT); + }; +} + +if ('outerHTML' in document.documentElement) { + Element.Methods.replace = function(element, content) { + element = $(element); + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) { + element.parentNode.replaceChild(content, element); + return element; + } + + content = Object.toHTML(content); + var parent = element.parentNode, tagName = parent.tagName.toUpperCase(); + + if (Element._insertionTranslations.tags[tagName]) { + var nextSibling = element.next(); + var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); + parent.removeChild(element); + if (nextSibling) + fragments.each(function(node) { parent.insertBefore(node, nextSibling) }); + else + fragments.each(function(node) { parent.appendChild(node) }); + } + else element.outerHTML = content.stripScripts(); + + content.evalScripts.bind(content).defer(); + return element; + }; +} + +Element._returnOffset = function(l, t) { + var result = [l, t]; + result.left = l; + result.top = t; + return result; +}; + +Element._getContentFromAnonymousElement = function(tagName, html) { + var div = new Element('div'), t = Element._insertionTranslations.tags[tagName]; + if (t) { + div.innerHTML = t[0] + html + t[1]; + t[2].times(function() { div = div.firstChild }); + } else div.innerHTML = html; + return $A(div.childNodes); +}; + +Element._insertionTranslations = { + before: function(element, node) { + element.parentNode.insertBefore(node, element); + }, + top: function(element, node) { + element.insertBefore(node, element.firstChild); + }, + bottom: function(element, node) { + element.appendChild(node); + }, + after: function(element, node) { + element.parentNode.insertBefore(node, element.nextSibling); + }, + tags: { + TABLE: ['<table>', '</table>', 1], + TBODY: ['<table><tbody>', '</tbody></table>', 2], + TR: ['<table><tbody><tr>', '</tr></tbody></table>', 3], + TD: ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4], + SELECT: ['<select>', '</select>', 1] + } +}; + +(function() { + var tags = Element._insertionTranslations.tags; + Object.extend(tags, { + THEAD: tags.TBODY, + TFOOT: tags.TBODY, + TH: tags.TD + }); +})(); + +Element.Methods.Simulated = { + hasAttribute: function(element, attribute) { + attribute = Element._attributeTranslations.has[attribute] || attribute; + var node = $(element).getAttributeNode(attribute); + return !!(node && node.specified); + } +}; + +Element.Methods.ByTag = { }; + +Object.extend(Element, Element.Methods); + +(function(div) { + + if (!Prototype.BrowserFeatures.ElementExtensions && div['__proto__']) { + window.HTMLElement = { }; + window.HTMLElement.prototype = div['__proto__']; + Prototype.BrowserFeatures.ElementExtensions = true; + } + + div = null; + +})(document.createElement('div')) + +Element.extend = (function() { + + function checkDeficiency(tagName) { + if (typeof window.Element != 'undefined') { + var proto = window.Element.prototype; + if (proto) { + var id = '_' + (Math.random()+'').slice(2); + var el = document.createElement(tagName); + proto[id] = 'x'; + var isBuggy = (el[id] !== 'x'); + delete proto[id]; + el = null; + return isBuggy; + } + } + return false; + } + + function extendElementWith(element, methods) { + for (var property in methods) { + var value = methods[property]; + if (Object.isFunction(value) && !(property in element)) + element[property] = value.methodize(); + } + } + + var HTMLOBJECTELEMENT_PROTOTYPE_BUGGY = checkDeficiency('object'); + + if (Prototype.BrowserFeatures.SpecificElementExtensions) { + if (HTMLOBJECTELEMENT_PROTOTYPE_BUGGY) { + return function(element) { + if (element && typeof element._extendedByPrototype == 'undefined') { + var t = element.tagName; + if (t && (/^(?:object|applet|embed)$/i.test(t))) { + extendElementWith(element, Element.Methods); + extendElementWith(element, Element.Methods.Simulated); + extendElementWith(element, Element.Methods.ByTag[t.toUpperCase()]); + } + } + return element; + } + } + return Prototype.K; + } + + var Methods = { }, ByTag = Element.Methods.ByTag; + + var extend = Object.extend(function(element) { + if (!element || typeof element._extendedByPrototype != 'undefined' || + element.nodeType != 1 || element == window) return element; + + var methods = Object.clone(Methods), + tagName = element.tagName.toUpperCase(); + + if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]); + + extendElementWith(element, methods); + + element._extendedByPrototype = Prototype.emptyFunction; + return element; + + }, { + refresh: function() { + if (!Prototype.BrowserFeatures.ElementExtensions) { + Object.extend(Methods, Element.Methods); + Object.extend(Methods, Element.Methods.Simulated); + } + } + }); + + extend.refresh(); + return extend; +})(); + +Element.hasAttribute = function(element, attribute) { + if (element.hasAttribute) return element.hasAttribute(attribute); + return Element.Methods.Simulated.hasAttribute(element, attribute); +}; + +Element.addMethods = function(methods) { + var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag; + + if (!methods) { + Object.extend(Form, Form.Methods); + Object.extend(Form.Element, Form.Element.Methods); + Object.extend(Element.Methods.ByTag, { + "FORM": Object.clone(Form.Methods), + "INPUT": Object.clone(Form.Element.Methods), + "SELECT": Object.clone(Form.Element.Methods), + "TEXTAREA": Object.clone(Form.Element.Methods) + }); + } + + if (arguments.length == 2) { + var tagName = methods; + methods = arguments[1]; + } + + if (!tagName) Object.extend(Element.Methods, methods || { }); + else { + if (Object.isArray(tagName)) tagName.each(extend); + else extend(tagName); + } + + function extend(tagName) { + tagName = tagName.toUpperCase(); + if (!Element.Methods.ByTag[tagName]) + Element.Methods.ByTag[tagName] = { }; + Object.extend(Element.Methods.ByTag[tagName], methods); + } + + function copy(methods, destination, onlyIfAbsent) { + onlyIfAbsent = onlyIfAbsent || false; + for (var property in methods) { + var value = methods[property]; + if (!Object.isFunction(value)) continue; + if (!onlyIfAbsent || !(property in destination)) + destination[property] = value.methodize(); + } + } + + function findDOMClass(tagName) { + var klass; + var trans = { + "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph", + "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList", + "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading", + "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote", + "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION": + "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD": + "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR": + "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET": + "FrameSet", "IFRAME": "IFrame" + }; + if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element'; + if (window[klass]) return window[klass]; + klass = 'HTML' + tagName + 'Element'; + if (window[klass]) return window[klass]; + klass = 'HTML' + tagName.capitalize() + 'Element'; + if (window[klass]) return window[klass]; + + var element = document.createElement(tagName); + var proto = element['__proto__'] || element.constructor.prototype; + element = null; + return proto; + } + + var elementPrototype = window.HTMLElement ? HTMLElement.prototype : + Element.prototype; + + if (F.ElementExtensions) { + copy(Element.Methods, elementPrototype); + copy(Element.Methods.Simulated, elementPrototype, true); + } + + if (F.SpecificElementExtensions) { + for (var tag in Element.Methods.ByTag) { + var klass = findDOMClass(tag); + if (Object.isUndefined(klass)) continue; + copy(T[tag], klass.prototype); + } + } + + Object.extend(Element, Element.Methods); + delete Element.ByTag; + + if (Element.extend.refresh) Element.extend.refresh(); + Element.cache = { }; +}; + + +document.viewport = { + + getDimensions: function() { + return { width: this.getWidth(), height: this.getHeight() }; + }, + + getScrollOffsets: function() { + return Element._returnOffset( + window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft, + window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop); + } +}; + +(function(viewport) { + var B = Prototype.Browser, doc = document, element, property = {}; + + function getRootElement() { + if (B.WebKit && !doc.evaluate) + return document; + + if (B.Opera && window.parseFloat(window.opera.version()) < 9.5) + return document.body; + + return document.documentElement; + } + + function define(D) { + if (!element) element = getRootElement(); + + property[D] = 'client' + D; + + viewport['get' + D] = function() { return element[property[D]] }; + return viewport['get' + D](); + } + + viewport.getWidth = define.curry('Width'); + + viewport.getHeight = define.curry('Height'); +})(document.viewport); + + +Element.Storage = { + UID: 1 +}; + +Element.addMethods({ + getStorage: function(element) { + if (!(element = $(element))) return; + + var uid; + if (element === window) { + uid = 0; + } else { + if (typeof element._prototypeUID === "undefined") + element._prototypeUID = [Element.Storage.UID++]; + uid = element._prototypeUID[0]; + } + + if (!Element.Storage[uid]) + Element.Storage[uid] = $H(); + + return Element.Storage[uid]; + }, + + store: function(element, key, value) { + if (!(element = $(element))) return; + + if (arguments.length === 2) { + Element.getStorage(element).update(key); + } else { + Element.getStorage(element).set(key, value); + } + + return element; + }, + + retrieve: function(element, key, defaultValue) { + if (!(element = $(element))) return; + var hash = Element.getStorage(element), value = hash.get(key); + + if (Object.isUndefined(value)) { + hash.set(key, defaultValue); + value = defaultValue; + } + + return value; + }, + + clone: function(element, deep) { + if (!(element = $(element))) return; + var clone = element.cloneNode(deep); + clone._prototypeUID = void 0; + if (deep) { + var descendants = Element.select(clone, '*'), + i = descendants.length; + while (i--) { + descendants[i]._prototypeUID = void 0; + } + } + return Element.extend(clone); + } +}); +/* Portions of the Selector class are derived from Jack Slocum's DomQuery, + * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style + * license. Please see http://www.yui-ext.com/ for more information. */ + +var Selector = Class.create({ + initialize: function(expression) { + this.expression = expression.strip(); + + if (this.shouldUseSelectorsAPI()) { + this.mode = 'selectorsAPI'; + } else if (this.shouldUseXPath()) { + this.mode = 'xpath'; + this.compileXPathMatcher(); + } else { + this.mode = "normal"; + this.compileMatcher(); + } + + }, + + shouldUseXPath: (function() { + + var IS_DESCENDANT_SELECTOR_BUGGY = (function(){ + var isBuggy = false; + if (document.evaluate && window.XPathResult) { + var el = document.createElement('div'); + el.innerHTML = '<ul><li></li></ul><div><ul><li></li></ul></div>'; + + var xpath = ".//*[local-name()='ul' or local-name()='UL']" + + "//*[local-name()='li' or local-name()='LI']"; + + var result = document.evaluate(xpath, el, null, + XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + + isBuggy = (result.snapshotLength !== 2); + el = null; + } + return isBuggy; + })(); + + return function() { + if (!Prototype.BrowserFeatures.XPath) return false; + + var e = this.expression; + + if (Prototype.Browser.WebKit && + (e.include("-of-type") || e.include(":empty"))) + return false; + + if ((/(\[[\w-]*?:|:checked)/).test(e)) + return false; + + if (IS_DESCENDANT_SELECTOR_BUGGY) return false; + + return true; + } + + })(), + + shouldUseSelectorsAPI: function() { + if (!Prototype.BrowserFeatures.SelectorsAPI) return false; + + if (Selector.CASE_INSENSITIVE_CLASS_NAMES) return false; + + if (!Selector._div) Selector._div = new Element('div'); + + try { + Selector._div.querySelector(this.expression); + } catch(e) { + return false; + } + + return true; + }, + + compileMatcher: function() { + var e = this.expression, ps = Selector.patterns, h = Selector.handlers, + c = Selector.criteria, le, p, m, len = ps.length, name; + + if (Selector._cache[e]) { + this.matcher = Selector._cache[e]; + return; + } + + this.matcher = ["this.matcher = function(root) {", + "var r = root, h = Selector.handlers, c = false, n;"]; + + while (e && le != e && (/\S/).test(e)) { + le = e; + for (var i = 0; i<len; i++) { + p = ps[i].re; + name = ps[i].name; + if (m = e.match(p)) { + this.matcher.push(Object.isFunction(c[name]) ? c[name](m) : + new Template(c[name]).evaluate(m)); + e = e.replace(m[0], ''); + break; + } + } + } + + this.matcher.push("return h.unique(n);\n}"); + eval(this.matcher.join('\n')); + Selector._cache[this.expression] = this.matcher; + }, + + compileXPathMatcher: function() { + var e = this.expression, ps = Selector.patterns, + x = Selector.xpath, le, m, len = ps.length, name; + + if (Selector._cache[e]) { + this.xpath = Selector._cache[e]; return; + } + + this.matcher = ['.//*']; + while (e && le != e && (/\S/).test(e)) { + le = e; + for (var i = 0; i<len; i++) { + name = ps[i].name; + if (m = e.match(ps[i].re)) { + this.matcher.push(Object.isFunction(x[name]) ? x[name](m) : + new Template(x[name]).evaluate(m)); + e = e.replace(m[0], ''); + break; + } + } + } + + this.xpath = this.matcher.join(''); + Selector._cache[this.expression] = this.xpath; + }, + + findElements: function(root) { + root = root || document; + var e = this.expression, results; + + switch (this.mode) { + case 'selectorsAPI': + if (root !== document) { + var oldId = root.id, id = $(root).identify(); + id = id.replace(/([\.:])/g, "\\$1"); + e = "#" + id + " " + e; + } + + results = $A(root.querySelectorAll(e)).map(Element.extend); + root.id = oldId; + + return results; + case 'xpath': + return document._getElementsByXPath(this.xpath, root); + default: + return this.matcher(root); + } + }, + + match: function(element) { + this.tokens = []; + + var e = this.expression, ps = Selector.patterns, as = Selector.assertions; + var le, p, m, len = ps.length, name; + + while (e && le !== e && (/\S/).test(e)) { + le = e; + for (var i = 0; i<len; i++) { + p = ps[i].re; + name = ps[i].name; + if (m = e.match(p)) { + if (as[name]) { + this.tokens.push([name, Object.clone(m)]); + e = e.replace(m[0], ''); + } else { + return this.findElements(document).include(element); + } + } + } + } + + var match = true, name, matches; + for (var i = 0, token; token = this.tokens[i]; i++) { + name = token[0], matches = token[1]; + if (!Selector.assertions[name](element, matches)) { + match = false; break; + } + } + + return match; + }, + + toString: function() { + return this.expression; + }, + + inspect: function() { + return "#<Selector:" + this.expression.inspect() + ">"; + } +}); + +if (Prototype.BrowserFeatures.SelectorsAPI && + document.compatMode === 'BackCompat') { + Selector.CASE_INSENSITIVE_CLASS_NAMES = (function(){ + var div = document.createElement('div'), + span = document.createElement('span'); + + div.id = "prototype_test_id"; + span.className = 'Test'; + div.appendChild(span); + var isIgnored = (div.querySelector('#prototype_test_id .test') !== null); + div = span = null; + return isIgnored; + })(); +} + +Object.extend(Selector, { + _cache: { }, + + xpath: { + descendant: "//*", + child: "/*", + adjacent: "/following-sibling::*[1]", + laterSibling: '/following-sibling::*', + tagName: function(m) { + if (m[1] == '*') return ''; + return "[local-name()='" + m[1].toLowerCase() + + "' or local-name()='" + m[1].toUpperCase() + "']"; + }, + className: "[contains(concat(' ', @class, ' '), ' #{1} ')]", + id: "[@id='#{1}']", + attrPresence: function(m) { + m[1] = m[1].toLowerCase(); + return new Template("[@#{1}]").evaluate(m); + }, + attr: function(m) { + m[1] = m[1].toLowerCase(); + m[3] = m[5] || m[6]; + return new Template(Selector.xpath.operators[m[2]]).evaluate(m); + }, + pseudo: function(m) { + var h = Selector.xpath.pseudos[m[1]]; + if (!h) return ''; + if (Object.isFunction(h)) return h(m); + return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m); + }, + operators: { + '=': "[@#{1}='#{3}']", + '!=': "[@#{1}!='#{3}']", + '^=': "[starts-with(@#{1}, '#{3}')]", + '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']", + '*=': "[contains(@#{1}, '#{3}')]", + '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]", + '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]" + }, + pseudos: { + 'first-child': '[not(preceding-sibling::*)]', + 'last-child': '[not(following-sibling::*)]', + 'only-child': '[not(preceding-sibling::* or following-sibling::*)]', + 'empty': "[count(*) = 0 and (count(text()) = 0)]", + 'checked': "[@checked]", + 'disabled': "[(@disabled) and (@type!='hidden')]", + 'enabled': "[not(@disabled) and (@type!='hidden')]", + 'not': function(m) { + var e = m[6], p = Selector.patterns, + x = Selector.xpath, le, v, len = p.length, name; + + var exclusion = []; + while (e && le != e && (/\S/).test(e)) { + le = e; + for (var i = 0; i<len; i++) { + name = p[i].name + if (m = e.match(p[i].re)) { + v = Object.isFunction(x[name]) ? x[name](m) : new Template(x[name]).evaluate(m); + exclusion.push("(" + v.substring(1, v.length - 1) + ")"); + e = e.replace(m[0], ''); + break; + } + } + } + return "[not(" + exclusion.join(" and ") + ")]"; + }, + 'nth-child': function(m) { + return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m); + }, + 'nth-last-child': function(m) { + return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m); + }, + 'nth-of-type': function(m) { + return Selector.xpath.pseudos.nth("position() ", m); + }, + 'nth-last-of-type': function(m) { + return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m); + }, + 'first-of-type': function(m) { + m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m); + }, + 'last-of-type': function(m) { + m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m); + }, + 'only-of-type': function(m) { + var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m); + }, + nth: function(fragment, m) { + var mm, formula = m[6], predicate; + if (formula == 'even') formula = '2n+0'; + if (formula == 'odd') formula = '2n+1'; + if (mm = formula.match(/^(\d+)$/)) // digit only + return '[' + fragment + "= " + mm[1] + ']'; + if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b + if (mm[1] == "-") mm[1] = -1; + var a = mm[1] ? Number(mm[1]) : 1; + var b = mm[2] ? Number(mm[2]) : 0; + predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " + + "((#{fragment} - #{b}) div #{a} >= 0)]"; + return new Template(predicate).evaluate({ + fragment: fragment, a: a, b: b }); + } + } + } + }, + + criteria: { + tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;', + className: 'n = h.className(n, r, "#{1}", c); c = false;', + id: 'n = h.id(n, r, "#{1}", c); c = false;', + attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;', + attr: function(m) { + m[3] = (m[5] || m[6]); + return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m); + }, + pseudo: function(m) { + if (m[6]) m[6] = m[6].replace(/"/g, '\\"'); + return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m); + }, + descendant: 'c = "descendant";', + child: 'c = "child";', + adjacent: 'c = "adjacent";', + laterSibling: 'c = "laterSibling";' + }, + + patterns: [ + { name: 'laterSibling', re: /^\s*~\s*/ }, + { name: 'child', re: /^\s*>\s*/ }, + { name: 'adjacent', re: /^\s*\+\s*/ }, + { name: 'descendant', re: /^\s/ }, + + { name: 'tagName', re: /^\s*(\*|[\w\-]+)(\b|$)?/ }, + { name: 'id', re: /^#([\w\-\*]+)(\b|$)/ }, + { name: 'className', re: /^\.([\w\-\*]+)(\b|$)/ }, + { name: 'pseudo', re: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/ }, + { name: 'attrPresence', re: /^\[((?:[\w-]+:)?[\w-]+)\]/ }, + { name: 'attr', re: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ } + ], + + assertions: { + tagName: function(element, matches) { + return matches[1].toUpperCase() == element.tagName.toUpperCase(); + }, + + className: function(element, matches) { + return Element.hasClassName(element, matches[1]); + }, + + id: function(element, matches) { + return element.id === matches[1]; + }, + + attrPresence: function(element, matches) { + return Element.hasAttribute(element, matches[1]); + }, + + attr: function(element, matches) { + var nodeValue = Element.readAttribute(element, matches[1]); + return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]); + } + }, + + handlers: { + concat: function(a, b) { + for (var i = 0, node; node = b[i]; i++) + a.push(node); + return a; + }, + + mark: function(nodes) { + var _true = Prototype.emptyFunction; + for (var i = 0, node; node = nodes[i]; i++) + node._countedByPrototype = _true; + return nodes; + }, + + unmark: (function(){ + + var PROPERTIES_ATTRIBUTES_MAP = (function(){ + var el = document.createElement('div'), + isBuggy = false, + propName = '_countedByPrototype', + value = 'x' + el[propName] = value; + isBuggy = (el.getAttribute(propName) === value); + el = null; + return isBuggy; + })(); + + return PROPERTIES_ATTRIBUTES_MAP ? + function(nodes) { + for (var i = 0, node; node = nodes[i]; i++) + node.removeAttribute('_countedByPrototype'); + return nodes; + } : + function(nodes) { + for (var i = 0, node; node = nodes[i]; i++) + node._countedByPrototype = void 0; + return nodes; + } + })(), + + index: function(parentNode, reverse, ofType) { + parentNode._countedByPrototype = Prototype.emptyFunction; + if (reverse) { + for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) { + var node = nodes[i]; + if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; + } + } else { + for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++) + if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; + } + }, + + unique: function(nodes) { + if (nodes.length == 0) return nodes; + var results = [], n; + for (var i = 0, l = nodes.length; i < l; i++) + if (typeof (n = nodes[i])._countedByPrototype == 'undefined') { + n._countedByPrototype = Prototype.emptyFunction; + results.push(Element.extend(n)); + } + return Selector.handlers.unmark(results); + }, + + descendant: function(nodes) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + h.concat(results, node.getElementsByTagName('*')); + return results; + }, + + child: function(nodes) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) { + for (var j = 0, child; child = node.childNodes[j]; j++) + if (child.nodeType == 1 && child.tagName != '!') results.push(child); + } + return results; + }, + + adjacent: function(nodes) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + var next = this.nextElementSibling(node); + if (next) results.push(next); + } + return results; + }, + + laterSibling: function(nodes) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + h.concat(results, Element.nextSiblings(node)); + return results; + }, + + nextElementSibling: function(node) { + while (node = node.nextSibling) + if (node.nodeType == 1) return node; + return null; + }, + + previousElementSibling: function(node) { + while (node = node.previousSibling) + if (node.nodeType == 1) return node; + return null; + }, + + tagName: function(nodes, root, tagName, combinator) { + var uTagName = tagName.toUpperCase(); + var results = [], h = Selector.handlers; + if (nodes) { + if (combinator) { + if (combinator == "descendant") { + for (var i = 0, node; node = nodes[i]; i++) + h.concat(results, node.getElementsByTagName(tagName)); + return results; + } else nodes = this[combinator](nodes); + if (tagName == "*") return nodes; + } + for (var i = 0, node; node = nodes[i]; i++) + if (node.tagName.toUpperCase() === uTagName) results.push(node); + return results; + } else return root.getElementsByTagName(tagName); + }, + + id: function(nodes, root, id, combinator) { + var targetNode = $(id), h = Selector.handlers; + + if (root == document) { + if (!targetNode) return []; + if (!nodes) return [targetNode]; + } else { + if (!root.sourceIndex || root.sourceIndex < 1) { + var nodes = root.getElementsByTagName('*'); + for (var j = 0, node; node = nodes[j]; j++) { + if (node.id === id) return [node]; + } + } + } + + if (nodes) { + if (combinator) { + if (combinator == 'child') { + for (var i = 0, node; node = nodes[i]; i++) + if (targetNode.parentNode == node) return [targetNode]; + } else if (combinator == 'descendant') { + for (var i = 0, node; node = nodes[i]; i++) + if (Element.descendantOf(targetNode, node)) return [targetNode]; + } else if (combinator == 'adjacent') { + for (var i = 0, node; node = nodes[i]; i++) + if (Selector.handlers.previousElementSibling(targetNode) == node) + return [targetNode]; + } else nodes = h[combinator](nodes); + } + for (var i = 0, node; node = nodes[i]; i++) + if (node == targetNode) return [targetNode]; + return []; + } + return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : []; + }, + + className: function(nodes, root, className, combinator) { + if (nodes && combinator) nodes = this[combinator](nodes); + return Selector.handlers.byClassName(nodes, root, className); + }, + + byClassName: function(nodes, root, className) { + if (!nodes) nodes = Selector.handlers.descendant([root]); + var needle = ' ' + className + ' '; + for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) { + nodeClassName = node.className; + if (nodeClassName.length == 0) continue; + if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle)) + results.push(node); + } + return results; + }, + + attrPresence: function(nodes, root, attr, combinator) { + if (!nodes) nodes = root.getElementsByTagName("*"); + if (nodes && combinator) nodes = this[combinator](nodes); + var results = []; + for (var i = 0, node; node = nodes[i]; i++) + if (Element.hasAttribute(node, attr)) results.push(node); + return results; + }, + + attr: function(nodes, root, attr, value, operator, combinator) { + if (!nodes) nodes = root.getElementsByTagName("*"); + if (nodes && combinator) nodes = this[combinator](nodes); + var handler = Selector.operators[operator], results = []; + for (var i = 0, node; node = nodes[i]; i++) { + var nodeValue = Element.readAttribute(node, attr); + if (nodeValue === null) continue; + if (handler(nodeValue, value)) results.push(node); + } + return results; + }, + + pseudo: function(nodes, name, value, root, combinator) { + if (nodes && combinator) nodes = this[combinator](nodes); + if (!nodes) nodes = root.getElementsByTagName("*"); + return Selector.pseudos[name](nodes, value, root); + } + }, + + pseudos: { + 'first-child': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + if (Selector.handlers.previousElementSibling(node)) continue; + results.push(node); + } + return results; + }, + 'last-child': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + if (Selector.handlers.nextElementSibling(node)) continue; + results.push(node); + } + return results; + }, + 'only-child': function(nodes, value, root) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!h.previousElementSibling(node) && !h.nextElementSibling(node)) + results.push(node); + return results; + }, + 'nth-child': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root); + }, + 'nth-last-child': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root, true); + }, + 'nth-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root, false, true); + }, + 'nth-last-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root, true, true); + }, + 'first-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, "1", root, false, true); + }, + 'last-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, "1", root, true, true); + }, + 'only-of-type': function(nodes, formula, root) { + var p = Selector.pseudos; + return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root); + }, + + getIndices: function(a, b, total) { + if (a == 0) return b > 0 ? [b] : []; + return $R(1, total).inject([], function(memo, i) { + if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i); + return memo; + }); + }, + + nth: function(nodes, formula, root, reverse, ofType) { + if (nodes.length == 0) return []; + if (formula == 'even') formula = '2n+0'; + if (formula == 'odd') formula = '2n+1'; + var h = Selector.handlers, results = [], indexed = [], m; + h.mark(nodes); + for (var i = 0, node; node = nodes[i]; i++) { + if (!node.parentNode._countedByPrototype) { + h.index(node.parentNode, reverse, ofType); + indexed.push(node.parentNode); + } + } + if (formula.match(/^\d+$/)) { // just a number + formula = Number(formula); + for (var i = 0, node; node = nodes[i]; i++) + if (node.nodeIndex == formula) results.push(node); + } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b + if (m[1] == "-") m[1] = -1; + var a = m[1] ? Number(m[1]) : 1; + var b = m[2] ? Number(m[2]) : 0; + var indices = Selector.pseudos.getIndices(a, b, nodes.length); + for (var i = 0, node, l = indices.length; node = nodes[i]; i++) { + for (var j = 0; j < l; j++) + if (node.nodeIndex == indices[j]) results.push(node); + } + } + h.unmark(nodes); + h.unmark(indexed); + return results; + }, + + 'empty': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + if (node.tagName == '!' || node.firstChild) continue; + results.push(node); + } + return results; + }, + + 'not': function(nodes, selector, root) { + var h = Selector.handlers, selectorType, m; + var exclusions = new Selector(selector).findElements(root); + h.mark(exclusions); + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!node._countedByPrototype) results.push(node); + h.unmark(exclusions); + return results; + }, + + 'enabled': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!node.disabled && (!node.type || node.type !== 'hidden')) + results.push(node); + return results; + }, + + 'disabled': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (node.disabled) results.push(node); + return results; + }, + + 'checked': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (node.checked) results.push(node); + return results; + } + }, + + operators: { + '=': function(nv, v) { return nv == v; }, + '!=': function(nv, v) { return nv != v; }, + '^=': function(nv, v) { return nv == v || nv && nv.startsWith(v); }, + '$=': function(nv, v) { return nv == v || nv && nv.endsWith(v); }, + '*=': function(nv, v) { return nv == v || nv && nv.include(v); }, + '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); }, + '|=': function(nv, v) { return ('-' + (nv || "").toUpperCase() + + '-').include('-' + (v || "").toUpperCase() + '-'); } + }, + + split: function(expression) { + var expressions = []; + expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) { + expressions.push(m[1].strip()); + }); + return expressions; + }, + + matchElements: function(elements, expression) { + var matches = $$(expression), h = Selector.handlers; + h.mark(matches); + for (var i = 0, results = [], element; element = elements[i]; i++) + if (element._countedByPrototype) results.push(element); + h.unmark(matches); + return results; + }, + + findElement: function(elements, expression, index) { + if (Object.isNumber(expression)) { + index = expression; expression = false; + } + return Selector.matchElements(elements, expression || '*')[index || 0]; + }, + + findChildElements: function(element, expressions) { + expressions = Selector.split(expressions.join(',')); + var results = [], h = Selector.handlers; + for (var i = 0, l = expressions.length, selector; i < l; i++) { + selector = new Selector(expressions[i].strip()); + h.concat(results, selector.findElements(element)); + } + return (l > 1) ? h.unique(results) : results; + } +}); + +if (Prototype.Browser.IE) { + Object.extend(Selector.handlers, { + concat: function(a, b) { + for (var i = 0, node; node = b[i]; i++) + if (node.tagName !== "!") a.push(node); + return a; + } + }); +} + +function $$() { + return Selector.findChildElements(document, $A(arguments)); +} + +var Form = { + reset: function(form) { + form = $(form); + form.reset(); + return form; + }, + + serializeElements: function(elements, options) { + if (typeof options != 'object') options = { hash: !!options }; + else if (Object.isUndefined(options.hash)) options.hash = true; + var key, value, submitted = false, submit = options.submit; + + var data = elements.inject({ }, function(result, element) { + if (!element.disabled && element.name) { + key = element.name; value = $(element).getValue(); + if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted && + submit !== false && (!submit || key == submit) && (submitted = true)))) { + if (key in result) { + if (!Object.isArray(result[key])) result[key] = [result[key]]; + result[key].push(value); + } + else result[key] = value; + } + } + return result; + }); + + return options.hash ? data : Object.toQueryString(data); + } +}; + +Form.Methods = { + serialize: function(form, options) { + return Form.serializeElements(Form.getElements(form), options); + }, + + getElements: function(form) { + var elements = $(form).getElementsByTagName('*'), + element, + arr = [ ], + serializers = Form.Element.Serializers; + for (var i = 0; element = elements[i]; i++) { + arr.push(element); + } + return arr.inject([], function(elements, child) { + if (serializers[child.tagName.toLowerCase()]) + elements.push(Element.extend(child)); + return elements; + }) + }, + + getInputs: function(form, typeName, name) { + form = $(form); + var inputs = form.getElementsByTagName('input'); + + if (!typeName && !name) return $A(inputs).map(Element.extend); + + for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) { + var input = inputs[i]; + if ((typeName && input.type != typeName) || (name && input.name != name)) + continue; + matchingInputs.push(Element.extend(input)); + } + + return matchingInputs; + }, + + disable: function(form) { + form = $(form); + Form.getElements(form).invoke('disable'); + return form; + }, + + enable: function(form) { + form = $(form); + Form.getElements(form).invoke('enable'); + return form; + }, + + findFirstElement: function(form) { + var elements = $(form).getElements().findAll(function(element) { + return 'hidden' != element.type && !element.disabled; + }); + var firstByIndex = elements.findAll(function(element) { + return element.hasAttribute('tabIndex') && element.tabIndex >= 0; + }).sortBy(function(element) { return element.tabIndex }).first(); + + return firstByIndex ? firstByIndex : elements.find(function(element) { + return /^(?:input|select|textarea)$/i.test(element.tagName); + }); + }, + + focusFirstElement: function(form) { + form = $(form); + form.findFirstElement().activate(); + return form; + }, + + request: function(form, options) { + form = $(form), options = Object.clone(options || { }); + + var params = options.parameters, action = form.readAttribute('action') || ''; + if (action.blank()) action = window.location.href; + options.parameters = form.serialize(true); + + if (params) { + if (Object.isString(params)) params = params.toQueryParams(); + Object.extend(options.parameters, params); + } + + if (form.hasAttribute('method') && !options.method) + options.method = form.method; + + return new Ajax.Request(action, options); + } +}; + +/*--------------------------------------------------------------------------*/ + + +Form.Element = { + focus: function(element) { + $(element).focus(); + return element; + }, + + select: function(element) { + $(element).select(); + return element; + } +}; + +Form.Element.Methods = { + + serialize: function(element) { + element = $(element); + if (!element.disabled && element.name) { + var value = element.getValue(); + if (value != undefined) { + var pair = { }; + pair[element.name] = value; + return Object.toQueryString(pair); + } + } + return ''; + }, + + getValue: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + return Form.Element.Serializers[method](element); + }, + + setValue: function(element, value) { + element = $(element); + var method = element.tagName.toLowerCase(); + Form.Element.Serializers[method](element, value); + return element; + }, + + clear: function(element) { + $(element).value = ''; + return element; + }, + + present: function(element) { + return $(element).value != ''; + }, + + activate: function(element) { + element = $(element); + try { + element.focus(); + if (element.select && (element.tagName.toLowerCase() != 'input' || + !(/^(?:button|reset|submit)$/i.test(element.type)))) + element.select(); + } catch (e) { } + return element; + }, + + disable: function(element) { + element = $(element); + element.disabled = true; + return element; + }, + + enable: function(element) { + element = $(element); + element.disabled = false; + return element; + } +}; + +/*--------------------------------------------------------------------------*/ + +var Field = Form.Element; + +var $F = Form.Element.Methods.getValue; + +/*--------------------------------------------------------------------------*/ + +Form.Element.Serializers = { + input: function(element, value) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + return Form.Element.Serializers.inputSelector(element, value); + default: + return Form.Element.Serializers.textarea(element, value); + } + }, + + inputSelector: function(element, value) { + if (Object.isUndefined(value)) return element.checked ? element.value : null; + else element.checked = !!value; + }, + + textarea: function(element, value) { + if (Object.isUndefined(value)) return element.value; + else element.value = value; + }, + + select: function(element, value) { + if (Object.isUndefined(value)) + return this[element.type == 'select-one' ? + 'selectOne' : 'selectMany'](element); + else { + var opt, currentValue, single = !Object.isArray(value); + for (var i = 0, length = element.length; i < length; i++) { + opt = element.options[i]; + currentValue = this.optionValue(opt); + if (single) { + if (currentValue == value) { + opt.selected = true; + return; + } + } + else opt.selected = value.include(currentValue); + } + } + }, + + selectOne: function(element) { + var index = element.selectedIndex; + return index >= 0 ? this.optionValue(element.options[index]) : null; + }, + + selectMany: function(element) { + var values, length = element.length; + if (!length) return null; + + for (var i = 0, values = []; i < length; i++) { + var opt = element.options[i]; + if (opt.selected) values.push(this.optionValue(opt)); + } + return values; + }, + + optionValue: function(opt) { + return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text; + } +}; + +/*--------------------------------------------------------------------------*/ + + +Abstract.TimedObserver = Class.create(PeriodicalExecuter, { + initialize: function($super, element, frequency, callback) { + $super(callback, frequency); + this.element = $(element); + this.lastValue = this.getValue(); + }, + + execute: function() { + var value = this.getValue(); + if (Object.isString(this.lastValue) && Object.isString(value) ? + this.lastValue != value : String(this.lastValue) != String(value)) { + this.callback(this.element, value); + this.lastValue = value; + } + } +}); + +Form.Element.Observer = Class.create(Abstract.TimedObserver, { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.Observer = Class.create(Abstract.TimedObserver, { + getValue: function() { + return Form.serialize(this.element); + } +}); + +/*--------------------------------------------------------------------------*/ + +Abstract.EventObserver = Class.create({ + initialize: function(element, callback) { + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + if (this.element.tagName.toLowerCase() == 'form') + this.registerFormCallbacks(); + else + this.registerCallback(this.element); + }, + + onElementEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + }, + + registerFormCallbacks: function() { + Form.getElements(this.element).each(this.registerCallback, this); + }, + + registerCallback: function(element) { + if (element.type) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + Event.observe(element, 'click', this.onElementEvent.bind(this)); + break; + default: + Event.observe(element, 'change', this.onElementEvent.bind(this)); + break; + } + } + } +}); + +Form.Element.EventObserver = Class.create(Abstract.EventObserver, { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.EventObserver = Class.create(Abstract.EventObserver, { + getValue: function() { + return Form.serialize(this.element); + } +}); +(function() { + + var Event = { + KEY_BACKSPACE: 8, + KEY_TAB: 9, + KEY_RETURN: 13, + KEY_ESC: 27, + KEY_LEFT: 37, + KEY_UP: 38, + KEY_RIGHT: 39, + KEY_DOWN: 40, + KEY_DELETE: 46, + KEY_HOME: 36, + KEY_END: 35, + KEY_PAGEUP: 33, + KEY_PAGEDOWN: 34, + KEY_INSERT: 45, + + cache: {} + }; + + var docEl = document.documentElement; + var MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED = 'onmouseenter' in docEl + && 'onmouseleave' in docEl; + + var _isButton; + if (Prototype.Browser.IE) { + var buttonMap = { 0: 1, 1: 4, 2: 2 }; + _isButton = function(event, code) { + return event.button === buttonMap[code]; + }; + } else if (Prototype.Browser.WebKit) { + _isButton = function(event, code) { + switch (code) { + case 0: return event.which == 1 && !event.metaKey; + case 1: return event.which == 1 && event.metaKey; + default: return false; + } + }; + } else { + _isButton = function(event, code) { + return event.which ? (event.which === code + 1) : (event.button === code); + }; + } + + function isLeftClick(event) { return _isButton(event, 0) } + + function isMiddleClick(event) { return _isButton(event, 1) } + + function isRightClick(event) { return _isButton(event, 2) } + + function element(event) { + event = Event.extend(event); + + var node = event.target, type = event.type, + currentTarget = event.currentTarget; + + if (currentTarget && currentTarget.tagName) { + if (type === 'load' || type === 'error' || + (type === 'click' && currentTarget.tagName.toLowerCase() === 'input' + && currentTarget.type === 'radio')) + node = currentTarget; + } + + if (node.nodeType == Node.TEXT_NODE) + node = node.parentNode; + + return Element.extend(node); + } + + function findElement(event, expression) { + var element = Event.element(event); + if (!expression) return element; + var elements = [element].concat(element.ancestors()); + return Selector.findElement(elements, expression, 0); + } + + function pointer(event) { + return { x: pointerX(event), y: pointerY(event) }; + } + + function pointerX(event) { + var docElement = document.documentElement, + body = document.body || { scrollLeft: 0 }; + + return event.pageX || (event.clientX + + (docElement.scrollLeft || body.scrollLeft) - + (docElement.clientLeft || 0)); + } + + function pointerY(event) { + var docElement = document.documentElement, + body = document.body || { scrollTop: 0 }; + + return event.pageY || (event.clientY + + (docElement.scrollTop || body.scrollTop) - + (docElement.clientTop || 0)); + } + + + function stop(event) { + Event.extend(event); + event.preventDefault(); + event.stopPropagation(); + + event.stopped = true; + } + + Event.Methods = { + isLeftClick: isLeftClick, + isMiddleClick: isMiddleClick, + isRightClick: isRightClick, + + element: element, + findElement: findElement, + + pointer: pointer, + pointerX: pointerX, + pointerY: pointerY, + + stop: stop + }; + + + var methods = Object.keys(Event.Methods).inject({ }, function(m, name) { + m[name] = Event.Methods[name].methodize(); + return m; + }); + + if (Prototype.Browser.IE) { + function _relatedTarget(event) { + var element; + switch (event.type) { + case 'mouseover': element = event.fromElement; break; + case 'mouseout': element = event.toElement; break; + default: return null; + } + return Element.extend(element); + } + + Object.extend(methods, { + stopPropagation: function() { this.cancelBubble = true }, + preventDefault: function() { this.returnValue = false }, + inspect: function() { return '[object Event]' } + }); + + Event.extend = function(event, element) { + if (!event) return false; + if (event._extendedByPrototype) return event; + + event._extendedByPrototype = Prototype.emptyFunction; + var pointer = Event.pointer(event); + + Object.extend(event, { + target: event.srcElement || element, + relatedTarget: _relatedTarget(event), + pageX: pointer.x, + pageY: pointer.y + }); + + return Object.extend(event, methods); + }; + } else { + Event.prototype = window.Event.prototype || document.createEvent('HTMLEvents').__proto__; + Object.extend(Event.prototype, methods); + Event.extend = Prototype.K; + } + + function _createResponder(element, eventName, handler) { + var registry = Element.retrieve(element, 'prototype_event_registry'); + + if (Object.isUndefined(registry)) { + CACHE.push(element); + registry = Element.retrieve(element, 'prototype_event_registry', $H()); + } + + var respondersForEvent = registry.get(eventName); + if (Object.isUndefined(respondersForEvent)) { + respondersForEvent = []; + registry.set(eventName, respondersForEvent); + } + + if (respondersForEvent.pluck('handler').include(handler)) return false; + + var responder; + if (eventName.include(":")) { + responder = function(event) { + if (Object.isUndefined(event.eventName)) + return false; + + if (event.eventName !== eventName) + return false; + + Event.extend(event, element); + handler.call(element, event); + }; + } else { + if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED && + (eventName === "mouseenter" || eventName === "mouseleave")) { + if (eventName === "mouseenter" || eventName === "mouseleave") { + responder = function(event) { + Event.extend(event, element); + + var parent = event.relatedTarget; + while (parent && parent !== element) { + try { parent = parent.parentNode; } + catch(e) { parent = element; } + } + + if (parent === element) return; + + handler.call(element, event); + }; + } + } else { + responder = function(event) { + Event.extend(event, element); + handler.call(element, event); + }; + } + } + + responder.handler = handler; + respondersForEvent.push(responder); + return responder; + } + + function _destroyCache() { + for (var i = 0, length = CACHE.length; i < length; i++) { + Event.stopObserving(CACHE[i]); + CACHE[i] = null; + } + } + + var CACHE = []; + + if (Prototype.Browser.IE) + window.attachEvent('onunload', _destroyCache); + + if (Prototype.Browser.WebKit) + window.addEventListener('unload', Prototype.emptyFunction, false); + + + var _getDOMEventName = Prototype.K; + + if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED) { + _getDOMEventName = function(eventName) { + var translations = { mouseenter: "mouseover", mouseleave: "mouseout" }; + return eventName in translations ? translations[eventName] : eventName; + }; + } + + function observe(element, eventName, handler) { + element = $(element); + + var responder = _createResponder(element, eventName, handler); + + if (!responder) return element; + + if (eventName.include(':')) { + if (element.addEventListener) + element.addEventListener("dataavailable", responder, false); + else { + element.attachEvent("ondataavailable", responder); + element.attachEvent("onfilterchange", responder); + } + } else { + var actualEventName = _getDOMEventName(eventName); + + if (element.addEventListener) + element.addEventListener(actualEventName, responder, false); + else + element.attachEvent("on" + actualEventName, responder); + } + + return element; + } + + function stopObserving(element, eventName, handler) { + element = $(element); + + var registry = Element.retrieve(element, 'prototype_event_registry'); + + if (Object.isUndefined(registry)) return element; + + if (eventName && !handler) { + var responders = registry.get(eventName); + + if (Object.isUndefined(responders)) return element; + + responders.each( function(r) { + Element.stopObserving(element, eventName, r.handler); + }); + return element; + } else if (!eventName) { + registry.each( function(pair) { + var eventName = pair.key, responders = pair.value; + + responders.each( function(r) { + Element.stopObserving(element, eventName, r.handler); + }); + }); + return element; + } + + var responders = registry.get(eventName); + + if (!responders) return; + + var responder = responders.find( function(r) { return r.handler === handler; }); + if (!responder) return element; + + var actualEventName = _getDOMEventName(eventName); + + if (eventName.include(':')) { + if (element.removeEventListener) + element.removeEventListener("dataavailable", responder, false); + else { + element.detachEvent("ondataavailable", responder); + element.detachEvent("onfilterchange", responder); + } + } else { + if (element.removeEventListener) + element.removeEventListener(actualEventName, responder, false); + else + element.detachEvent('on' + actualEventName, responder); + } + + registry.set(eventName, responders.without(responder)); + + return element; + } + + function fire(element, eventName, memo, bubble) { + element = $(element); + + if (Object.isUndefined(bubble)) + bubble = true; + + if (element == document && document.createEvent && !element.dispatchEvent) + element = document.documentElement; + + var event; + if (document.createEvent) { + event = document.createEvent('HTMLEvents'); + event.initEvent('dataavailable', true, true); + } else { + event = document.createEventObject(); + event.eventType = bubble ? 'ondataavailable' : 'onfilterchange'; + } + + event.eventName = eventName; + event.memo = memo || { }; + + if (document.createEvent) + element.dispatchEvent(event); + else + element.fireEvent(event.eventType, event); + + return Event.extend(event); + } + + + Object.extend(Event, Event.Methods); + + Object.extend(Event, { + fire: fire, + observe: observe, + stopObserving: stopObserving + }); + + Element.addMethods({ + fire: fire, + + observe: observe, + + stopObserving: stopObserving + }); + + Object.extend(document, { + fire: fire.methodize(), + + observe: observe.methodize(), + + stopObserving: stopObserving.methodize(), + + loaded: false + }); + + if (window.Event) Object.extend(window.Event, Event); + else window.Event = Event; +})(); + +(function() { + /* Support for the DOMContentLoaded event is based on work by Dan Webb, + Matthias Miller, Dean Edwards, John Resig, and Diego Perini. */ + + var timer; + + function fireContentLoadedEvent() { + if (document.loaded) return; + if (timer) window.clearTimeout(timer); + document.loaded = true; + document.fire('dom:loaded'); + } + + function checkReadyState() { + if (document.readyState === 'complete') { + document.stopObserving('readystatechange', checkReadyState); + fireContentLoadedEvent(); + } + } + + function pollDoScroll() { + try { document.documentElement.doScroll('left'); } + catch(e) { + timer = pollDoScroll.defer(); + return; + } + fireContentLoadedEvent(); + } + + if (document.addEventListener) { + document.addEventListener('DOMContentLoaded', fireContentLoadedEvent, false); + } else { + document.observe('readystatechange', checkReadyState); + if (window == top) + timer = pollDoScroll.defer(); + } + + Event.observe(window, 'load', fireContentLoadedEvent); +})(); + +Element.addMethods(); + +/*------------------------------- DEPRECATED -------------------------------*/ + +Hash.toQueryString = Object.toQueryString; + +var Toggle = { display: Element.toggle }; + +Element.Methods.childOf = Element.Methods.descendantOf; + +var Insertion = { + Before: function(element, content) { + return Element.insert(element, {before:content}); + }, + + Top: function(element, content) { + return Element.insert(element, {top:content}); + }, + + Bottom: function(element, content) { + return Element.insert(element, {bottom:content}); + }, + + After: function(element, content) { + return Element.insert(element, {after:content}); + } +}; + +var $continue = new Error('"throw $continue" is deprecated, use "return" instead'); + +var Position = { + includeScrollOffsets: false, + + prepare: function() { + this.deltaX = window.pageXOffset + || document.documentElement.scrollLeft + || document.body.scrollLeft + || 0; + this.deltaY = window.pageYOffset + || document.documentElement.scrollTop + || document.body.scrollTop + || 0; + }, + + within: function(element, x, y) { + if (this.includeScrollOffsets) + return this.withinIncludingScrolloffsets(element, x, y); + this.xcomp = x; + this.ycomp = y; + this.offset = Element.cumulativeOffset(element); + + return (y >= this.offset[1] && + y < this.offset[1] + element.offsetHeight && + x >= this.offset[0] && + x < this.offset[0] + element.offsetWidth); + }, + + withinIncludingScrolloffsets: function(element, x, y) { + var offsetcache = Element.cumulativeScrollOffset(element); + + this.xcomp = x + offsetcache[0] - this.deltaX; + this.ycomp = y + offsetcache[1] - this.deltaY; + this.offset = Element.cumulativeOffset(element); + + return (this.ycomp >= this.offset[1] && + this.ycomp < this.offset[1] + element.offsetHeight && + this.xcomp >= this.offset[0] && + this.xcomp < this.offset[0] + element.offsetWidth); + }, + + overlap: function(mode, element) { + if (!mode) return 0; + if (mode == 'vertical') + return ((this.offset[1] + element.offsetHeight) - this.ycomp) / + element.offsetHeight; + if (mode == 'horizontal') + return ((this.offset[0] + element.offsetWidth) - this.xcomp) / + element.offsetWidth; + }, + + + cumulativeOffset: Element.Methods.cumulativeOffset, + + positionedOffset: Element.Methods.positionedOffset, + + absolutize: function(element) { + Position.prepare(); + return Element.absolutize(element); + }, + + relativize: function(element) { + Position.prepare(); + return Element.relativize(element); + }, + + realOffset: Element.Methods.cumulativeScrollOffset, + + offsetParent: Element.Methods.getOffsetParent, + + page: Element.Methods.viewportOffset, + + clone: function(source, target, options) { + options = options || { }; + return Element.clonePosition(target, source, options); + } +}; + +/*--------------------------------------------------------------------------*/ + +if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){ + function iter(name) { + return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]"; + } + + instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ? + function(element, className) { + className = className.toString().strip(); + var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className); + return cond ? document._getElementsByXPath('.//*' + cond, element) : []; + } : function(element, className) { + className = className.toString().strip(); + var elements = [], classNames = (/\s/.test(className) ? $w(className) : null); + if (!classNames && !className) return elements; + + var nodes = $(element).getElementsByTagName('*'); + className = ' ' + className + ' '; + + for (var i = 0, child, cn; child = nodes[i]; i++) { + if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) || + (classNames && classNames.all(function(name) { + return !name.toString().blank() && cn.include(' ' + name + ' '); + })))) + elements.push(Element.extend(child)); + } + return elements; + }; + + return function(className, parentElement) { + return $(parentElement || document.body).getElementsByClassName(className); + }; +}(Element.Methods); + +/*--------------------------------------------------------------------------*/ + +Element.ClassNames = Class.create(); +Element.ClassNames.prototype = { + initialize: function(element) { + this.element = $(element); + }, + + _each: function(iterator) { + this.element.className.split(/\s+/).select(function(name) { + return name.length > 0; + })._each(iterator); + }, + + set: function(className) { + this.element.className = className; + }, + + add: function(classNameToAdd) { + if (this.include(classNameToAdd)) return; + this.set($A(this).concat(classNameToAdd).join(' ')); + }, + + remove: function(classNameToRemove) { + if (!this.include(classNameToRemove)) return; + this.set($A(this).without(classNameToRemove).join(' ')); + }, + + toString: function() { + return $A(this).join(' '); + } +}; + +Object.extend(Element.ClassNames.prototype, Enumerable); + +/*--------------------------------------------------------------------------*/ diff --git a/cb-tools/java-source/java-source/src/main/example/web-socket-js/WebSocketMain.swf b/cb-tools/java-source/java-source/src/main/example/web-socket-js/WebSocketMain.swf new file mode 100644 index 0000000000000000000000000000000000000000..8174466912475a494681e9436844f4bf90d909f9 GIT binary patch literal 177114 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�{?S0&?Rq)c&c-(}SFHOPy4tz`b5gun?zakh{ z@cpxA+wgoCGneChv*N&ec-#cHYY&V&=E|NEnEvMD1{gQ>_!JnI_kJNBxAp13K0I#g zme<|^xhNhVf!nnOhx=i^yx`j~?%NX&;c;8v{No0U`_=28;c*l8Pu>9ITKWxv`F>CR z1boY1n2E;~_<!`_aof<&4CL1M@Y3VBKDZ3z&}=wYbQtG<eBBE3)c=}*`C9$l?)!27 zZ9AJ^#Ov0a{;w>|pCI2e4&-$1>nphZz`uXP!}YD-=EM3V{ANqQ{kL7s_!*Bs<X?>a zAkTgm7Q^~JQI(JTZ5=C9<8cMOZ&YBuhiockL3?kV64MXKe(em5$9i6b+cmEj?7+{~ z&o;K;`86|pOoaIpZ4+QUZXUb`_cvrp%4@j4wtr<G1i3$y@g44`_4{Alke~U-Y+T>m z^V1=qkL*+?^n*6-@MI^@d3G$H5UG#QMe{V>K%>!!`5tngc`@YKPqyIx1#f-(D#-nZ zH@?B+w&jlK1AN^7S_anlwnq=m!g3NU`%w&hTc67Sdk8-AH<<5|%lBjc+Aa@b!Mtze zJb?RYO?+De>w59cCqPf84)EiCRu4V60FOUp=fkh#@mJ4xyp8KuKR;Ux^3E+O0eQ~6 zZzS$_2s`&HoKJWxLjn9e=(r#EoAB;GwgMkT+gQNg)p>{U_-(fSAH#aCobe4_|7QP^ zVm$BWk*`mL_1-o1?=bJ7YhMC>*1obC_q*D9!3X2X_gn)xv2MHve28*vSRY&GzJ3nN zN$~l+Owgwx>xY9q)=hVVy-e8U2YV?R^)IlqgyCahezh0Z;eH2>8*l*Z?4i0Q(4R9S z_|X2?r8OYe;NNPXod2&Pkl*yDo(22dW>(<-1na(^g6EsC>}(32*O2ayFNO70iWzv^ zA=Nj`xW6IPV|Rf){O5icZy@WNmx2D3{#(JG?wNcBkGJ}A{~WO6k9yg`Zf5lxi{&7g zw&!P%S3=W%;KStFi2H5hhx&p%PSg@G-it36;`-J13U-1XG+we|IVC)|VkhuvlG*_` zECW8l<wJ~fT48>teKR0`<+aVA*W=Ht@VG<%apo}C^Fzj2V1Jdz-+*<P_tUG;U*D2O z;HUP|b9g=p+02JP55k$ypWw}Z7K43%l6Vl$vpH{58SZD`pq2lC`ER5qf;<mQ*nbMj z`yK`SKDeU?=&ka_Wmql=S-rLco_)8#i0cyyf9#3t6P6#nip$%`*Z+<AX?u6!cYx!j z{eR$Rvs)$xc{A_n33=8^2H=M1j{@jF=iU(L-`TZmKyIr(QiDESk_>_VTKX=Ac}+R{ zDd@BQ>22U|t|z?%?e6~PhcM6kml&bHwt2lk&n#ma0T+{J>;im#Vbo86V_$w80K7f4 zq!EuZr0KD*K|eQbU5Dqf`msC)*d<k*26FeVwgE1DdCUoV@+b-NY&-GO8d%@`4dX!G z&EGu@_B;B34!0M~aBl%S%6;w%;O$4v$6?$pb3OxoxpY1Y^r;{DInX^Z%?o<E{_$a8 z*SmLq3VLzRmMM6=)uo@`0Dn2A>>ZHL{;YDKb3WP)?5T)1jP`#go(2Az^@X^+^&##J zpkF$d4R~UEa17wdkk>AP{EjEkI51@v;6|H#5D(_r!r%i=w+&;#`c0p@2Gh0XJ>G)( z6ukT>;QElWiji2(%^T<44LG#;#TS6zesA;xd7ij;B8>m_^f7=hi~1H}KHJ_KxCZ2$ zbN(H`*CXm3VE0V32tNm|4b_2Mj-8kYcJ|rCb6}4Z7eU_53#k5pH?KVZCOlV7_d&a+ z32(qUCk{IT_-%L0f_1z$CI{B%Ov5G6lLObwU|ugj_XqHQtYQz?-8|=hz?I7bo`-(d zY+eJnG406HFt5UGJkSTv-9rJt_Dov~>!5jlGT_x~udD+9we!>qAn$U^VR(MKDg<_Q z+FA*?xNuY$>@T$u;vB)X;~B6XD+WRQ(rS49Tae4Noj-t{&b&YZZk(Mp3Hn)>2l(37 z=pG1q@^<Y!+<wTK9g9FO*A5Ed^=RHRmTvz}?K{B#Xx|H9M|mDL@cB?48}!lIQiJOS zcU}4!&u?|c#g%wI1MfP|g}7rLc^&Za+Pdqs{_fj{^KCCT(Ej0h^XD+$1O0yoJ^kcC zh--$7d1Wh%fBoHLkmpOr5a{LMS3R)a*IZQ)AB}q9B+R?M;tb3$Yby)Zed+A)LEdMa zuVQ%(eAaLp>_3?L7WBXKaz3=XtN(Psr?07nF#gmVn*pER`0gd}W2)od0e`9IR>S!G z-yZ~ie$5_$+YQ{?JPmMvP$L88FMIQA(6_N4JE8x!PricjnqCAuO>mxc1FpO=aw)9; z(NzOLpJwwe1O6Qg><2wx83MgbP|bZ3*6X2uV?p264c`Rz^|vQRgIvkcQvpX(295_j z8pCb@yq`7dDBy2m-FeW5oQ1og{WC3Bz@J~X9f0=NXGuZ-^+Y51hgIJ~e3&3eIR*N# z;MY`;Yr^CO;0Ly@s{*^f@v|Mq3-sO!e(nq3vw%at7XBOLbJ;%_o_6P4D1Vr(0eQ}T z!3=ug-~BMG(~Mq9I!+3G3v!yhy%)&8WYMD#A3pWOJK)dk|Df%tSh)e>xQxGXK+h5$ zTm|?z*K!Z|@7a$oh4LBSPJno2-?>AeclR}bezyrHRKUKX_k%Y9kFISx4fynEW;OV+ z$M@`k`4WcD!JZcpd!V1Ui0{CT5;aV~fe$Wx4)R?&^*7M3hiCr+_C8}(0FSqN`Zfow z)7gVh0Iq)g@COjDyfKIgcDL%qXJ8)v|M?jBS@XBFUVm7#74#|L!S`uBegxvZA(tL* z2L3KQG85Ke5w#EGa;Tse$Z6-?N1^?B(gJZ}M%qxoRo#9z;Pw+6VSh2iA^R3^?&;zS zV9$;Dg8~0XPk99NK=#rSkW;r^TIf%B1NJd({IOqv{8O(!1i0k<>^WGc%dCIHJSQGJ z4!CYtIYDmTU4Z{>nTe^u@A^I~fUhka*dGjBylN20<HVFPp#Mwr&V&E`t`7V`Yw^l4 z(C*waE7;{$Vl>$E@(XDY2P~ZPKEwr^1_?m_CWjsYJNRm#58{E`Bfr4>TK>_8j#m^T zVISBg=?3;h{3jFSrv3e0(9a!JPs2Xy<?q{IU6z(70L~5XX@Yq-C%M61Wxp_i@BN=W z1naa>TnX#_%}WqZt={)8>_-HP-+USLXx?KG54V+g!9O%Vye9y1oB!Gzz_<73^FYoA zpMDDXo%Ep=#^wI76qh&4<rg9D68lO4H@|%0Wmuo%>ITrGlTshpdw5(P=+O@0hoGO9 zvxk76H>bmXcgUOfw!!|&GweM0$GqJt7;o$7w_!bg^zH<_l9=ZKf8Qwbp#K9i-UfRg zvVSwkcV+c+AgA>QwBYwD@_T|@L#wBQ+<I3}1^<?p+yHu~9Pkp*&H3;;>@S*LF9kgq zTyz5b*YltJ1ai1`;~4bw`wR3w_R)7906kirdKvoJ^|TrKo%^r|*6+gm8zKM7k{<!b zemntj;gGv3Rsx=n$>D%rFVAMd`s-g_2>$b}hWj9{*zn1Hun*yXG7t8{U$40Udc3W+ z3GDfc(GS7Cdu`w)Sm)=D0G<v!Ht<Qn$%Ah!0zOx+`aAf~_lhbYu6}M10rvIOYk;S1 zB?|$+1fqv+(EeVX2K1tFs{_s}sQq_Cyff=b67-9?#t3qhe*P)g;~GT({Cmd0_W?gO zs>OgqUrl@$aQwZorO=OY?I#e2og94v%HQ~>9^`REy_eo!Zvns2R%7}3G{ocL?({y4 z`U&bsB;<l$lJFJ*t|jgK7X04tjtd~aCo~RNx2JhMVcn{(upoZyF%jgu`q^s}VBGvi z+d$td`vATTTy*7C;Nyou_k&&)4Vnh}yX9}&PtrKP3ix(CaX0wA+|3t(&*!FB!+OY$ zUVwedjG=yrGu#UYgP)kPV>ih4^<*R1-_L@*fEUE4Lty<z1&4sV){K4-@*iI~0ysR# ze;>?e1UCqFBY8F**6s1{z5w|T+UNy4IKSvP#DB-02td1&o;BbHiYLtmJ_|HkV4mM@ zoe6l=I_x8mXXUlGKyUT7gS5Q=H3M*Wy?P_a?PG2T^x&z~sbE))>iggvL$_r+;6P#j zJka+md-nq00nNXFpO=es0ROLlVFP=WHV=gP{?hmZ=--GP^I*O1V#9uS^>;s<1f1UV z@|WNjm-T!eaP+x<9ss|c^1*$u&O0Y<2E5tv0O;+&zil}M{gqsYxJQup3)or0zC{A? zqfc%=1p5>_<0|;wOUi>Mh~0t&c0J{?arHsJ%j@8YDauWny<Cl@hO`EId&yur{Vhd$ z>|2WNv2Q74^`tk*a(ROs!tXaXCd20|=_D=<aG;VMLbZ&4q$*==N-2kf^mDCl7phKH zYGi&t<p<hcdeR#5yMm4FMJZZ8<?xe%0OY#S{fB}scOXgJ7$jlTc6Q|WTaFZ($mR<8 z+~&p<DgC6UbGMc9q&q`?x7iy^xB44>K`PxJFmrW!qJ(U`r;A!E=@0g;%*iZ}poGhg zzquhLl-rFub5Y(zdB_Wi9-T<K8YqaEHk9fml-KTZ@Rd??(5&?OLcwm3)P#a4ornlv z_FQ$yV<G*J$5T(DXN8u7OV5>GN||k>U+J||squq(%t0q#>~bi*K@!Qit2z1nUI$la z_Sz^<vc%<cl74*%EpVVKS|=KntxypX7W#D)hupPvi@*b25C<AP9uldMm8YbsI1<El zfQ{xy@|8Lge|Ln0)jPf^Ud(hfkt8J%@Cpr$KC+w1MCh|jru2H!$}LBNru=NB)@<V_ zRg{fPQR<_kpe5rd6^Ow^Jy{nby;hQ|EXOpwN&{&|wNG@Vy@j1sk>#cw91;sXrNc); zT)ht|Bl&2Tophi~vYL@b_>F!y$3|L0j=Mc(Z^-P{Qf`;EQSNe+uvRvHINB({2OB91 zks&fUKn5kz_K7GX#a;Aqqb5?)ZVtJF?fvw|rK(Py^L=K2fCMe!!Y5cHJ3xAE9IKlO zkSTuBO1kRl>Ohi%3~Kx?2VxneBotNaB2y@DoH3{P{FF6nZ-K||Ja-`Cc@VkTK_-UH z0SSf<wiPV_7cml`yqF0unHZ#KbV>9y(ALW{>Pn3$ks2*1j1(lrI`09!WAil!tw@(1 zx-&0KmyLx~iW@+*9+K^(0ztNq@}ob<Ah;Avx|bj8Ngznt5Ud0#E9DMwtsn%Rlf>2( zNb;M*bbm?6Tq7xDgY9Ls`Eu#uQ_WT@>5J>HJK5kv8cW)wE(aQjYYExyq`w>8yFQ5R z013v{2uZFR^e@F?4v@LoGB1{!KSdm$<Dg|y+EUP8U98z%C`WV}w?}jl5qn<5Kx|PZ zSP5daN(#WGPc+{FaC=hTNNZ%HP?8*_!vNK@-t2bauk;iMIRZ{|Et!bbOiOtkNjL%E zOY0&ed)XpO2Q1J4?<YM}Jt+pEh)Fx;C%ZXFFX>0UB6Cf~qA?oe7}@bkV1Pm}#<8Q( z-F#Yuu#VgnQ<{!tv?8K{bSo@UG7XXRiV3<PDC|JcHDnjYAe)P3fMnIp+79!4gb?)+ z=B;r^nvDALVYh`O8MxQw@S>SYD36Eos*z+=u7C#%rgzXE3IqwaJI-+g?uSA>>9*@p zJJJ?kcMmd7k1uEpkbXJkr*#0=^CW~Gp(L5&rVtV#W+2C_M+%J?=!q~zD^taF)CW(M z{vs3`73#IYMFvuIa)~Hkm`5-9V_nh|cA!bZrHu+?YII7A?5&TDM2qVuZLs24R}iuV zFvw{!3HDb!E3IZpF@;M(Mr*CrkpUl)7ug%Nb|Kt~Dl_zSPqeUIBcVLiY@>%Nbs@t> zx^$N^s?}OE*25qg{_nS0+b&2sXxYdhwzzg@hjVla`==fbjQUo>?Lep!bb68k(c11f z)xPq*VA7<@j2QEyk^BhMFa<<DwEx9<J?Td)Md!Jp0EvQ;o>+fEL9(3myX=j4uJ=Yt z;#QHayvq?YdNd>eM4%5wtXpRG)a@1gR$?~Ra#k(b$hXAAz_-R^z(X~9R}<M2JymE( zzsu|{CA|&=waRi_k?e_+P$G^ZaA~(VyFe7}a!Izv2?Z%jyF^kW)}xMEO-j7g1bTEb zmqaupaG>1~XR-#X)9iJScgFyu1Gsi~A!YLi)nvHT<s}0gn;(095-PjXk0=^-qqC6a z*xjLkGg^X7t~Sv`<9vW?vQi<0SSB~>kYfr{{#xukWL^Y1<Q)KpCP?)~(A*TUT%L)B z9G;2BE3PR34!{(+WxaZuP>COl5Md~2L8`KoTJ(yNRN$}%ih7!S2)Pj;##;4?mb4S~ zGC8A&uQfN~2n3B5E7Flx%5MuKnp_BsNwX)>M9~S%ELympCMt-OCQfgA_{a|V?lgtc zMo%`y<Y~mUbY(Y`i`H<^?ES$M<b%vaz1ih9Tij%_$!ErR0HIVj@IEw0F=0j_s5?|@ zypj5QVl#*m&~|uhIc=}^M%F^nxw=ogEF@+OXhD}ZMD}Pe1=hL)@3F=-GqG%{mqOB@ z-8*>RyP=*Qxg<`Jp_pbguDC}%KtZg#yE^u+Al+{2;f`gubltaOJv|1r?AujKsOf?I zeq27u?((AW6d5Vh#P!j=of5C@Lm2k(WNFB^RUnjz*ASqN^H-hn5#;MMh7?7becH!~ zn_Du(CoV6(zF<qinb>T4Lj$0SY=uz#20h1JvA_V#$7QtY^e~0NIFQq_g^&uQ1R^Q0 z8H{l#NQHDtJ3EB6HWk}Wyg%<lhmYEh_Rz;q;=O98QaZ9C&~U*%v~qM}x;IVGk?ZbG z(9-Cx5_VpW0Oh9A5qx0?LUe4sf)MuYs-_Uv+yS;|SBz20<}!PGm?+p}1G*vv=@Eql zj5`6!&oc#L^ce2CTQHi&EIBC>6_7SA-Cy^P-Jxg!4GBvDDF}$^Bt)286GYZH(ujp@ zX!nD5B*<c5U=&{>9S9;<g<Mf07$l-(<FanJZRF@95z>gaq-Y`lLA#U+VK#A6j#e_r z!zmP>rr@Uv?bBSC7D(yS1|ZctQBaf$k*zlXE(%lih%Z2W^o-9#6J8`3P6l0zu$Dw; z@G!Adpc^D<?&xA*FA<Wc@9vO@F-Vis9Ma@8i;#?CiEfWLNQow3v|cFQ=%kuxo6sJM z{>hdRwgxPO6D)tAo=f?#59Rr2x59Np1i?muL2cxCqhq+=UM9TFQBC1(k80Q^kJ;A~ z1&bX+;qIs;K8vHN_Ut_>s>$Vcn{^ZwUmVD|-SD@ef3WLwr$YMNi9s^F3>(A8sAJna zJ|X&3C*)Wu(r)Kl&0Z-94RBIMTV^s2PI26ULn<ELW+C<GMa-6Gqv_drA-S|;NHj$% z6HSrYL|?5nAoQ7Sd`N`6D9J^8U-U>s56H7^A&)Q7MrrZIFDoA<=*^BODVAg*Vcy%- zX-*-kc<eU75r5>wr9(<K95bm1N#Oq5G{SWc-sXI&5DtN`RNy=f0!@@rM1oE})>veS zSXc<qa0vMD?RtvTC!*<_0;mBS;XV1l9jJvYKjmiHgUs4sa-7Er+-~ymG>_hfChnAt z9Yn!)A_`@}_EO0PAHs9eRuU^fVJA}Wdb|S_yMlovOH?no2->td`9rrk|0|@c*#VNY zF#|$oK*Tf)nPw5wCuI6WOq-Bt6D2xP5O4C>a*`l{{G~9-OEv^y=)`D>WwV$QJxEMI z2?<QeQb}{}zn$PvU7|J8gk|-3nRYwV;b1zQOqc7)+wIjlhqE-w#~1a#;}$-ik}pwe z6*8R(Nj52xRF-pH^&SLAsYuso50B$)v^inp>2m{QoykqX!|8hX4zWBOd*dLL13vdo zq_{q{v;8E&sWwKr`#M(@#;R_+uJo>n0~-a7Qma!JalkLKeaMAye4x}mU(nCRE06X| z=1`E*qhuewzVxr}8M`C~J;nOR#}cH$oP#Y%2!BOS{zcH{ncQ$*%0;m~>YI(@3N|iE zbOq>LUNTC=wtPKlkn0dXqKbG2g%JQy44DbuFzZ9${t$|J5dfLp*r_Q4fe`7B72S<b z*`ga*dIw^6k$&8jnhM4auW*L22P0<$u_K=DvFHfY;fX(sp@8cC&gb}r?dSN6;Ny|o zyiq%f_C|R!;>ZFFGcQPin(*q)?hvW5bG@VkW3|~5NW%MXv!66j9Ipl7!WE>q?JT7E z$arm9Dy+ok$ds?qK=JH;IDt#TPw10lb(=lDB%53BblHOjD#_-Lq!Ob=iO~{HAY?I6 zOtXjY!Lf(Wje1NB)P-RDM4PK#M|x1g<*k>x9IhbO1&7WogmD}fikE{d#3HH<mg8w8 z;b0L``~VJD=t8SA<fYLLt++bmK~vY6ZLS7j7*gGWRP03&mqdfR11t}UK0M|IJo&II zfS5sXhzXeu3;j4KN%@(SAD1;yeir&A(bZ^=#zhab;4G9(3gB6mlJ+1UQaYEz8DzUr zpZI*JlnRshSR#l(nQcXdiRdT6X9GNhowziKb^<7cED;wZT2X}OM+u(Y?ed`~2ZbgE ziiQ3rB1s1QW@|7pDoHL9pUZ}WQI8qzxk!I6x`4oYlt@t$9h4$!zKyWiG<G{0h~=Zg zJ)*r}F8u~7mN)u*IM1c^faS2-a1tp1A0GHrg6!c6X#JE0N9@Q}JY+YI3%|S2nJq3a z&xcBoDRNaL@&g^;rvZG?<t&X-+I7@`Dp*pPuIoxv!KKe{Zta*Z?{%x0aV7BRduvT0 zUBj~EW^<JI4c8sQ3slRC9KhW+7{EkqY*e@RdW0m&h1`%IM<z)W>I_nekV43m2$d+o z55yvtNAC2Rtq{K!V<BwiK){B#KC!rviy#`+K6c0UG|l689)k`=AfEWM{{9M0<P{y_ z7w_uW*l96c^<1|(5LA{kmE}B)#pT&#<Y{~GcAgHL8-`#l@@#m@Y#frLDCtcn%{80Q zNwOcfT@SdD;bPP<^ehG0aLa4SLR8obiRdpz8Mjg*l(9b4r7p)_cH&v$Cw?DCBQYmP zAx(d<%VyE{oGj#Ad*Q<tB8X&a2}Pk1LmmB2<l!#cx7V}rpnba}!Zj#*6Wt+t6Q$Do z$9BK?MA!D*iHb$JY>Ck~J{&l(=z%?M5W@-0*yCMqCpr?b0zrQgoFU;HmK~kC)q}Rb zb;fTU5P}7{elqev6(k2?A9huB2=`LOW<Lt|g0Uu#-0F}!Q7}AAzld3v#q@+y)FHP! zR`Jj+O*>TIrJ}q+2jwCCjj^)FZ)ws65!2&id3<j4b2397mM17mg?H>O>m9i6Ou;i6 zBnE`t4|FN(NF@$oNsL61JPUcVdvBwaL0%;O=(@*9B^T|NL^-(+-C;Y0su@l;3X9!5 ziCBVx;?Cd{A>v8o;=e#l6aD?p@D%@f0aq&1qsN^Aji$lx4e$ePu+gJeRLLtSe=Sl> zRD?F>0sOfLPxDuJjSwXp{Kzqr^bq~;jJ<J`JenC%<e9~zxe`U5Sv-kB_ZQ?v6Y;R- z{;8XY5OqTiC@gmcoM@PScWe+x$kb;t^+Kjz#MEaov<!K2<Rb(Fws;&K;rFYNBMfkG z$&q6W$fUr2)Iep4u=h>D+u$sbfui5lXiFseoJa4-2#|1zzp&CYO%mGX&``}(`&mI0 z4%6FmTt$b1R5*}~!diValj3nTpe=`la)&(LKq{(I#TRr#u26?!L)=5M-i6GYq@CoQ z7d)PljW@1|NWt@Q?ukD$1EAu`B!58>{}NH;dmFyms50p^MzvJWs0C{mW#7@-McHhP zR;FfCKGMsPlxp-co=T?I6GbwPOsCW6`Z%JmNAac#za_x0m{U5G@*o262k|kF94Vnu zSDNGsD9~mJrTBCzfRYd?L_e4ClRmfEN=EbDqIukVByFH-NpBzt?QsJ#yl?1(xHfwI zWPozlW4)y7d+KGna+%JgCR8$$K~o}Ar@TnNzD)Nb=3a`{Yc)DUr+WN#%1e<$ZbRe) znY)pjBmG{pThpPqPqY~JG11cQ0eBcFnz#ciI$4pdlB**fvIbuV9-yK}g-mSHYa}Hy zgQ>`<ls+!X$(IpwB4>cCK$txsJ5NdsD9Diz10*?wI8!Rg&k+lSe2GS_mPrgsbrBD* z6?$@E&7^l}v=WU}W<mt0)<_tX8g;iwnL$=*NQtDyN;RRYN{&+Fwox)I>MfSmsEhjO z4TQm{H>ou`6;TSbMxCsCtki@l?~P<>E=+`&rY^0zCt4BVSfe(9KK;GRe-TTRnVAd! z8dip<AtNej9+H?I&m%isn4Xy~%+1WpWg~fedg4E@4zkF<?v?BcR5<ZzrVptNAMMow zL01sPWGKY&k>+3`(iv<e!4$mplQ_+ICrA3b(We`5a>8#<b~6RS_Hz3DFTXv>HX##A zm1tC29WoF+A+@ozw3o67O&N(&ZBi-qD#9R9JS0;S;!>H(h!!0QMk!MpOcI&Spp+{m zgh7@{m&X-obsB?4qA4{+CY&Ns5IQ|l45LAA%IBcJ1M+!Z`W<->6;Wvt8|8A;hhAAN zOO@#*gcgmDn!t+OMU-k4gc!As73sOO`f+Jydd0?55C|s}M2W0>tcWN@jNKp0qCV6` zsCW8+POp_o`nHp5wWW9hSQJviKqTQMmzmVEik{I_k;b4Tu)21mOLQ_qT9r(vX_dJr z#)C;AD=pO^k}{)AZ@9a?JR(A<jw_Cs6cqP~qiz@MgSQZu$f``TN_tWccG)I6e(%nF zNi}NO16?;kJuBr^58lFY{Aj(}YvoF{Qm>Fndn;s>CONhjgQ-+gA=60+y)3@asMXSi zPm0l>+-#GUkix1G5(AoyR3;~krD&azN|q`KwM6z<mvlP0B#wsh%w1xoL2uGxCBzy+ z=p{;JpN?flwGt~1&q+2|(Y~IGcIbGIYjyeqO^K)w&X*HWDV;#Z1&I_~6+5X;!S(Gq zOccu{<3FZ)j|m%Ok~Ml{&4Xflkbc$%yF*Q^Rbs$wCrxl16ZE54!)r;ze?%xpC(!tG z0u6l+A(6<m1~jRNxtWmWs<ejtyL{!Vh3~rf$i(F?H%5VEFaGEM@Ow(+xq8xHbrKfj zdJwFj5{@gNGFww^jmXEitf+u))&oTZ$f?Bg^j8M4lg}P>HulL{lEvkqkBjft=?TQ3 zvK%WERvZN~6`Ahg0r-DnS&>pLKoo)qg#b}N%`MI38Vy?W2cc4?mUh{w4>U50k@dz6 zbzj_2bmeUu(1Y9?8>AC?uXy92JHJ1!^SIhu$?2~9#=7oI46kVsJMA(|jg~}(3?z7I z(WZw!hfN_H;P6iD@|yjP_ZUj`fZe7Fr9nZb@E`ommI=?ay;vp9>7o>w@x2{Nb&_n8 zP?V3!yRJYj`gW*LN@auzZJloI3&C}?;=v9TS-3`qy*6EYKdzOc273K1Ed`zHZr8Z0 zVFjJ*IyZiRCXjVG^dq-a-nMyHgFkXh<!zdq%wEcSi}-J?ytVJR$}ankjf^%M&~K#j z;m$(6P5tfK3vQ9j?Fjy)hqwuJBDj@*w4J{r|6SDIu6@`1cSZ0gJ#^wfj(}@I`%Yhg zM{g8-T>3jt{7FFETia9z{z4zV%coyqv$0j;HuwM-+7OZ?1)*0!gv3JJaOr7t>Dloj zli)>o<waOZ(Hl^JqKZU2$>LHJo<}lVtYg`kyojptbrKQqjEW)(mW-ZRQC&(xne0rQ zN{m`MFS1m;$dox|w}T3Fqo)QtW&B!9*6H;!ly!c?49BwgxHT#ERV*6^U>pdqdeLsq z6mfqTKvU_jmfJrONrsyVCi<JVULkMvn|V{@CP8<L*=F*?r&cE9e0xSV`zQ)F+08CD zY3mWGq|i3X?xw<h5Jmfkr9c$&lRaZ!;6+P%;Ev-y-@Yg04TOByWs)7r?}|$!M(WW{ z&(@&~aqRMUDvSI}xIOXf5Qo{_9^LaJH72V$1$+NMz+@x6F4BfiG0@i9MBhD$%#zNb z4PPK+M?K?D$6}v*c8_jOB27YQCy9FXT1h07R4;{i^^y)GR`~vlDHSuN<xHuHBUYN^ zTBf|5DHk&3B6hV*r(vOhlI6A3Cn53Sw>D&e<Mz2-9;U;}M%~)FV=I!WCAumt4zYU+ z(?#j|*=Qdm%*x3wKwBd8fA^@Qq6h~5DX|C<=loUT?0fN-6@QgFCk69Clon}psDY|i zClXP*C<Tvw2Z9`yR8RDb6=!Cmzi8W+B8?&mX6KgrtW;#Vk+?){REcG}4kWoKE$j{x zi4C11OwUReqNbRu+-@i?R^go-+OYEw7r0Lz+UCaPvy&wnquQXWiYLlVLD)c)Dh(Y< z^Z1c{PkT{*PmQjKP%EqHZ4$0Yq-%tkDX0oZ+J+9rLXJUcD2-)BT>K`{8@xOAc0yxU zqi`m&^X=)v*I`in8^Y!buQS4avkygF$tL>qXuR7EB%5eOhfI(5FR39q>Pgob;$G9z zCEb-?7kvEZhlY@q#`@vY&;=j$V9dqmS&?skxb*iv_<@v%k6#5xZXaO>fDx%%TwK?^ zV|1B<vwF0xCJaWMj4QR9@Np`B`D}3oc_?7UNtVwl<OZprp9-R+h^5yG(GNef&@UVP za?mdq{qoQ+pIM@1mFP3kPl$dZ^vmMXr(}E@<*}pS>BX8#lfHzlHR-FFY757tw@?j< zCZ)F?C%Ty`gUCunap^3a=0(aQ&w|KP9C=D3PZ`Igv*J%Zag#`w{PrIC?HzLIQu<Lq zKg9GyN<U<5+*KlMCa`ck4e5B1x_KH*QGDuVamCWzBbP!O=xk3}B!OQF`0ZvZiH|?= z1vrb@TKiD!L&0>DiSo*EQ)TR%O&c3uv`c|Ib^_9iPvLCI4IX#oBg2dgc42;YR*Da< z#tH1mRTK&$cTxk5fgtHgjDEDz9e+;Jeub__{IG7y(JS)xzfOs-b5LG<Q*}^`=6ONS zczVEMZqV#U3in9lfT{g(iO^@!2TYa_+LO7wy*pe_Eu`-lKHjmg!<|+9MY-AKCH)U~ zBm()F!8Ox__urwD2jXw$7IwN>*DZE0QE5wzem2iSv*;G2;1dA}+J!%Ak6d-_*Z)O7 z84UTo0vlxwVHilK@6f%(_Jsn@d%IXc7!SWn!tar&P_XAMyx-mRlzEnr1ub%bPdhoh z;He#N9~KIjg@PV~<Ih|Nzr=8~mtgOXN-}sD$r?2FBsibLaKLz;QHVCuYO|V|p20=Y zou3GHgV)eBB`>m;iTD{n31%Vs|3Y5md^ia%Tfsd;tZ9sNHo`%YV>i14BnM5#73@U= zS-ciK^+LbF;k5pRgEIza^c(cT;EclO)B5)->^In!*1!LA{RU^G{~bS`!;i-~aNnh` zzDjK%D)9kkp}^&ZTX=$y-yKtJ(7!-8FheiGBzGVQrRk++g#!WFQjy3XMj1ia6?6*V zwwAyZ5TF=L5DS62MNWiqG{=h^ohB+Z^wDW|y-b2;f_x5SAByJ+mBXiOh&=)R;=Nyg zE`AgWQlk9A=V*yhIwskG-%R0exs&jRa*=B_I9^JVqfkv?jlD+~SQ-T{X1-J?z><9C zMd3>V`eNxbFN$8`(o4*PJ_`k02rF$V(R+vV?NnTdKSqbj_V@GM?I?h2zh|BV;*#L6 z|KPiD)Iy-wmu4W3aWBG*0WqZ;5PfA(C}2FruAy9BW(vPok<5U(6etV^E&gvkr0~1p zTXBT~#Bd6~x6$YDn{5b;Vjo1O@cXp$f&@qtr0_cphTs+TAwqqm(ARcRE(_)E??@Io zBQ6vOvk)bHtrt;dBTCkbIWMI~uDuHAYp8|%Cy_bfm5+#75TpdCGXdS3fR?Jj929gD z^o!gWEflbW2*DZsX>G%I0z4rvTr@zE0-frzhn|9d9qyPSbM4R9dQeP`)Xq-|f=)6@ zjkM7p@_KQ9C~g-3a0qB=3E~hP(;;_*Zvwg-1z?G`p8m+q!PpFuS1%WMLTJtwQjnSc z1blWbz@b2NIH<%zKm_=MbJCWc6S>|bpzMN(DG5L+V?7}#3Lu`%UV%`QD}eh8>A5=k zhP4gaxV#|<zUhWf3Bpd7)fu%kcwvVT6AkwFn9(2-G%Yf6%>hK>k9LWzBYi+@1PdG? zL20M1V@90LmBH-^zF1j=0%xg`P$&A331Ft~j`bU{3YyElvHl}F6#@NSexZN^X2C-3 zlcHCc{p`X1!QLJ(U{xQCbzLCL6bkT`O%PoLWcLW>^a$nz{Sc5n{x^EphSdz9NYH7y z1o*aGp@0{ntsp68B|W3?5i=lOB*kw>h~816c!T1%XL_UE(|}l)1L*VDYy*;V%z&ag z5%7`b+JJy^+v4h?4)G~L|E?vDAqw?BATmHrARU=aP=JjNVN9Tlt^^9Sl}2()7Ys0< z<@A!s#|k2M{;=}JTZLU<j^2<-zuN}4%8}zG1KuZt0`L#WGzE`5Be=WcG^0cHjg8r< z(UbfqX`S>6o@6}PSC8;IqU`t+VYF71z>Pzi`{R`~K;I3d3DIi!=#PB4^g5={b`-Iq z0kqB06auorN|LsKAX`+BU67k6D#%F}h|#=oZzv*ogpt5j;fwn8t#1z6)l+^>09ifB z5HOM$DGWX%nK}4H=HQq3*i&PqCW6V>pe2zk2md#98u{+_e-<P5Pi7R-@=tU}t_btd z4p(CKp&*95i@uWH;RbPH#P=cmivl{I-)*q}xxrre^Eh%mX1~?Rr9%nk3!;Z*5mcm= z4qGt=t4KTALEWAgpq0hyA{fMazEFUoEEoQ=xG@!RQh}6Chc8XzFS}CcTd@NA%kv)e zwO9mv^xd@{5v_>kQ)<aZ0epP`?+EeHGY7t0=K)MBr5yMm;qJJ9H>I%sA0By7)__kV zkmci}F#&zKEwW&Rg1*M~!x5;jk9wIW+uKDhr$i_^_0ZjE4rsz&e3nM~gN;vg?l3}* zqdh!HynjT9Vf4nqqyR}Z3QDo#909%*@rY0vvHSLNh%y9r)W!@0$l%5%kYlqNaLdI& za3=JnSmaDX@gqMn;FfEaccShS<tBQ~5ZReE{h2?xblKS}0Pj+T0DsL9NaqFU11+Ry z_@I%69Ps^WN&w$*2VDpd1oZ70)GFk)r9auJuKh$z;30!f$`+w}N=<nO;5RcgI|9Uy z;C2k9-*b!V(Em_PGJLHZiRXI{8vMfG!Gi}po<3;s^Z)GE|GBgmUwWQ3`0>9n2mhTj z=!Nv>)0hqyn`|JhF)>-;Q+a_GaHQK)g6J8bps$3EP3VvM1#u^A58;<)@ddza^k3k3 zq@S9?plh}@V&kK4M+)fkNddke`4n=S9<-HA?`1&P7ND??M$*Q<^bwZXV(n)xC@9Dx zvvRZZGIR5Vxn_$wixlPL3G?!EGOeOqYk}34Y0EFLagmv$;Zrk((f{@nA(lvG^0)%j zosF~)q|1vG%Honzm0F`M)9DSy@`}o;YMQ{|bk)?lJzmOJ=MMx!_2GubruI(KGP-Lz z+<@do-nnw=+u%%#Sr`$P-azOKSmV*8%+a6;#VKel0rO*5E7Ox{=%NoqQZfb&l2)k+ zl~VHj^BE{4#xD({|D;hes#L1*HzTMnDYhYI3}!sXc#M^vo{@|o)NDZz9(0ibCKX_* z%+?3-)m(i0A_FTJgnp!eHY9<BC{@Yx8zfCcvDl$57ZX(m!d-4q>4~tyQ6()eD=U#T z*!0FsnL}43&#cmm4b>HDq1EHgB=up3z9Oflv_=+ENy0^h&?w87G^pH0QF)E6ve>PZ ztKC*_wcF|vORFo3GtCv%zABL%JqtognOtZqa@IpfWzRhGz#ZNO@{uf*617w!A<Qy~ zj3^0Kdvi28dk$GH%Fp+R-DH-P2usVVic6?!CF)a6l*uIGG9p~rBr8=BwMBdaGbeVc zB<1C*2BnlB>B&+Cxv<z!rWebqDs@iGZ&g-#qq$O-Qz{V`TRf_eN}nArC90%+0*Rtj z)>s~yzR`hZ*=X~WH!5X#mO5Xx$6bSZ@mWOKj<U+~Omk5I^rRAJS4s`FnP?UUSy*Er z!YV2H$thGSLupx9-K1<%8)}7`aw^7gSN!sc|AynP_~jFSiDPAvMMY#5N%VC^dZi^x zS|$?{Wk!O?E-FTnluE?Tvf?zNxKu+1ib9U29CIDzk(TF%-9%-T%5ToqluGTI+@|vE zYI&J7r%Fc{t)7gUaE;4FDV2O8(`hbJ3oCsAw0reeR#T2@N>0_*7wa3#JxX_Vz(tVS zf^2!UrK(I(T}c?Ml~rk;ESoA@C@C)YIZ+QGoyc3FqYS0Bl&+#WOv-KL#pM7wK*qn4 zh9-^2BMDSnbG${uaCK&lJ4>Hc(J0QzBOEF*LC9h{!YAUiL~3`4-C31+&OoR!oL%F0 z<SFDu9%rpCq}D2^(lTk8q)MW7c&ypFlBUdZzet^1k;Nyf$~2;ihH9TCzgi|C8kNRy zgWsL$RcLD}t(H<xUR|)j8fv2IYef=KoiV7+cS*CF$_p~86(LO)A-3`hgc-i9#+urS zii&(qZcrn1WEH3l#^MT_-%wl`He_qGHU3O@fv_@Pn3tic&^Q!L`9|WIXZUv)8Ds)U zqlNSv@ISO6z&BgLl4ZtQEm`LX%cGVYE>M)qjE*u<L8BEdc4YAz6jH)Ww?QhJX*25d zVrg%(qS$S9>l$s9>P)4)xZaXg_6#;eaas*B#0r&!a1_;aRueH{B<{oon9+a5M`t$p z1Y#tta9Y(GLseK+qij;usKP2_Ru!mBC5@E{v8h$mh~=uXKv+@+5-5^|i;)=_n`Big zaTOSu#HrHiggQAgih8S8i~xxSD3vZ8E~4col{KhoezV*z1Y|}&VfB_b*(74UMO2W9 z%*B2ib5Ubsag~*b9c7a2oZ7TpQI0d;TG@zZp)0i;q?wlLYE6Y9Y%I|<<oSwg48|ZC zB1`K@qg++)Eeh!KOQhP6-Pe@nl2n*w<<9J^5?x4<TbZrm6OFz)yRR}sD6iJn2+hSs zD7>Q#YFU=NteW!YWn~9Swc!$bMR|pzwxX)iTB3AS8V#8hbq#r0Zg(&vhffGgTpA>t z+ORlRSDaZVw3L~NswS_Da%X1~VFWv6gt)5}Ml%x=;*ur=C)R4af|NVUs}RB}<)vhk zOsf=Wy;{3Unr1H()s@y)XXjQ_8b}HSZPE&}S0l~|NQ$WfZ9{fRg_}<(Ye;idez8(n zYOq*CT4h1donP-Ssy7x;#-OAqTNtdU$j->oNae_<`Ftg5#!_2NQJT1}Qjt+W_!{~B zEfqqU)R)y{Q|Kw7L06nxo=fIhbh+7Nxml527WS$&cAvhoiho}xvng^@7PA|yr&Oi* z-%O{tiEz_x0tQ7bt+J&Zbyi((QIvyDSsP;>vqmM6f&LK<Dsx2ta;gwGmn({e)~qsz zPSj9e<&g&x<84n;fo;zUw&(8B_7wlC?R7Pg&htRF_x~`H&erP)YoxL!<O~}%QR|JC zwOg<7f3#k`HC$X4ae@swU0bigB5FWEh`Zk6RRxqXcc`i{yFuARIHMR;)Ri1$wg8%i zsMu-w@9e)^nky0I%O%!ag<ruZT&gUaJe(a0Aa7pOl&LB383>oppb!UKW$t`ecD2}- zYxjinjcKy15{24f&nu}khDp0I!yT+BHt6|;AK8Yhh$M1CqDHs1piyP5b1M`jxs^nz zw^~-}O*7X9gf*1fuM|q^TxDVnX|`&^Me_2nu*#WPnT;HAc12ySsMh1G6e}Ile7)Ri z%L$6}<)uwQm#6Gc?7t|>&nJq)USDO<trLl?t}1g`omS?|%FY$%`+_-5l(S6gG-Tz~ zg|ovARjLYAw#*<Y&dF2D^6X_QXQnXEUBD-@v^r6FrN~kxEUU`3OY8E5jSZrzphV&K z5^iH|Zd$O?W!IKdRhBeO9#K(QDGY^$xf;1zRaWZ@XKMNV-PPLq#vDVH*O_LkRF(%D za_d5dhJ07hDk}F|>by-_k6)7I%i!N@aGJwthoVJ$X3C2!Uu+Jztl&76iqcMsW-c?x z%2i?&t!84Cs$7JWrl`Epf}+`~O0ip|Q-)=9oTZS3-B6~K>*`Bw$eN03h^lCaDJ$1e zRh72PYNSaPQBISsq5<h>j#s63go_<f9WA&uL`K0~DT<#_L3xu>rt{f6?j~BfWno1) z>YAKJbCEpDR)iuv#GO@C5UQ#OJIXwAM5}U?Wy%^#q=ZN8sIAL%*1C!c!kJ=8nXIA+ zv1|1rY(kb<T2bt@irh72iYTW!H5O4OK#o*tK#)V#IgmpT6Atu~5^CIAv@@ctOpevO zu~Xz)Q7dcU6LJG#patPDNaZ%SHB0BVdQiM6l837eGChjR=oSsCVkpy#jg~AMa=#SX zQN%#2K^~hW0<MrXt2}@VH&opuqg0Xz6f~%i!$C25k=tXgP&>h}U19^DK!O#a61TNc zEHhVD`_NSDEj2QyN}L}BuB!4Pr_+XbQ_9=LqLk5MK~74h7DtvVZjVtbmOI^H)O!<J zRs;t^S5>9jSCuKwjPM$&5)}k-{Ld%Q4nTxseN~6}Uy1T4STVH6{|;-Q6M*}~DxERR zN6{;+xKb8wsH{dngvLd?D7DjCWNeVt5M^`=t3hIfGDmq)K^B^&StWMBEcpZiOI>W9 z2rw;Pwa}tK@b9rkbR+`*nYZo_qPquoO?9OTZ3W#TGny-Q97+RWj{&AUrs`$wauG{F zE{$Ten#ihP#o{2Pn9x;KTibOvSOvPPCTtO2OFf8JgGB6YBGhOP)Is;q=9|!nYtUZL zQB^B(RLO|?(nf<(ltmb2X%abEt#nH(JQe<&`m%aTt4qs4k-M!Dg%C!M1%DGnstqMt zVNsL0ETpqJ(M~nTR@WdeGU(GJ>bxehoGNQji)!@9GRR_g8X?rkZ6$mntSwVoHMRLV zMX=27DKgh;R5h8|4xP?r7fQlbO{gGCXKy4*T&3;`Nn^Ivq4qaq=Vbfx8uSH{T0<TJ zPGO)b(~@6P+lV#~2uBFB%AgXX9SUA$U8aF3Q)D2h4Wl}7M$@ephxTD;-i|UNnC)}> z15#nG%Vw*ld}U%qW4%pQ8#ILLgc)^pR-&?4P9SGoqA979S&?UO5NU2w*o1hn=9>$I z**<3_p{*&^laxG7B6Au`t(Cs|qF`QeFqEaQ&yi-7`trzdwp^4(DphW`y42z*E65Rx zgj#-1fwZ=~!mSjO1=)?l+RW?%XR%f6mfFSTp#r%q<jiXH<Z3dD)CD4S1!c>31<+2P ze_v-9*7`}0E98j)vG)HAh`Yv4RajXT#ZF(B*lAQ0yDb%Np#=dp8oW*=aTH~DQdmp8 z!djyZj2%rM@UsNtr?2Es@w2H*{JafzbqKn;#4fqGvARO&L)u=ENqd1Bf{Gs(oIqBb z6R4zH*eZk_EDr@c27;7_Gjr56Xf%vr7{8=qGb(FD`9@U}{uy)B2DiDZ1q7@ApO!G{ zO0m`dzd2Kc;q+<L|Ev*64xM!3zf}o70si1Gg^SYS>Yz7UoN3U8s=}fC5=Ff%vm6DU zVp|4T+)%Ak*UEI3qHtcGJ-f`VD<N~LgAz}DjzeWtdxBZ2TzyH7CR3`+aw}azsl`&A zpHVCm*(#LCj|7DxgI#IPD9W!<c>N71l9d}A*0Q{YYDuOHc?6@M%&zkntL*u9LLttt z4&;{=m$`D&LfJ&F5qsonmsKY>gz73XN~*F9)sjH4PHBsTi&TRuEHe;Vyj}_&^6vJ0 znb?4I9^qMZ)_mgDV6s-^vRgAtYSfLTqRc#@q@*!alxA))RC}b(Kt5%W3oAmF0<}$> zU8hx|@Y#}G?yU)C`$MF%G9ayU<P*6rqS$ZGZzwCb`0JI<^13RA)|*l9EwgFu+BB7< zQm1axdDH638%TrL$$zG#vXsb1K|`9*Qmd(R*5??CtBt{+utYAdmTDcPMWj9;GN)D4 z=nKk3VY@WfndVoeNrfd%MaBI4J7bvKPvU>#!yN%J-T%)ZRu)#q&L)Cgs;tgi>_+D7 zG~-|pn|~AG%kOOe3PpSnSdHwzJXlrX4s;m^mI9EO{}jkF4gUp@)v6@QD3DpY4#H@* zknteK=s+wGE-C9Ah&99qVjiof9GRcoXOZ1ve)MtdUkk=;vJSx*a=MXl?0*f$_(U`u z`(J}Gy!XtG96sL~j1@}>S1cG4b{UKz^NUGG0$}D>NoqTTnKd5Fs$;s;0ImT!l}=&V zp9N*YB5$RzRAQ`_qwSf|jsRUMFB0W?OLPIHj<oAD)iqfGeO9*7ZPPn5HMPn>RU>J4 zR|ZS-va+k44WgplI;|~a&8?^w`z7+C92Cy_6-}O+G^bjcC9{`FT^^!Dqad}G9JjKX z^pk#~(rfXHbo!dgikw_K(dgC-%QM8zTAw{9*IXPduj3P;CP%HLsIICZPhqBVT)GU@ zB&XajQ<T>JrJ$^HFj^+3gEFZiuednPP+uc-*Ohy#Ty?eivP@l-T3laKA+D-2hdm*o zQdmjUXpuf>LzLcUE>VbzCCcn@r8GmITU{Kk^vkTitQ=8QWw1C<rI1^xx+W`0YP}YR zSfR)>s!KEuiI>zPjmpqzGkrmKe!yO1t!z|zB-KTPwSX`d734_ks#Na$kl9+=P+5{u zXZE`sHlZurWHVOj{5jehU6Yq|@i`70mZe4@E$+pTQX%_4-4!bggeCUkMc%a|R~D&j z{>+(|-qx4vYQyr_&RE<IjP37g5PVdHi-~sl=ttv+tqCJkb)_;`g^3lNwjW4B0D`h6 z1cKhib`Ugl4T25Q|J&VgX1FNaz8ltcy&G;6*Hrz<ju^oQ+7sUfjH^{*YZQ#j+QGQ} zEgo8Z4^ml4S!cMYv_>RcS^;p?)X9Uy4(v5j0?C1nAR~u<II65sHPDBC#+-_puB8_* zI}9KHz4YQ`hal{KkY2p(5RCp0(u<cJn#Es~o})Uy^ZQLPesjeq995Y}FN=x^3Pn+B zO|7vgPnjXC_U8l&Y{3wvGAqgzRGnWUqRQMEB{i8kp~CF67#+4^eUZXZAmkH`WUaEw zZY;7G#38w^R+3v@N>l}dYEN~Z%j{^7X;fKh^&xewq*5x+@XP%rma2Na)q!F(tE^Zj zspJ!-X=ItjN>F7!Z>2$|CBqp;r=?tPue7SGvaQluQF$mFQa5Ij{~vE}*5fwUt%=<v z7cC$_CM0N}5qtsqAUROyh~iA3zYESY#d*3DNr|&4ilR9D`jD!@WxM>^eto(Pxa^`N zid<a#S<f0bfrE!m-iNtp?VIH;o>@7*SUBlSnkxB>#w6?tu7km;<dZ#qd*AGO->XTX zcGt!AnXxJ!7n6oH9%EbVP;|Gj<q3)JqoZ@u(wLvvz~nQ&%S63eTvKQ3>PKF=5BIzl zuKWBBigt;f79U0Ho0*Y--ov%AKg=fi2=jV-l*2yc=@bJ_8YxTHqpo>-WLlOH-j*#4 zU7h5T+w>T2{_i$vk?!I|M5#;|ex7)}mL9Uc%X77l+4!Gd0+6o`?^jTJ_;-WaAA34K zh#P+KckNdYe2*K#-?@zMZX$4hQ`=AJqO>CAmHsJG&c$a)`4=elqqyDMWqqCL{$bp1 zAwl<}@7@*vb%mB+JMSx1d!vf!FU0NO@8WjL8U8+1OjN3Sty@^AqVof)m<FKw2XygA zvAZ|fteUi@OWyJ8^Vr?H{_J0ksPtU~;n!#)Pz2zydH6%spTLHrf(D1NvRufXn?#F; zIz*Kj3fP#5ntinh`rc9vjw#!#2cuiQ2w|T<&_%)yZJ%}ueH1e%$s>S28w=S>L5s@F zc`xZoH8tdg8xM>!qnxYYcxm=v#>r8dpOpzJw#Np|E(g%8v|tiT?h&Zwat1-&wdqMC zSV=<wIuV%ow_<l2@@tw%))qZ@Lsg(C6#|Uhw*KDO;rFw2;;EBg!%#3ukaj^Q4mW`Q z?5uSQ=UF1{VrNI##FY|*VE3c>cu-@;o^hugWHyop+@#OZjJEr#-$#j=Q$leSo$v@t z>gJzUa<CTsDR0GY9Du5-o0*#!xVj@&*dyW;8|NXbu)9N_YUUUe+vuTO)7UZ6g(Utj zY}@DO<M>uZ^ej4nKS?=~1M{9aO}N;17meg#)dB{2c&kk@-;cl5vHW9YgCE)6@;jCI zW)nfuH4)0!jKo?A)YNat!fFWy%-?C3v!Dxg_%wZJs3iR6gbAk4?U%31P7Hfj>F&+; zm+!}E>b-`>ctMtTM&Nu_=>so#F{uR$Y7Dzq-DjQ8SH~7ySP;W5zOVFQnN=}f2r>n@ zW8zhH;HRTvlef1OF0t;oM)jVrr0=y>U)S_hU)5KAvicTe`DFbKE;xF8ECxNA);Vyd zdC=teJfJgd3#R6Sh0R=yb@QyE=f(Ls(eQ#F2kkKD`PE@Vc^;2@?My|w62kP(Nae%s zzL<^EoRO5N@ASvOXnUrwx*Ke6$pu-Y(sdEl!;P(fyq)PIt;RFE4^??Ig`$7RMAQd> zi;YQjb~L4MNF2=%ryZw5>4=r|au?6_cc2$kAJUZNRN=`16Jao~DjyHwkt!OVFa4QH zzSV_Q6P9Q32fDCo!iM}*7Ytya-)!w*GEO;+D%HF&9MV|uf~S~ywIk`UArFi=sdfOU zwljJKsXU`+2F@<HyxdvvVzo`6pr~}<8q%>2VY9U!R(JJ%lStPL6-DVPk{cJo7zE?_ z?gK>yEXNl0Q7>=6O?LOdIS{Ai%jZK-N;^j-hgZh6<`drd7XNN5LxKVpo<%NqGIpF> zsJ+TBHZYCI?~=lXvpmgbB#$c&^67>X1iGJ78R0OG1ENa-OTH@E>_VZAR&?2dWqmj~ zl5f`au4EIW$UHPySX^7|ysog_NUCG^d>Uxsrd#br1WJk$L;AFv`!gzf&dt4qghqmM zS9^-sBs17^LXvIv`$yA;`6*@2?IqsNZLIT-=(()FyW!YWHSOYQpH2jV0o#s(VtDhf z%MGrL>i_tEyFU82|MCyAUTfel<>}V_gY3G?zy0f%@~VYrf1*S0t9T)4w8<{3cww{e zR=rBH7|a%1odB!F<z~`dUsKj4&XgQ&SO{kTgtnIN*`@le3riU#+nbK2+ty39!3(8% z#{b?a&3}I3G;S91`)gEE<<UzP#+Lvug$^G`+5H*A1K*(2BsKD6NF+(Jp+&BLjOUMU zNjJ9^Q?AZN`90Ks5y(PuB-WVO)fm1xVzLWz^Ko^rAaf-G1?#Jn^S0m)h9gl*=`uC| ztQ8msV}eKCCjIv+F!qat-s?x6<n$%d^))x;Jg!`bNN?~95JKAW_oZgzB~ta0#8?)a zO+pKG8?{+o=$p2tF^1<crhGs4SC9IYl>NrT{`OffF4G@8?e$@A$e%p!6%6yw(+ncT z49};TvNG8$dSK8aFbe6(bs1{N)M;>O@*rv{5D<uTK<{Z}Gj2`jop3O~n96g$!LV7~ zkbtbH9p`xtY}S;)98I<+LC%n`md96zec?WcA(9UnpGfedPFs_MA2U7#etE(VH*S%n zZel}k4S)}ixJ{<li$l5SJQC>wAGm&!(*ezF#Xzr-cJ^}F$EZ3Q47b^xj!+x&&LYW! z30t&rl*|%E5idR%tvmzrgj2KY@9sCu%e>QEc5#sp*!6G)pgW`=?jwnou$6_~hRm&- z3>K<=iY2!=fN{T56mMi;rDpeIUa9h(Yy*O;yvT;<^93M93v>aQyV&vq!q?>*wmSkg zqKSyD0_YJM-ys38m__c=a=g4~%A2Fh6{Mq5;zu`ERLNj9#%Fv1`t-AP$1Klj`Ly|v zf~Z8U!R4-1^q?^ksc<`&L4~dc$_?3CxO{$qrKvEmOP&ZG0h8%+Hzc7mJ)kbGH{41z zPr76Aqvnl@Q<4nzi8SROc6i8d8}eb^Jm-8P@7x~ZFGNXm2r{Owiu<?3_Mrn(W|*r< zS=k64PXr`RqNfE+Q#&XG@e+g^a==d~9u1+Bo}KG`Wa7H$-$XR=PQiIsoj}mVGCw3Z zjOQBO5wb2P>s{3hNp9~9LBu!YDI{>Q%x`7~MI9V;1%`7*qEql_7ls+vfG}NRX9!WY z(c>x|&!;gxwQ4RaKq&F#ZbKa4WFGiOIiV=jPA=0`&i#aTJacgq?cgRc2%fr&OJ3cJ zWY64~YqfaZ8~@z=KhJ^p$Lb&J1_sBq4e1{*WrB3`Z*Qf7H2IJHLU@Bu>gC@vGBP{f z*=03e6Mjv@E+_;dSt7X88jgCcJY%8-=higY|Nh46)|z*BS?nU~jGv{AS?xl)Rq-jw z<v%64@0pbaXQRdGyDYS1jsM%rwe<@{e3F>A8a)HV7c6>pVHR?}mYZa(vo{&TqB!*x zI}dBJ*CrRgi5=A9iJaq?W5)B?n{?h%wtrtf%Qd_v62BGDg3iegQmD$aJbidp+@*`P z5c%so8z)JFwpm`NP@_J!F6J0_rAc_IW|5Q^<Z1z~>Y=(GV)-IT3z&WNBvzZOMW+j* zW-M_VMxkT(fQl()b;y}B)$g<2uIGuZa?j?oCJa(t<L85k3K>qS_iS>EOYBw4v#>6R zWS_vi!C=}Z8JTR0o;1WUBne9~H6SUD`_s+1$Hwxc(S^z`vRc-Bx>`}%qt|pP$@5!w zMQUT==psqYRP&Nx#o557&MF6{-r_@Exp;8x@7;<G`6VyZ*gUXUN3a5(iEXgDL^TJz zv}?RoIBE~2VBZ3AD*;{{Pm|LeW9<U5*^#kdDC%mVDOR)WMIYoBifSx|cn}*>SMBmE zeOU<Rq8+J$uO$QUYJXq;B`0;?yijEm(3e|W2qw$FhIkTjFani2k{o|sEcf}!UCk`D z=~RNQZWRSmS~0Bxs}%5jH<KD3MVal%N6n)X1nkhz3F?L(NC%RE<_l6YYXa_>n*NfO zrrxt(;MqtPtTj}XY$A#a0Hs*olUl|nscYjkbxkkkKxh(3WchnqRb6uyg*^%=+Y2i^ zM0|)kb|GhS3Lx7Os}|dDbSFz{saTU5IDQnwaSXQN0j2YqZOi${iEu9Kq}BlV;@<-h z9?AAXT4kGQYO)ic?dG2;PgIea&K3NikMN}r7)Y$Q#7>^2JVtayKWG?y31KsuvU0Gn z?24lvprHeNt$r2fpwFOb`W%{S%$|Q4n!W;-;sdZO^y^@=kXnRafaQ72cpm!#EI)vS z=@(&Pw;@Xy_sbri*|E5>0&(DOyB*cwZ5EBEt!34cn1dvCriMMvcm(&jT@V_rgQDjT zjQi-Cg?5}+ILGY1C53FxCNQU)+-!X8f+j=j#Ey<*r|?WS@j20@dQu<iagQx@O4<>k ze1VoT*oLl-m}HGvypYI#Zzm6N)FXzOj229%1VOkcRYB+SLs<B-B*oublCP3pmt^>H zNiH;)MN~VUwCWd@B!w>iw{2xr%FL4Cp`B(S$BDrDEmK8bFG=H+mOv9OoMCdf{>C`H z*F8?yBV87?H~+gYN%o|r=sw5q{ctIroQmpvpQ?w0pj<AG5ksyAux`DD+NFRH3g(6! zlQTf)j;{MTbxF5lh+Bru>xti<^ZvTq698DLejm%$UftEk7E?ld*S8TCDA8iNl+8aW zsOp7#8m5qbB5cgKC)mXTGh%UQTJgYb8zEyaX|b>Mi*O#hXrBftS>@S)ip;MrNpST* zuivbweLS<8N_KA^DC;g}{>XfBvlb`p;DB7Wm{m3K?rWrI4W*6BpW1{s_45|bMOmrs zoBlD*vL;`P<KN!!5^4V0U1Mu4`^;7QAKtUA;2P=cNSZYmLnvuw@lEOT(@z!GAJk`Y zh&LH+$Ul|RhFs<JCt_Nz;Tu8yuAJ7p__3s-kHl>G^ztvP+3t%=yZo*e>9|IhUXK1< z?OEvLC;Ia{a(Pxz|D^)`OfJhAenW$z)k0a~SMJm^;z_+aR64n8meJ5p=Sov}=@wnC zHbjkl;W|8Fj~*ehLk-`iQ7lZeZ5D`Fu<CKKm5cKL=`06A^|50ir-jcIt)4;z5qC)h z-K5BHhp-FvL}H610Yn~v$cDI<`(9p<hN=@n5f3{m?D~l1%0@RcYg~8?!nsE#Y=<;M zYNk0r_Efu+dDKWoiFoqS>Vb2k7LA%6O^!t*$U;A}^Vw4z?y+y|%Oqv&RGuS+wyL8Q z=|?QR1f32{e!K8jzz$)#H!XzQkcf{=I_xDCFB(WOnayzzkq6Sn^aGm;5?a(qTH$f& znIw?zAKCS2Uhh@B7lbSv@9JaGsEc;NuC`23Di@^TAYwXjJ|G<E8Oq{pwpUg^NS(>D z^#nuyYcY-eeI~?NBhbrpV9BO9O*(kN_Hc-}Y&ll8dcz4;;@Bz=#ADS_Lhh(yLRIlr zIi>T43>7qL+x>YFuTzI!o+CFYxXAnC=@xo&x5rE`H;5znAQL3?%=w%o$aHzQUM29b zt1gnP8UUVA)xR;-cpL;K->Z1Az4WJ;DIZsH3WatSkdqVKpmuRp-Rj)?_$7S=b4VTz z+Hx1n2qibkml2>hP8g@OiOr$N?bzPEqM}T`uz)i@-@J0Zf%@fwqle>=oS=U3U59ZG zwi_~a?!08qLPYsR<-mGdh)_i~z9flu#uZio;x}UPK7zEfIgHeta^CK0B8IPAFPy=0 z^RK}@X|@f5+Jq>AS3Slh(LCLmIKHZCjA(i06$Y_(jsBvQf&U#1gZMMUApVwN_<>EJ zpy(O4WS>8NViUf(zTdsPU&iB6ofp@pFhJD~Kj3jZt*q<i=-<cVpGo~^clB3TIPP(A zCX?`Z9C-08J<co~-ddOoXAik(q+>O-5-mLbN;F4`N|Ca>!VN6YgrA^}Vh%f`y^Y;j zjv7rjt_3Egk0ZoGUK>`fQ=hLgd9a{^w>%3fY;^8i-8^6N@JEB}<ZFYa_?Z<KWR)P4 zL!u<@_1g3y(t;^g*a96a+C=G2mdlsbf@Gd_g${mCax$R~{XH>c?h2ZQ5<4MpGo*BX zNWc#0>*GGW8+UEb@_puHjF(JMdyp505b6&49?P?!4I773%6aB5mFOQYKrnIpsKWbu zt((fuAo>7<&iVxNnx+W6!!|W=GPa95=hBZzhI5U^+#`rG{iTMJ+7-dHyoS7*_vgEi zG@hE3wn8V}FJR%_4i5f_%Ve%G!<gKqgNE1<++5R6AS;=h&$$UuRCo89)Eg&bLxMJm zk7a(!E2P{WxiQ(|tTf^Rx;1J7(tyb^0=|xHm_0m7`(aMg93-593M?e=`C_q+A~Ynd zgmCCNAfq<Y_%op^bgjDp#3qQmg%{=A?)Pj~2U)9>Q&UAQ8cfWYD|e&LD$BD#pzd-5 zW1fV!Ls22J5;KfZC}V#PNjovv!>k5oNbY5POoRunFOmk>WLD=gnptGQ!aYzktPyQd zSPIlxQgQCs3D@D_Ua#2TH1YMkceM_~s}ce_o$2o2L}L~{){dmMk*IC{wWr(!Pqye3 zxyyZ7K{p*26fB5qKi#UFzn^glzOQ*~>}@2*Kc3rg{bT=+ms#=uShr7Hr+IzZ2Vfo5 zLiYFUIU4BmVHsvTaK>A<8}UM4x<0Woe{Yh7RK|H*sl5R%J|Co}xN1>G;z`}wsOJdM zHS)OWWc_?t#<LYzUpdY(SaDG6FNB9{yYj)=Td|$yxV^gHdBW*}gRfWrO>pjST>pk# z@8ECUz<VO**-uiGU-gq57!rQzDS7QD`3rf!H8lUa#J|1XwWs4(uK35*D;x4-{v0vu zU3ApEzyd={5WtW?k>b=tm*z;)SN5<i(&`2r#k6<lR;D9~1D3TX;A0h~(*STsmKRS) z>e?BNub=N+iX1_VA~N4?<#g9(B45~icg9Fy592@xr_yX1i~kCj3wx}~^NEy6@IDHq zRbyIL6~5}H`w0|IC=aPO3R9J)Rc5}$TI~YfzhKd~h0Nk>g$kwWSgwq9amGq@W-91< z?YhO2-J|MmYw<e+yd}9S7Gbj?S#QLRZk{hR#!@rxbR?ng5ZX!}m;Axrt_2RJDS3lN z>oTUfl=O;_q}E~KeU57u)S?gEqi89ocg`b33u;l^cZOjwgFWo=v$M@aL_7i~l-+ki zSM|5TF4}H)kN{F6=>R9Vaq~Y`<R@zz{Kd76tNEWS$LDw&hh}LY+Odsa91kYCJ@1I% z&OOrZH^$R^l{KuwYWbRH%>I4AiyaTzTW|Y>FcHtqYcVQ0i5#^L>TzwgG?q`@17AXZ zaXbyU7Ui<j*W+nK_@(siugBB8UGK|ynqR%*&#rev{?ZlyMFZ8#c$(Lad(dn8g7KOn zg{j*-*8)MawmO8WbX4xHDf{E`ME2*F87w;8=D5rF(*kS(f8U<08KqJjy*+{`!UStK z;f+&mnX~g?Pq#B1W?RzLCunGp(8^5Tyz|ErL#Wa?V2dILwCx`r3ua`UKBKesn0FR9 z6nmGZrP2tCTxa4_6H08%UM}8lk^@rsFmGcanco2SGpr>xk-Te=!MA<?o`#_;1y&9~ zFrx_!I^PNO774wjP}P`jND04kP6vC~jPv2)<Q#=DRY%7>9=A!*fCafXZ`T`EVW|@Y zq_r~|h5+Qe5mFW-P%Yk&H&l<2{78!P1e<~l(Jdn7AP3L1wadl!!t~dfcLc&V^dZ-T z>Lh}dT<*YX0Q>~M1>Y@1=$9Ab^zX3{1sX*tzyPfhxEF<d8g#Y4;Xhf3i(1Zqwv2vr z5zOB&f{$H*K3AXctd~jC0$2@a4ee35DAAe@t!Lz*0Ja{2rl4b~6bO?K$!)fHw$$;~ zks|@07q_qv8?wzcV&6_&Q)}D^iI-V7)$!tvKyi84fHEWrE|==@AoAOQ+2!5B2lvDv z+sASWqDvPx>9jvo*(oL-7x1{tO-?07kUIf-bKy#xcly-Jxa-eVSh=fg{#cl0K-!~W zL&pf)k&T&&^DI;2rOXNZ-uWoBV-|~2MMF}6L)UVG8WVXraQa~1gL)7G%7uB$&xH|) zgdIYVnWyfr+*V@+X$VcGmV9lB*B)tN@qXnHJIaRX%w<2)rG6$Sa=^y<Q8bgwt4J-M zk?W2;N6B<iOYl(lXS*$S3sWsO|MhS3#DDyM|KQhftw4QJMe<bzj9a_Y(Ot8k@cRrB zogvY;1cwS}UEpi8A~Ni<7~DMKt75HoK6?8szLe=FJqjO7CR4?t7Guhy3zWa{p;3`} z^}3u8IoN6X$rY?0;v+~*gLf6bRv}I8W6((>5a~`fdwvb^v;TQHqxaz??@495c7X(3 zK0LgJbIEmjP2JHfI}2#r2Kq&?%aMU~yOqjN{Iu#QjoH6PS#gzV-fnpQL&|EAcaeR% z?shXA-iE8qY!ggn`b?|P)c4nrub&DZaZY7#4`f50YPS>xcMdJ;5!EB2n)pk>-m}*! z{BV}{Z1^GcO=$V_?R}KQVwauq!2GqXev{m{a$D@vZ%Xc4xoybbmE5;-TdeGFO72^^ zt;O}fA-PXD!x{wnZc0xJDD9TZ7cC}i8A!KyQV4RTrAY~o9(kL?XuLk0BJ2}4kk_(w z{cON-#(JK7ZnlJ&AUCzZql~d!D%?}rskcxhO4_R2k*;>Ho!uQdiLG~Ks6Mvp<ypi# z(LD9FAE2@Rph`(NR`ao2fH4YAdoGLGaF^jle&0HaE6$5(T9J>-!P33QWo^1%@8a`B z$P;koN*@MD2r;;uh`O1r{T_jyz0(s4VK<)!mX(ze;Vtqg964fKNvo>Q-i#Z@B{+Tp z>H8z)LREu|13p4W`JU|c0qN`{N~z%xNgmi&8?qNK@lCdqxZfX|bD}@U$VR)YTpWG6 za`-s(wEz$4qt3TutzOs+TTGC*=inO7^&N_D{sS{}F{&%!caRJI7UYVI`0s|2FPOEq zvDCxjX48lQGHNbuvr`#JJ$D}>v3-0VZhXQ0uToHd3;9<@tj&LNQ){^K>)ZNW?B9@o zh5kPU|Cu~3TK)`*lKZvX`7ho5A6U$_8Rz$H=0~}$10eoP9X8~jTFnLhe@k+|TDE_M zQ%dxW4jCH;mx`>K2VFFZ@uKxU8ZMkST_lMiXQwOF4JqV<<>nJidSy3D<@pxT-tAJd zc&0ACMIZEO+M%k<veVQaPo+etrz_c-^g{A=>s~Bc&9n-pdb5Po+fW+Ld*orNCkeTu z%Uy48H9xub)OFTl&jzvAm9DDeTBJL7riKn@D1Iy3w?8Qvc?mmA%A?u=XKvi$)`{rc zlM`IYTFIKN)Ws=L<Qwu5K|Aay)M4K|=(8>Fw{0aOJ}B`tZ#zbxqCTq}8T)q0t`Vdh zBN-NP{$Od_BQ`SRqER>htXj6$&P1*Q5eo5AD8%5^5eu_aqQI;7RdnaL@NiQVPB$zF zJf(yTOd*Gk=0&`0{-=VHu+g!A+Zs~*uYc3qdBZLB-x;^wxRwBgyY#HL6bi9Ib=jgP zNmGNLaIL4spZ<g?{Vk3$edHLQ3`J4>J8Syxcm?E#-FsKHHi@ui=mXa{safv@ov=7j zw6iGE;SG5`04i?A`e9dJwwCV?@s<sC5+2~&+}tU=Hy;imOR`6mCwtF`P5&%_Jhrc_ z5_b+Mup#$QdTy2L-9UJ^09@a{VB8jOOX~1wt2uF0ev6UL!f$XWc9DB|^aQvbfjD=F zI2?XzojFdMBP5=XG`vvWMA{Xslz<*W)KJHP;a#nA;ab4niLCy?xO*}OZF|4PPHw%p zKWM}XCY5P-wUg$#k&PN*JrkG}cfO3H*hXVUp0H)DaA=^y04W{@?1BR3EMITqF;pJ0 zX)}l8I9?G}iniuR-43Yd#`Vcyz%2>p>P$9!`5J8gLnDF7F%*dJv>g6TEjRufX*nZ{ z#r0+pr_MHa*>0psNIIv#pye9<-&@N+e&eSc6vYp=zKyLub1j70pOFH0cSlNqN9r_~ z+FYoD?#i{;_8PO|`2lD=2qlj9X)Y~WHarpEks|dN2Tu(Ru7VbfY(XM0lg=(^9_Ls{ zt{9=^WeVc%3yW<#KE3nUP1(Bs7%k*aU?Q&svm)CP<hTn+!qRQ28XpIlp1Go_nU)9O z0LBOMD*Rl_MNefr{?faod+NIFP05%=BU;F`&^+L_q&+OwIktFFft5JeHMDsgIWLJ@ zk2Rw^vZW4))aWM>l5`h~L;}>pKx(-<>0`KPA=^uLz_f;L&cV?}m_$GJV^ANFy@sRy z1GIKJ6$bjL2-;W|bM4?>@V3D7;wVsIMN@G(-^JS0H~+!LTe6IT>*jxbNr$hA@-N*# zT>P~=RZ854_6?aGDIwyCJn+V6+(+aQQ1P|wLocMl4#_hQ`(K`Y9cDN1?}MkLuRr`U z^mXlYd2yYSHQQ6YH|{88`YeMY_m>>1NawRI`N91zylj-thxVGrX4#~Bjxyz5C`fb( z?lH)I=tJymYX7nAhM&psy*a1+`qNHLJd)=~nYS!BiKx%4_u6jqO8G{OA)j?g>(TYt zugY~D0Q7Uo{*_?YPQ9;0`_m?wFD)|6k?D_XIe3f~FtC-+kH?t9YIWRRMPbLbol?83 zVG%g5n{b`f^EfB-co*d@s^tN?AY)C2$H#R;juT)_XcyZ8DKs`9t193Y&JKnWOLZ2{ z727lYNMq3MP+9nuBVZnKHJd*8r8eWGu$*D<-HNU$QMV}JXbA9}uOhjGxa;16Bk`iv zyAq#zpUBUSrArcA-J>|+QQPDNvsnJOA-jun$oe$Q@9sU)T$sChIU1+(;qaIwO4H-L zWwWq@^4M_F6i=ao2}$671V=nAkDatugrUZK>7YcZ1`}a($5Xx25gl(OThnS9$^^%^ zW#NVN%}-~0r_eVlqAJEpPaHyM_=Y5MuEDmq$+;4{5@GbWCK;ok-g+_<=A}vIrRcpN zlb60onB`K#S@c1;*L>+{72Zk?O%Rj{0Ij~mcTV?9a}-zDGTyW9x3)d+l(RLB#Q9*A z4KKmK<c)eX-iA<?hJ_Q@+4Y{v64orL9-NI9+mJ{^C%PR)#o(H|p&aFGm*oEG#7sz6 z8TLKvdVplMELno&F>mfCvhSm*&r`r+1blC7$g~{F8wNqw$QD;5LxALD1u#@S&|^+I zC>S%59Gq)D42A35)`i&N$qwNIG_k>atzNwe3qF>QdH|D9!45KW>$=CWZjH;ZAChdh zKgQ#sL<(cBAGER%2649Q#R6!0pl4IL+{Go9JWtA?VHZa?{g`YSw7u?44%dBTZ*$eI zz6Te9pM`s3p@r!0SSl6b3Xv2AjC8Qs{7aakG!zyJ*h#ECq3LO-K*wnW*WnxaB`@G% zp~(N|_prL~0uxkJcKEO>LuYfq50GUwyqq|xraG|iruX_4bpEFgFEZc0_{7qFB@OL+ z{klTEKq<w*L5-xqr=-DV*2-)6t@&?M{kO5!uOX**^bu(wbqeuAV_;*`ACPiGE_d|d zRrRY^m)D-Na#SW*5R%<LgL?0ia@gmw6-!@_7L@v7s}6-7lOLIpl|xy}r=QK4?{g*O zukNDw(z>}|^f#}4T~YbIgTm)kL~^7{B)U~<yr~_R9_4N;x>iIJ+Ab3ql)dT7hJ3)u zdAw|S022pai-_Bq!=Qr?2T~xF8M6mC{j`Pd+ZfI}r^zs@jb!T>o?7{&dKtgAi#5va z(ahHlojl1n`PQ{;sc8!cm(wm!w|nw3MAfa}db{Z!T4433dw4!62GxXTzpUB~dAF-m z&e+{UNL5<O*+?!l3$xO*F(USvUI<B-O?S8RASi3-nr+S(yJ9bj#LK3r%*lqN2|t|m zODhe{Ek*?XV)osnJ{nkN00p5H``iy&_5gXm<rXZT0`nmH(r#JySF?hz#|=5R+uQzp zYU$%OeXMh+2x4kI()Lr5nw%S|qjpOTYDP9FJ*7Hb!}#mmi34?6Xyb;g?H7A382HP? z#n-;^AE4k@I9hMld5SI=$3jQ7)Ex=$QgerhA<S}k(9-7_&J>Lg>lA`T2B`!G)lo2k zm}wpgs7(=#To#7#qIdH%=T#$BLKD-vCKO+slzMp=^D=8huvC_3NH1NM44fu@I1Z<Q zZx)?|sv4+s=nPRcuN0pK2cAe>2b`UaV^hIL6FX3H&l*Y#^WBD&fSor)X>36G%t$vJ z%?CWH*~oUfD-q(HU`14as2P%%xO-cQC01WmpbP3wfipNeY)FvUj~aEOT<yy1TgH~# z``x9L{h?Mgk>8?naPgr~=z;x|>n#@<Xa9_NCZn7#?&0{bHe_?(LOH>ZDOJX1Ox}w_ z0V_unaLWrvMXt^zNN)-$lA+nQ-)RUh@fg8xli^IOS}-PSGw6efqRYeN_lQ|=Tl#^n zrC&P?z4xMO8k4(C?6C6-a<0g@P8UCMc6M#$C}uAiy9Li}ssMJb5*Bqmq7<t`O%ZIz zy+ok5Qx)UsNtIIez8ENRY<B`eA6TknAE#4#;%#LOX8dpNvC3Ch?mh42KKtLX4~={R zJKnwJiC>@I8%;g`AM$VR(xL%Evbam#w*?K^;{7qo>R)|HjXHz+J{DyBc+$5t>A1Ly zkJW8le6w%9h}$b)Ds{e|4)m_2Py!_r0<Ir?j(2-$l775m=U1)O|B1`EYgC61LwH_l z@x0OxGvH`s4GCA|s*9gHm{IqkIzG1f{mVVCCtAah&)>?oEb^Cd-?!mmt~)ww*4cOu za%wg?za_#tTj9Q(cd<OLAx}1Scw%ubHRRiGGCl-!P2`(tP)#D3!y(5Xi=-gs^3APV z_^W;XShM)7g!QUEtO+t%ul6~;%;-Oi*<AR4FlqK;u?rPAyzF`OJhmY}9Q{j){YYNR zUHqcNek8BA;QG%c_9J<1$X}J%kL0yZS9+D$ugwJxyg)+pDEIm{cr4czY)GX|9Dxjb z;?QcAGK>{<9&iA~yIZ<1FUJ9py^2g?a#=(<ZxrxuWQ!Vy1tKD@d>(BVyy!3H838(j z+J{FTt+!l0T+CJn=G_QUC7&MC1I*l#iPJdRh-wC6m~BbdOfPwqfiKr!pBHE8&dsiG zX~{)Sg%C&v4cNs$+D76%1V%Nwb6M7fLJfSSZQVfFdZ+QcwITi<szs6z%ENR0M&ITK z%4#w^JzMu<V(qTec<-es850t{DBiNN@UBU1g&~D9z_`-S;|qVxka0SLP$D~(>0LNW z?n2n{WXbD7toJbX;qD0Zs5!AdZ2slqTTpAG>Giw?Ekj-SwW4A^Ps_+fKitlH#aG1d zun}Cb@i*aY`YSm5$pRODVS)eF@?0HpyWDGEFV8PW|K{@i)vNvS;`+^{aX4HA896>g zFO=Lp`dHL5Au$BIUqIJM!qABil<UVRl7c!n+J2%A^IUWfhnFzRrD@B&(G@zMwY{Y{ zbG_F>VncdWeGL;+Vke-q&C!T3pUPYV+g*eb*pKu!VA9fTWN$9PaHsBEe~5I<Jcoh` zz?fk@X>PgI8f!Pl3mw&%eoJ6cj$Dc1Crg95?-toT7H3<sLdz5314SrX*%$TpM5pKA zHf{@U?o*T%6@@41gB^j5Cv3BmLGSx*t=#}2WM+FEejc!4!$^{>+iX!qu2blQqq*DK z8yG~b)SY_oTJShqIj<s-qpAgLbluoB$#ksQf!_R!x6==Oh*oim$oCn?kt}Ss3U*J} z3HJy$7XUrk{2%KEgD;-lhaDmJzq>l-KUf{=e~Z<z?kKkZZgspI{iD^P{`XrQMa}V^ za46i)I8w*z%&ced>=xgiu8(=<D#cSA@T@sjqok%r0kwRfFZ*OZU+(BVf#z`Jq*{km z*}_jrT3*kK0R`D2e&8%EszG^OY_Q5*!6@QloPbW3j2fGu5Ip2gMR7ayBJ5%<Fv~b0 zP~5nWjdod~l9XPrKowpS2)+Ya9-MU>zEiILv(>>JF|x|p#ho6~4jOmS2`FbG(Jq&> z1<)BiPqEX%SE-Ht^nzb##<;878Lb>b7lJZ7s(@iD7W$Aj<OT40U2LzNz|MYLa4xZZ zw2U{O;aWgXX;t2`UFNs_{x+PnecFb2ZO&6m={>|Bk{!AE7d@+EJ|1j4kf?hwpI~O6 zE+kPmedN|$wnaht{T~&RL2EIY{CA4Ugjav>8CQ9&OH^Kh18*%{U#R+_AmR5cx|J2O zMUIDMB`n(cT|wg-=rC(yQbhheHG+novG{aaCGtcc3FT<o6!RA6&_A5yE57<6BLw_b zO6aw)jOEwp#2~Ik8j1(GUdCA*Juv!h&cyk~mp>@dkpEth#<vn(2dcl`)!!89%Rs1) zLj7HtzQ(qHAk|+H>a*MVMXd8{{Qh-+(hmh2^8$R3Y6l++Hr}=J*^lu@difTi|NDx$ zA^${*FVEsnq`1m(d74ite&2)q<E}f9-77V)J)Q%6FyRG~G?;++!RGE23R$KZc8nlZ zrFJ6&ak$#1I_`#acFs}u5TW+ka}MH2+Z`L3w~pSfJ%TuERW6>89)%X>NQ@ZIfKh0R zJaf74N-=PkpEg?>Cl;oX!Y$Y$O2XSfQOvP$y~5p)O%X^pxyz)=;BWR$<_>f>wE!=K ze!i8U5$2iQa3S_GFstAL6>3Xp`v<&rwX@~<MYJJx9@Coz!ueZzX-?KQL?XFSz4@X+ zQW9b}Rl1%V913rjG}I^k>TBKgaooETb$1G02R9@tN9{PrmM^PUAyq<f#Id^;>u!0X zIKz~0uzCSvH^@~XA=>uRjK%RNU+=6dWDn52Es{h+5U>IIFG^0HQKg0cXnX8{jz|k4 zm2Xhz#jFPV<A!YSb!+B<9-qMK=4xJ$a)Mmi*IhvX^mvH+v#IfzV)~d20!<co$WEIG z>jHYTn=!ZLX%2Rr1o7?CseV-^SLfOeV|qk=r~|~CQjir)?V^iZB4BA(o^u{072o-_ zPHD#0-rk_)E;>vLp-~SFWmg8S6XWNd_Bip%X*T?Fw_`(M5!w;vADqfqVFi~KDRigv zqD2jTzb&TvYSGK;%GVwFvLlWQ#<^vR+W<om*|QOM>q<qfZ!==i5@Kbg-etp96zuTW z9kzI$EqB50L&OI62AcK5-93#O7zW}_j~N1&Gu(b;sqY>rGp&!B0;NYUw&n@Mg7fu& z@37BGWWe7tfPZFYzOJBqu^MYmbzo_h=Y|QWRUgSX`E(kt_sZ~~<_E<C&EG3W{;uq6 z!MYz7e*LImul%a)Y(=VHE!XBal~v#3V))xxp1Fwhz5M2H^gnIL*J6nIoiwj4L|?TL zsi4ZlqgH$@O>W+EzH7(C-<RT-yZF0OTxGbHBmaFVuHU{P|3r$b4A-Z*NKq6ivG}yt za6u{jxdAhs$}btqq0xrB(}Ca#QBIz-kZA&}9z8wS!FCSZ0N-C`hERcR*7bRQP%=<9 zuVdur51g;fJ#{jRTG@w3^zPx#V`LVqA&rL<<(wkbZ)}j-g}@*}ISO><jy|Nu@xs`o zitb{ZnaBhZBpz!Wdg30;<c3#UbrwQDgd9c_j!jBEj^Uc9mtiW3PP-3ja1Q!(W|ZW< z+mmi283K3gXK@eNah%%6cuabt*^p(^8bX>=?oGZQkD3LjT|Ck%At0BneMr3k#&}x9 zK=mA1+ujAC$cf8VljazpW^e6gZoL|9wq3%6Qz^<3)u_H{mV+N9au*g`uQ<@`o{4s1 zb>X%qm$n?vp3mZu$Xz-xc8gPC-O_FTm$9q0Du4u}%)tk7uNgp@JWgd+-jn*GoKG8a zj0ywc_U9y5Na<0thvK%Vu#YWvctlm0?bIT<4hLP;4*`VQ^_3CMz&I%8eVjyQyXYjA zI{1}Qcl2C$v5?IcQr@7Mp<RV;4tsC$NrGl}WFp~F5}XqDiX*I_7*Q#uno!D%K6D1z zQHv|r3?e7iS}_NQ{TAo$L4Cg_=Y0~hJVHbS4q+IjUesI+?m_BzZt1ru44UF{24RP3 zxqqw@W|Szyhs>Nj0zq+b?R`;;5=DVdDIX&w_j9B4+Fn`<#&f+j?ldpi`sSZ;amhMI zPi1urA^U5`f_JIg?FwF)q<q$r8>9Qz-Q{?we95C2r0t30y(1Khz}r76s{B)np?+d9 zzW5o@ISr*|M)E%K`;yH@`9%1C?PvVe>3@x(|H-HSCNoug^uj6liNcRUjW3)|gAT-2 zt!@=14)%@&&7E(Q#%<5xc@@O0XRr{K*SvxFK{}W$e_ozj)JA*O;Sbv!TyRdVx$w%z zwXHGDgOj3|CRtD8(CKJiAEN4qGQj27Y<McF6}o@JSxKT1(3ik?ih6!o8Rh82u`;B# zg$K}87EylU+>u=v6;?<Y=QO3~xq&XL;-!JKyuhrM)FA>1-FVAcRK56o^wur0ZEH^3 z#=M>fwpX@8+T5H|2qL$Hzi&nD+D**>jG-G`YLwGr;m{^HFM2e3UhKa=%mdU(;N0$@ z<3mA><1tr9TSEM60U$`O5gr$Y@ByjRGNJA#!IW$dU&tY>Nr%x}?jo;q#6{wtGE>&H zyy4hev>xviyq<QwdAxx3f-&I4>Djdek|Ok>gme|$yPrqrtbx+3-|dT!!#+uL`)&S+ zZZ#?SBXTZ46r*RT^)hui1H)5~dra@eN$jcNp|rlOm{^w#$*DTx!viQ4!b-qP9uszd zZ%8lCcj<ix_nDeF;*s8vy8s@Fsp2~dCbXo1gDeQF7OL+R*Fi0jqk~`wxxGC#xW#8u z)9d%THVwygryfC(MXNcOy?Mo1^79)o_HyQPR&=cla^5lH5T62-YA`K}q6Wm;-4UFI z`|UxNv7L9!*v<T+QT8}zHZnb!VM+)n)j-|YyA5-^gMeQkz$x|YY}dJ5-|la{aYgNt zP_hG};EU8);}d2I|98Y0<F9ar=^FW@L>IRu<K04khDXI=zlAf#AK{Gg-wS7CNV%Ha zb*$<e9&Gm;k~au%?xwvkG36x>Dr-y4VNiw8on{c-(eF`z$Njkx&vS{AbAM)?ocE8` zojO1wx7Mg_Yrb!Fd2QIG<$H7ri^!@61!yHHJxk;DF5;!Im?`HGOc$05gj^<O$IUAv z>j6GWHl%|pDo!!6D;yL`=6H41Vh*5@O9|OHhPVqy81n!*o~70kuOyb!{sdG9kxU2W zq$>-~Xk)St07pQ$zYG=-k-A^+xu^7Lx25>`K<?}7_0dk27J)F9<KyALSLMA;l>w72 zA-vC&zVjD8)t~Jy?IKGLodk&~JyS1X0~ife-+c2p4@dC!u&W)+2IA?!t3X&Wj(*kV zZNV^M3$4>GC(y?R^`|&f?3(Hb5D_Aq2Wju!#LNwKwOyVCeK%zgeI$)b*yEA%53zDE zr+$0rGVg&ap)`_cXDm!eh{t}u*_403*V!->BBLxG!8+#(?Kme8X_DeD$K62;oa9U* zy6X*4iYHd479Q6Xx2O~48xVK$gWakg?kXhHo#jSeBzZ2593;SaIM929xm&PYECTS6 zv4)152^_NMbgeI{<5j(GSOvL1rFZ*ip625%#9}O7&{A`7BLn=wloHb}n1in4nT0ZD znNRzPI+w*!N1#%#IW$Cd#<(hvy@LVmT-C!3L>5U#%EJ;;dJs<#Y<M!=C;2ogciz#} zaSU*(73>?~rx&n4Lt&}Umqy(#H^gWjZO*F){GZs%eVw`WZ<o{kZ4Z^Nc7b9(3Gu$o z3|~WOis>P5bKd*VH^#$EBJY8*%d^a(iZ14)yv(F}*5NP?r!M|DQY`CpP;x3?{E;d2 zf?lMv#ZTEUYgSE{HJ$}Id{r%H5a04M<M3>8Ri2%i7R9Y+{&HeUeSVmC%MbmX3_Ek* zzVCh0jCCWT|NOc$(_i+}KVfhk^+mD#mqD|@%`!gS=!K?+ZzfZ0$meVdhP2*`@Bp_@ zUYx`}TAW1H?uNr#|L8112^9C?2kEYRmKqOZPuU`0t(lf7rr|VL;4iM-TFJ>~5!rpY z*jePqYv75!Y_pY0t8Xr6{IO%KOuu<@sr~ZlDrCMms*77Wn%`c-mrY(iY$o?BBX$lp zxfTl+&f<KNi1u25y&iozraq7TOzWN-$N#e4eWrG6`S{;cyf?jD&hUqtx0WowZvy$V z`68c=5#D?=NSM)NQ3YJ8m+)cx&=7Xqq4*f6b%QFRaXf%$1W`fUxRyO|-cwY(0Eh@A z+GxdLY6Yq*h+0lVp@1~j1)JtLIgq?f2;rqyHLI|C<93#`Hr`hhlKR&pFrT5ty)JKo z9Ex?gxOg=`prm-)Ytp^i9-xBNcSO%t0VFf0w5V=r#Cp8PBd!wF#_%#mc)5!t9@12> z-v$YPKV5t=%VznyjgGba|5*sHE_!lmL}76OmY=~@6x`*A^>^^<O9|7@H0MWpvnDHk zO>=&vH(&Mg{}i)6)0>yA)qc9^&ss2vl2w*#Uzb?bZol5e7d%n`&9Z6YDW%tf$=l0d zWRhL;%PEOJQywbgOq$JtDpLZ>U2yH|MndJF+2USZH`%m};a9d1!l@)*!@3U9?Qogo zADAOrKEB=O0M&fGjgU_IL8iHSr0?jIx^4F~TkZ6Fa3|M2jX!K8^uCRd@$EK3!>~L& z)-8;Fc@IrV<oCtI+}q}kr8W%Y<60)Fe4$$x7Xtv`&;m0DY$y4X#tt_Ok@7tT&+urj z1FQ%{-r}+D?nAewl&+ie01V@F9Q$*xZ#6#&Oq4wlyge2GfaF*!##4V$K>Z*R7a~qJ z{{&+#HQ+H|a@kWrxlpJrdNce-J4<keQcgV9d!O_Xr$FwC<0!<?+UP<Qw~_eI&Hwd@ zVSnEo{Kd3Ozq?c{hT>lrilak6Fzw44SPz~JfIC|rQb*HYte(YAMP3EO6Sb@&E>{Y+ zQa&22Uo=;<%Dh*Jw;$j4U)DDo;CNl_=r5Ug%w9}9_r9q(dms-cR(!rw<YfQ0iFeY$ ze65Y=7~)4J{`G==K%;zMEeXN@!t7ID8Lkbv0MIDeWbwWOBmE&e$$ou3BKz_H7AJ9? z4?4cZzg1f<zkR2+e9v->{<*1tZLeRmwYIo?uL!8~o2h?0<~)!6NDJ5Y`rp^Xk7~Hq zfB%^xuKNoAnI>+?Z{PW=s`zH|znNO}-QEF=c(_(4)jXeXbn+;LN6DWFu9<Bb#QDbF zQO$-#8=JU5E;8f1?1a{1+{cUwE+jt?Bkz9QX;3Eg^C@7j(P7Lq0wY9K<@oC%MBJ_3 zj~f!17xRtDsQy@7Z@a^?sBIH^-s&^g-w@u7(#WBJy`pfTz>}s0rm#_BL{UnQ41(vy zS0Y=xw)Ys6zZ!k=w3=`s{!5c@iSH(VIfMD!Pj5Bx<znu$<?m@R`O)%w@GCgB78m`V z27RkPi$?vX27RkP3*GtyEL-3A%<BA_27PM!r{xTCOEy^^erNnYG5xPH_}ct$$nOY% zy>?Kth*}u)2lGEZ2Wi;lq%rDO=KqN>pAvvk?_=5T{n?qk-2sU@ybTkvchPeA%KR?~ zquSVouCNN<tJP!fJxrHt-%_g6Rs9w7zxhi7@a^8tI*Q78lGNnjo%F}871$%TA+KDo zpQOgJqzghY{)u4%fvh{@(3DOZ4&(JQO-3ZO$PiPF^FS_>gH>G{4l|lL<~%Rtc|uqM zqb_FKT8RBP3RLMF7{-NLag;1<#8?N$&MEJa+8o?SLk$S>0otk0V4OYfj+_6;mio>< z1iaMd5r4e0x^SHQ!|>0|zkVbDZyP*=0E~a;=JSAR8Uy>jL+br&^2u<d%DnDMIU3Lp zG{$iD_@t<e0sme9^jnZs{>~ctQ7rI(ehUW5j5ugadVSh^e~tM8))#xvACR#4+@-%W zU*pPm_&yp8xqZVG4f>HQp`F!&1P(N{QubPV#20eG4+&X}VHM_ZDu0V5dHF+&zQ#s> zZqt8Y(VqyiaE>2fh)Ac?`Z(ox_nloQh>zOrEYzNRv*<4q#K*%b*5&Bm(WVXgBYpZ+ zby~Zzf38q}rB5$Ch`*yz-zwCG{H<)cvsYSE=DWTvL^5`{+E^Ha3_Q%oaF^`>yQjjj z3b6z>RBAT5tQoYsEff1wFB}6cJShBikF#@g5cWMJbh}ETjD>r30e>xg5T5zFLhe|a z(WcwgAxlyqfJ){Qny%-ZeC#vml(;ce4iM%aYwMPo7GJsjI@+kB9(tUkVLBH(1%$1b z19PVgcQM^Og1QK&9e=^bk@sj=IhEZMQeb%L9k?aneeK#C(zj>tqA0p-=!k-vMt3*G zIdvvCxDj1#s!VSM6y!%|VvEq6n`Sn7-THU&%GW$RJG;fkG+}bOH16YslSSs_E)|m{ zeY77WA{`}uw`I@vC3NY*Kr{%%>ls%A(UWQeV!VA!3L6rB&6YQiF~r5%-CCttnNry! zqo3Hm+8{Fi&IY-QNe4`Ru@H}YF7-xUsOLJ9$kOY5@@prQ^UE}0%~ZjHJSxRzUPC8_ z#NDc4tqXZ)Z*O}Bt@}JjG#BiZf<!eM9v0MLOXkNB@D1rXr%ntknZxTichyth&>Y_d zxLu&*MG5ioDkVH`&@iF$i{p}=dA*PkU?d?51X^|Be~oTob*;^q2UYU@ap&anHWq;i z(2pAh_Z4_WVm{n^?89~BIOVWNwB!Z?$nmj55YbqiVT?rY@gbq?*lO@Iuq<cy%9LKc zS5XlW##WT`W3%X|U((`jYOnWW4`HO^W4QomdwfHlp~vnv&N_1Mgqw>+SqK^O0L<WE zn@Cxa>~sgWGj_d*rOw50(1|WAI>gN$FjTA0>pbY4vZFO7hwk-GZntm-2boUV@*Odo zGmhhESojBh*9}@_;54FmGQvj$*$*Idj4OBA{Ktnga+jJ%AFxH0scr&wg^z2l?LSh- z>Ymrlx_$T*oo+9iP^?`8ipn+L%z~&m)f5Moe8aYi4!m(K5?;I?=R+5DXVayI1hqT8 z)&Y&c0u1T<=Jt-1MZyBP9<lB-$ecW>imTT$)0Zac*Et){-oEX-efs}td$S(Lv8_w& z#eV1j4A_9twgESAZTDw1EPNIR4J6P7H0MDiCC(z>D3anlh=Vx%dLK#=krA1ZSy{KP zAF?Ww$z&WJ?z7h(*IsxwdgII4X5M#pun6$UV02*rydjpT7HkVNSO+v${-G^hVut$D z5@cNfR`_cLEzOLd-ZE0rJOMu78>!)=)BIU&wNzP|i!HcOXy;LFwa`Nf@bl`U-fFoD ztijKovu=XDO9}PeZ~m}70H2I+ec(W=A3ptjnn}PpezOMLk8cPN?(qe{TfEj{J^J?P zzmf%%nSMGnFGNAZ=N;uvMPqGYs!TRvVnq@+Y~cQQm<rETR`EGR!2VkuGDZDd91`cg zgA%3EcXJA!Ah;-@>Md!Oy%6BCpJL&zcM{l(%F<s&ntHfuvs@>sj^^pm7p8KkOj)jW zce@dt@Br3OJO0fMH>Y8?@O7>;)0*ONYw%ChX&|Ko+4kNhC=W7Pqwqa#`zu>%j3_X; zL<~2*2YR`nSKZBF*OC#Ca7Mvw<`mosuI*El5hH%S2wZqSvb+9n61!~})7$HMJa^t@ z1{mrtv*vvqH8XUcT%zp_G@o?EIE`3VP^?dGv-<8kyZtff<FsyaR7>SOTyZo%gaVA* z*Au{}G>2~SEl@ILD7o}?7LTVam2982>(LHugQ7Gzm77x~ShUB5^OF4|8Fct+Z0&Ps zt^DL*(V)|_%lvg(+=l=<kjYO~Qxr?H$Z^Zzn#Txir67A_^7EKD;y7a9Qs57povN~{ zyLZqFBs6g3g}~#?5cZKu-E*f_wAhy*#<43siLVybpzNI{F}TlU(Lp$sS-0~+Ssi5u zWz2Pl+RGHIG@JEyV4;1ph0jqSa3wyhN7s*KavNVFc70y0T#ZX<jG($xtSfWIz+N_n zak8$c26(hgU7{5PKzJTQ+(p99Pr@s;7iw`~G1a8Y6&@~j>p6Qk+r}$&zSC1V?e^UM zI+;QY{Ew`alFjHTMJiCY&Bj5)Ol89DO7VKNnKwhuH>x}%rK_&twXh4F7Uwl;AkUXJ z=nP4J`8N$0Hd05gqF|W3cn9y(*;*e%lGjRFWTcxj7xl~k$=v$n>Wy!%$LnyPknG!} z4BPfeXH<Q`4m&L4-(P8W6xKgmz~N;#OWw&r9}`-iEnGVE94@#ht4U*_FK&G0p%^09 z1iAppfn}%63qOWrJSmN+Y&rPX;!V|(Kc=SDuhi@kZt$0YRrRK34eiXrp6$lrhp7E) z!DuH_?VGa7KHdk^-0PihG#3JAQ9v2jz#N`|D?qC{G=A&^18z6;fp7OgY4qvp4{CM^ zPx3V3z0)m0))?4%;hdvKP7fUKC9C(^5V6oJyy2C|e7^H}-z!ZBMdm6{#h$&*pB!c? z1m5!(Ln=OUMfSaa&ImDY4b3_+CG|p>!TDO_44UG68J!i<ET~KAF=w$%x^d4}Vy#YH zu$fb%x+Cj@7v?q4wu-;I_6L9Lz_-|9$92Tb%I#{{2aHBnZgF#Vhfy1Z6MWRPV=Q17 zq4q368xV|VGZ~*WzprecR^%-N%+G3jX<|c;d3qC`3bGICv5ft*Fd)pE!YX7=T>w;c z1Q%fCWupSXc&eMl{CpjS^roQR0}<bwoA+B10mn!6ARV3ble5~T>lO|GV0hf;pa(1M zs*2Yplk1oc%TnajSL?lU_K>*!p#X<?%@2|UO(%rK-NsdYMVeA|RcwIMy+>=Bc&Rx< z9My4;8K`8QH+wl;R~e%SdtF~?8F+%bhsXv)BmX*jGh#3s(Q}}KLTn=Lu%CP$vrRI| z)Q&7H*lZ~d>`r_}#)d|wp3<iK3yj?aN0BwS=4BsqP=CKew=L>HS+fS#1`*!dyKsbW zj6>#je#ott|MZ=*_^2h8Y1gd$H}r%vh;JFT5oZ6(7dO6(^1C3v(JVwu5meZ}>j#ql zQa>1fOFtAcrM~Hh;A{Qxg#vp}^c<IrTe{3KRzFbm5bxI({qUgZ`ErH7vp(Ra|6+x| zT3_To2OR(h_j{AeftGP-B2fWZl{a_pGMqc~8fLrvYDW<8Bvp?VQ%LA?7nU{P&}erG zQhb{wCYoy?I~OdLRm0Y{f%dzsMAa#b?b_n}TVlAlh|q`1KL~-WA8D;kRT7v%_KkMc ze&Y%5d242)Ww++($l%<nKsQo)w)ykiZ0^($IJ9unj_til@5f@-oTqBzL<g{wOnG0W zJBNNF33F32`*|??IUbnT<R=-9K|B}=m$9<njj(0vjhqV_0%x|8@2`WVA}Uw&S7wU8 zn<)*<!H^bpCg@x?bwd<mOeG_48am3=SGe`$v4!njav?^(*jcXpLd@5qHe^C6?kCx` z%R)R~WczZpEm;m`w`jQlf#TpCovvdd970Bi;~Wd6EigUTc%ZGoPLf+bhSimQ;Nb2W z-S#M7-sKb0nmM=W!pK`$c34+s!H=918|8Ezdtwx~5gi4(a8O?Uk2QH+9criP>_btf z2!7p)?rc@Js1eX*hQ_b;!9vxw`Lh97Kk5VHn-o(Yr1&4d7GvS*k_n5QP!->*fKd9> zeeIGRe;haceJkP@CukH?B6|<^nO|b`kG8?|$u^jm(7OsL*0(P4w`(s1xH=f=&nM?R zh;gzX5&unpKHNfS(V)MjK-1sljo#APHXFA(8Ml>kPo`=r*}b!ym<zhvtwJb}52y8T zE)jPLuG~jSI1T#=?wG8N`4BqGV$$iOBt{~wZB?x~&^0(nPLb?mt&M#8a8qTZ$n!Ph z8%y;Ic{-x1u5QV(3Z;T|DpHWP-Wg!s{idM2%*zpaY>Kma3)|7IvYgrZA+{#G?s63_ z&>L^Gw*+e*=_yv`iPxl+R?D0}xE`5S9G9JEGWc^I@xLky%Qpdz<yW%sRr=?t;RA+H zfjiB;S7|Kl0m8hbWffY5f2U5v6gSfDhKIdQ3UyB2UWjRpIs$(yqtf{<L`H?w>UKXj zS)?Ca)obrKLXo?ABO)KuG_SS_F>%oit&F&V(<&=BspXvX0wOmUVv6?q(sJPr)77@J zt9~C)8v^5x!FpfT6mU%;pIMn(jMgM_rLVR7mLIO$)nVL9iMPj&_}pzIIW=4~Njn+2 zGqB?0OrA7XHR~pnhF@T2b2BSF%;1t{FRl(xXC$Z;iKCd(b?d%>)~bZcW?`*3O+Gft z%Rj87_5LB0UD%Y5N^IcOygD}z)(N1VxG#oYeET>+J!!UXDWt4pgY-U)$F;q)5-)~$ zz**BW<8;ZhW6z<dOZ88lf|Ex2sB(9Z{27!^zlC}LGK(5aywVgBj~Z?|TGUpNys0gU z7&l*b6gtL_ieZW3yv=j343c$46JkbbCb~D#6mz%s5oR$HAoncx&wRpmaSjadXJn}Y zSAfgF&OU;tp8b5I53w{Lra5oEMw5V&ePtaamN}(}oC-_fpz+P90asx;mEmPT7%*w# zFAmqYxuvnpSlQCets2Z-rrtaD3aiLxXE6>-E%%G<b0Zhm>si=XJBS&nK>uG5rG785 z`VgrCPL3I<Z>)xL`sb0QMe)7GmNL?p?AyrD!wspxq0U_5G_8v{aOnencggz(sclo_ zGN35v!v=Pt3ZOAC1lb`Xd*0ro9C}=b&0v?R?&%|`Tp<fNh-Zm}CC$&gP@$Zo(Fcmx zMC`>$s5D+7Q1U{obp_K(<#f%pnZHG?WyNp@&9j}^IT1HIq(*7Ymy`4TC}B2HLv3*F zh+$_sCxGs$xwu%@X;l}gkW;IbYNFK+?TYqP?1kvk#aYSCi4-wt+xu5G@$VRyN}aH# zV8Tkbc(smRh}g?z>MU%TuAgxqKLWGO8{^D67ILuD0;@8I<V35Jc8_ywUp4#Llf8T6 zBBlY|I)r&4-~sa6TyN#dhFk(z3zvRWWQUAXF@xPVPC|u}neXEE;uhCu9RPsup%i&_ zXkh3aZzkrAlqUboar-K&w77Xc?Zu1hvtwG<q7zZ-bUGQ<1qdv4>(heh4`gy;CwnPg zEYzS@bZ1<*ejm#R*9lZ*cZPaq>aeu3k=PF;@MRE*@$osy?g+QKCfb-~drY{WFX{`C z2{*GvF>vp$H4M4k+h?C`=VbPF*~vcWWNj9Ae6`(DRN*5HdB2CIrnt&wswA-G3NR^1 z0`a)o8|+(&8cv-`(U3q`d>V+}q6z-k+zwm$7*1URBmDgsm{viqwN47+-P9MrtWWMj zico9m5)p{8z7NvC%QQwL?p1F#**2EcBXxBdyEABuRkq={rLpQdHOv8DK0^5dlPj!_ z`(zS*KUz<hAXwpBjRXfafcB$$yady!t&Y>)P78QG00ZEEx`c*i>P8rR;Kcquh!mW^ zi`OTvrRkQXm`i^26MJ+a<`xSy2dph!wmS4ipz6<zCzJ2>(3(?NrU}%z#aY?+yx6R? zl!%ib^2sKS$MbK+>%qyk=Fjo^k3Z@=7U_@g@y<sJG~8Jpxs-6;-^%{{R_~nD@9}zI z7}5+Rr_7+d#_O54tMlvC9|K9S|2ba&;imxoL^Bte%-%Lk|9ZpN8F+KR0(g5P_Wg|( zS6BVO-u+j2D|E;Q_QMMSRGmd!cB0jDX4&a0yYCJPZ(ba|WHufoI260lPb&yI)uHOZ zu_j!ry`MX)`(zXyetIEx=?1=DqdDv^BaFd!-E47Vf_u7>cN`z16}B#M2A&bI+H*0o z%B|B$5U<ocZ!)tOfP0k|mOGyLwG$PmTWg<Yj*5J!IUi}v@|#tX?}K4?SSQ!m;f#}g zb18QsC%aRwgVX94@GX!N=7OJhNtJP<**<i->UUm1vx|N21W752MvJF0xF^PWy@mFK z982ZCpRen!KLkevoNR5gZ#r3FwzojO!Y;j&60B?XyJ&kY`t_aZ>LV~_qE#wzTYnST zxaiU)7tHLIcBqHT-!L~sz*MsG5a3&w)Vs-hjg}fN=x6#eidgrxUPvbx@F#W2ejCfz zG9!*k05+>C*&3UL?szetH_P7xb5l4B&Z0+oaDIm9s(LR(bY*pS;mQOndJNo3NXX;z zS>QfZLFJDs3XbhVogAFA2!a2{z(|6Zxw;m(n@t_W8Uk>K^XQ>>qJWEYf!8Y6-bBus z9G1>9#}-@gQ|9G9mt!YzZlI+yQ|y3w9bXDHau6K+P5!W9z_q)ZJi7AR<!6Jt(;q4% zd2*rHvK8oI1@Q{BtoHJ#ryQaHiNT!_cq+Tpfq=Jg?}5F|z|)}R&mT?}70d!3<W54) zOYMS0k&ikqe}3l_{iqinbX*eZ#(_OT5~@9zf#65A1GR*qfgU1(o?;X0x1aJtEYEp9 zMSXR3)Fr4r96X||C_58oBL0=m1!wbn4gD#!q27A8tEIHRATk;rQif5u`+*#j#+{#x zy^BP>arXZ5?jJdbubUskQ0%KFAqhT@k)0A=VsG4)uD0@9tH8SY-g~6C#VQ^#SE$CZ zS!Yy0(_ZkEyob{5nu+c^9jfCD_>q&MoX5OoFtJuHjxd@mvkU0ZTVJYScL~wR!v>wR z+Y7LWfnUrYnOr4ncyi}vD2QX17Xh=TF_m^)WlIqI12DmkbMM9lxKS{C205UswszrY z>6V3anVfA{_E?M9gxhAEDuQ4V@HHxOK!_WC3MnPr3UPhO0!77l5j*yg2CIj#zRpYa zP?6i`gKhVmW#;5r<e^pMpi){lkTMgR2vsZK<BwJ#o=)5CNk?47*SCn8BzG@YJ;#6f z_pTsQ&8gH*syXJcB`w5iPrv~^O^j+^u9>Pp1x=lh+imm5%Rh)9c;?roYWR1{%ll^` zTH=T8Ys#H7zk_gv{jQvdbUE!U`An)Q6Ub!yX40Fgtz~cN-v4H?n);EoNid!g^hm)o zZ05c*JAGqVTDZv{*h9RLc`Yhypnj1Tdcb-99h@7Q@qHol$F-@ZF!Ba*d8eHExExk9 z&DUvxk6xwbJI1|_JMfK>cp-a#!t1;9q!{w>aGd5NEsyUJVQ}^RTKe_MKNq_FGb{hN z?zcjMKVSLJMqs>rF$9CM^G=O*n@+q{Op?PKjz`*Orjs%lXMTmdM@Fs7^WEg0QvoTu zyBsuUx7~!3LR{E80o)yQqNXWRo1O!%3u>3$X(d?~lJ)ngSVNW+(|fYQ22$CTFGR;3 zq4l2gn{>JddjLrnyQ%uTKBmx7MziUJQdCyw@x=iyzJQOXohw*lxe<d=a`OqSArMwh z)27>jaFXqAoY9@xuVBe<H7gfR73>yeh)(l(X@&urhK%X%jbd0;CbvW@`tb{KI;*jO zn9yP0ZEWvkZ_duPxM^ZqjZlN^vf_4f3tZR$FFfJo5f(Q3YPS*<8Rd@Lodz!GO7?^5 z2GRL6Z|3RD;Z2mu7dAb+;@Vjg`DDpGzsHrg=yntUAoj6$C+dZGx>5GnohY+wP#)<R zjncW*;Njw0BloYacR)XfWsqe0IR*}%U=Z{p1}<=?HyZZH@rTIKiC;arvWv`}$)!C1 zPo(zGWOi|+e=W6tCbJ89_9C_VSbutJTUOSacpMT1`DcX=U0au)N2w*w~L{t8d* z&7C#4fp$1r<H2RG`9qzUT5yM)?I}$AI|G|>ya_Hx85kC)II*1OX5*8QW%CQMK}GD4 z9KAceUmvKZtJat=>}%Q@^3pK7giO%Q8kquz+&ete;T`a)kbuO_*EIAtva@6og0r-W zW)itx`8H?{wS_bley-LuWHx$j4(Vybtc@@~O6yDOTj^z5HwR>U_V%#8#31W=y@G77 zqxNG{IG6hgMQ4td4k30)Z-~a_*QoG<+=|e)&7<qu)<>qoF8R(Y{mMQPFMrqx8wx&d zSSM<C*_4;t;bzU7d+Pf`$W0_e$>grfXnZ`X@J54dUZJqVW^emJ_Q%VAe1{cge8l4N zH!#8eDwupq415$JNjt~S(3U+!KLE?8+OChlVr^bSc<l!*_XHMYi5HE#KLeKgUk8@Y zLF2ENvk33^V*jlAdae2TxHD|XKO<z9$JCRjAGxgIz3OYRDwitQ`S@Cn@ZGiH@?Ar; z37Nna{M;?|aiuKvAziy98@^l*fJcfnEtdGmv-P<m?8DxC{Df~f4^;ABsR)y<D3_iJ zEFu`mC?g}kcfcOqk1dTM@G3bXbGN%N#_?QJxqNkx*SmScvK2&g{qZ!ftj=T`JD&>E zuBbsjHy5cs<1a)n-`2_;b$q(hR1Y4k_Bt5zx{~WHG_{Fp9ZT*!;G5E|i4(DF9j^{q zVHL(Z_GT(GP$WgRyOWn=;k&x;Uli3L8lKrg8*<}Nw_{Oxy5nSrPHq`2z0LJ0R?_QS zuVc?%?O{j)Pw*fk8+T}bG1;r1*CR!Tb7<R{?NO$(_6cb~sjQSdh>j;@)GN)`f<uX* zopNFJw;6bw%%h0m9;gG6uL^GK*)_0%IkcXSC#$qv+=pry@=z-2@~%^;%V<wMf8Xy{ zd0^rukSIIx-)D)SG({rD=jk8~aN5FDhs67JH&U*-ACCBjG9-(v9x-)bXlT&sTh-T3 zd{Z}jO}y%4W&nq+BHo|;Lj3sbTkP+&@prY!Td&ct^~tZ)2{@a7Num4*K+go*I|ND8 zWh0pR&^hTQTG?cM2vj%m5l7obK^EGh$_ulbr+eB7_I<0`BM<ylLDAY1Flxdph9{0O zZV0=+;NDueo<hD51-D5I!au~RjbYtZ8<m6I;p58ax5HW+fOoMQ2FldbE^Q!tBlB0S zJeT4DZE@xR#jY|iT(~P$2iD`%IG~Menuj<nPTIEV<59l@Q$=Yu25_Hn2i<PNoiEZR z)p#9pO4<{GSA(ZUmIgCtxZDMaU`Lj~$XF2u*j+cAo4X{~{7tmB;&wce3FNCQZ}y{O zS#5f5#mR+mlK7b)HD=~WUJ-+WIF0KBXE$Uc^(C;8x8BRY6=il3h^mL(Z7RlFE2|AB ziUTjiDQw)}$2d~|c==E75cJB_^SxW6{>$8&|M~;^ypO7q4fdlFWl7j6#@d;_DeVXH zuuDwsj{F(&WM3oC6D!`QJOQi-KCb<pZSdWbq96SuOHm5EbfheWDC(#f&ymY-SKqGv z&Q1Z-?Kk)8XIu5`a{aT-`c*&2a>71$bnLWhNH<4;1(qyAPnUG;Rs^c=k)kxT+j*0= zg3P$2R_l0SF(g>Su4}J)e(B3e>2bQ0s!M*y%Ki52_=V$c{MAuv>x$n9zM0J=BcJfL z@XLlLT%TvxC6I288iuR7-SLGGj@g}9pW!6H6^2i)$O-D0cJFTxBfC0^rDdf|cLZ5) z*0O2zdkrPZ6F|RN<4lR(Fjr}H<V%#C%a!RK&J^4V=UL|n4-xWJSIpL4TjL{+RfgA> zVTTHYFz!Nri9*=d92krf$!2};y9Wc)S~O8(iqwH3xrYix<DDgB3XmCjMXYRk6^6Qi zbr7-JYAo0X#5O&X+6{^g^-_(KBv<WbhRtj3Xj9y=#K~y=*3Yp4PK3Z8KSqrfheRh& z>D9>^;u>smzdLmEt-m4)N8DFmq_n@EMD<AJlj|8^LG+sJ7B~(#Cv^0yC*Rnr=A6N$ z8EMG{eYS3XY1?Sfz`avMXrFQ8dwb?F>Fw2?Ie(PycP^|~Bv{VopEx7L31!V@BPvyD z#hwv#5OR=;p4+E1X6rkEJY>Ad^{li88@o~s)LX2DR6d19n9a`o=2m7sr5*RpUWhnN z%CmJYt(;S(DfXo5_WFfLc<a6%X-v-z^}MEc``MkZT8n7D6JIW>)ie83$xivns)#hY zq$jSO!u8>bNf#Vl6BUqZCLP9RR`0V;<gIvUZR9v^Nfu4;vSCc7LL1+5B~e2z4p+_m z-W`ucPw4&Lx*382tip{hDDgmM>uSWEG|%fEr@D&BnSvp2lxp|{p^5o=r&u(r%LAva zq7vaL<%}3bWvayseomw|Ytgk@*NkAp{us*O#c-bKug5ct?vTT6lqb{;?&9&7%JN|^ zcNyXQ@$#SFkwEReJh>)e@NxXaJ0SS~KgPLcB7V0XvX45Bg&*@PihRiieFg>oLGtGP z87P#$0tI;x-cwZd$MtK0z09<5>m{}G6)=F~^8^9t3t)hr!0_uG1Lo&n*t4&;?2QNJ z_crY-aCon4`k~!#OYXdNH;8r>W+fD^7;ANpY2)Ig<2ID8wJe=mnjCH~#7JJ&o8vg{ zHVw?!`_32;4T=RvGBXQUTbU4_c*Zu?LVWOgXX{JAJNNC|><+ApSupoPV0&^?U!9{` zHtBV|%g~8J4=3oht9NrNWz6l4BI8~kd+HXg;B@3X^6pdC>Qv;1>wqOM1ae$;6v=r9 zw~TL@#ul*{o@%%=?{itva3!rt$>#^-ct9{#KaX&`YpRwQ;be8)oG>5{_H|-1yx_0Q zXfmt*3h9uxBBR~0+vLdgYN$sfBV&nK(ocisz!+)xuHlH6Tc44b4=?pR0xi$(CD7eY zy)(K8DR&*^%vGuedrj%5id+{I-uCv{*?j{C6M%#EY`^=ERM_TDmn85k!UDCkiPiZu znu3Z=PCfq|9G+F0-v$*@WTw7@g9yNZi+=Uw9oxJRHJ1(U7q0K`f&-B1S9tgdIHWT5 z-VE)10*AA;OmHcd(i<!Lm9y{(AjqN#!e)~oR-9|cou?WAR^qlYDmlCvh<Z7VDRQHr z)^aemo9UZcRKY-5n&gFRS~_+6AzokTk>mQxQE?oZPs<wOdT71Vxn|eV*p4X!XE827 zV>O(2dqUIhW03b~(=uw&Z18-<w?VUuSFNI0VhM>$q%4SR<Fq~VMCjI*5w97JbEi<b z)@`+l3Prf}f}qp88`d0H=jK5*Uf{}htU4XGuNMk^Ax=ALB=Jja;B%;*nL#eGixidH zJA8=u?SVK&t}o>lF=%(#XY2uG(H&ljUVw|~>@TCu5|yN!8Q&fQl4%98SGBT2Lz+z3 zM&nD);3stmj%IjMHkddE>u9$F&d7R@C4Q}M%j)I7A8*VbFaOs&y{C~B;Xm}wE9#_- zKJa)x+w${&mV*3y0&?k9SoSiVz1=)niywKGpLx&+Md*@mjG=%h=MQt_7K+f<DVob1 zIeg-Mj)|7mYM`M<i6(AuT2p{O@QJcZ`<@w@s1{$&WPK_8&&}mP9v^jS^Wuxl@35nq z2sb&0{l#~7WiH8vyjFS2__`y1nPl)MWvuy+gJNW37>?2V@tT#7bDrLO02TbJ&N++Q zKV#Ye7Sl4_V-|ik6J+zNVKoa?D-i9UkhOj_ljN(3Buiem{Z=t&2*`6r%4@xx@woO{ zFK2G;lp<^6a~bjr@w+ooel`*1h4@6lmp`+_;7tmFw`Nx-<KV|^HL?h_{}AX0Egu5i z{gRA@h2da{E9%2=c=IwR<%9T>rf0(o0U_kr@Zj0oa$zBQ-^nHOzf_L=Rtpjo7lDfb zcgD&A^v#Vb3Yk_X_b9i!dq-b<<eKfHeygrB%I1fH<qtuDTT-0P)QEc*byn^wN^Tnx zHAw({>m#`-J<90S3>RQ_4crwoP};-2y^Auf5e2<h;mK4ZSI0?c<-@sJqSLOrW4)RR zFn9}nKPNjz=jYHRsl@7cCp9BlOQ(^2Tkz($2pNDRlzvaGFceL23uFP{iWov>%x9E~ zP**bjka_v7SxHkFo*fukrmEpUe5|=(Y#!)<g$nN1S^y}4K~~?U@q(w&L{U+CBvekc z%sB-z`E4HWr|<mu%A69*(>C>s;aLm&F>1#MkNSiKewPXbh9qfcP-tQzqo^UH1+4sm z@SK88w09nx3!=<E^_4_D{{~;`IiTPye+_*=4x*B*@?eF~zCik9et>cfc$WNao-}jf z`;!Up&{!h9QYekq74+?(Ft-GE%7{H%>~@M!f>z5z7@_)Fx@%(4urT8R9R+*AiM$=~ ztmd>dlvnK?cG+s<Q5%0);ltd-92t9S9Nm|l7p*jJs>QRJgb227#mRj6-?msV-*&@w zpV*gyymTE_UocYr<K=%bVsGfe?_lH!9@2M)IzV#9lF3(g3{sXY1Q-+x1|(pA&t(3= znC5{g%>-vLaqKN3dG^@426I~Svabq*v$!}8P%Zz?ku^PzMFY<Kku>8Kss78Wsbar9 z;0YAVO4!cl^4?wIFK<0p)dCLD?VC?Re@(@fd}m?V8M7o848nLSq{yJzAom}oE6acp zc5pvr3&~NIG~SwkK=nb9w-?SF>3L!CdoOv;waPgM5cpT0Jc{?1_z@%?%JZ8?!M;5~ zf2uD3BQ8R39bEqKAXECFyccqng<eS>KdSGq-uGv3yO2ix-uwRSZQqB?|LObw>}{96 z(9iGt))Ds0elW#pY(9=P$2FFMJZ*2fbJrf7o+I|F4ZEsD{0L1UiP`4Hrc1?iWOj|$ zSiduFtVo&|0{M)C9m7#v%E;^vm&5zXRYwS8_<Ab_y}G`W+IFUGfG~JDrPuJXGy5%q zutzzKgRoXZQm*-8IE*)7EMWI&pQKy1;W?d<bX9fJ{dtA%x@mV<2|xk7X3Q5Nh_8|l z%P?*1r!-S8eouzp<4-0<$Egorwe_)RW!X)rs=rlC;#{Z~0@nzEzO1DxvJVggA*Z`> z-XkS<?)4$(@IxP7LmSxdA%0KAm4ysz0v`Mx7Psvj29pYWl86d}b%V`%XsWU2L*rQO zCEQ0tyADU<AeWlS%$yXfM<%-j4q*`H@qks+eHu+Nb6@@g<xQd<$iR~QNHg`?`tTLO z)b7`mK+IaE->g^_`I2A?Rt$s1Y3c8BM=u31GW8^4mHsYw^hX}n|Mi!|lKG;^*1K<o z!D6eOexl9#^v{T<PBVeO6HR^1S!<ZkczvUdd?EgMKCUH|OGH1C8U7Am^hdVnCE@&+ z_@bAeJ3Z0@OQ#Y|dJkgB>DyKFarLiA*9-Ay^7SuZ`u!kaUo-uFLHGDO6I1!Y!jJxy zUxw*%6i4HtbH(L3pP+M9nnUNhnV8_!0O`$*KNke&L)LbikJI7euPz2~1&<1B-$c_R z7wUL)bvwDR1&4CRjfY|0NjfASCXKa=x+;abJX!8GMpP-eN-lOJuKotaPeqdf^+h3I zA8g;Cw(H88FTz<R5VUYiq}U=ngrTV*7|DA~Mj-1Yh3*lb`sD#Gedw5%1wMV6u^1Bt z?GObK_+>{?aEKy*1%!4OB0u{SHvwPs2M%>?IH_+0tL_g3s|&rV2`ln>p%<I}CIQ<b ziNmi6*uJ@hz>zh-yYIDU@`V5>`Dl`I&%&RPjg8+Lv1Ef}6y2a1u!h6agjPvP9fXQF zJ!J5^(-Wx+o!avIg|3W2730Yg@kQGF`@UqkYX|P0_VZp^QwM*2@1?As2CUd5Z)unp z;u&c(q>D^8n|-^Q)<aUB_WNXe@Y|Ev3n>7=S4ZW&`ip#*dLU7MjV9~8t2se7oHn-! z&<_D5-1ibxR<L|B$@|TyrQznS>Po5w$G_hv^>XL&Gqmq;TM91+FV5#%eq3dZq7seA zz7S$5rU-ATPHCFmUQxMpePC2VicPD&0Ca0|KGwU5Iw6y+NFwUt8HJbFaaWiO=ELU8 zzfDae&PR&Dv1rA(SHmGP=T4m07a^Om<6zOlAAh|3+s|?HGfnfqf|@a)7ZOwH?~D%k z2nBzGn(=G$(*N;G#Ecf|tJU-YG55&!Cy4nkp=RXO2jTLYX80S#iVvz}AdS>t!MSB5 zA%nb5MR=gxk|<xUs>pLL!oP-C@hJ_zD-6FPR{SOo|17cM?~&Oq#EQRzkdzTfI<l25 z3BIw_6_Kp{DbTFF0(MfbQy3CTv9V78=q=5w472g`DzsOd-N;|!-PRXb#=tB!j}KUV z*sjy)D7h1ka+<j?+b1F!67KcB%Z~+mok=unZ(?ZF%C)vg^^zy&=2nyJ`M4gjlW|ep z+3M;;mfIJ&z^_^PLTp8=gwvHCvHMBkp(g%-kmZtnbE}^+$^d+P^(LPPX%5e-+!a8_ z3H&O+=X1cHe@#FRcG4t<(>n!u@FhZOkV320*S65oob}Hc+i!CEI2RwRZTT5SdW87u z@2#dc3gS<=xP<wB4;g=ki*G0EdEP!D<GfsRpf_av87?lxj{q43T?$SpoBH%xM4(84 zENq>i$G%hW&uuL_lb{AZIw-N8f=-|VfHl|Crkk6_xEjAk#-HKhQnc_(WISGGn>ga! z0atgWAh(@soz?7al(A%TVkPS@L`?MUaW69a20f`t8_`YOv&U-K&|PxnSCWU0hZ;BG zleDYO$6D&Q$795K4Ly~(p(E=Tf~(s@Cfg0tadX_McaatSus}KNzR!)NBu1pCz6%bg zOj*Hlta8<LV{CXnh<fH&9)g$uVO|=scbnq8j3oMhiKFz4XW3p;0tfQh-1(DvXYc}% zUyY0T7N&`1Q#En<Eg@kc>~wj!ZLcxzcS5YzkQj*VmH?uIFp+_5+l9yC?M7uL;5Ye_ zGWKLpZ$aoF_JOnlujZ}2@}bTC_n-3W($8j5db^zY_Rk7{DKeVBYRUgr&t5I{>|d?t zneO<;js(u7LSAfZdY&SI!TZY+a(m-PGW<b#KZ{$^fQHBd+8q4-**S~-VxPXXWiGh3 zWEOuqK=zfZY(X}2IvND>VE`{GFI2)bvt<2g`C)}d&R4un@3F<hk_G*0JwUTR^I+c? zj7Hf|mk8Uk{W^KN6acrsF)X2AJ#Q9DzX-;o5c;!dyfvx*3GuJ|+M2o$3jRjvuvmsE zCg&BBK|A7%22!!MYv1klmH9$I^(mK}@(}aGnqlNiDs))PrW=tTg?Vc5fjqk$zhk)g z3Iub1o_fQDceP_W*8vkc;e{}Ii5l6wmbn8W9}Y4#a;{@o-L+q{t(I{Dc{r*)f2FsM zYTnXQ$*wF`N$y9~lw_T_0-K3iB~Hf8{TRgvR*xrPZo4K^$`!8dA$ZT+1&Ka#tt#N? zbx$8co{`U*JNgIdJft_E(Pnk$jYooL6*J#;L$ejt?kG;8Y6aPWGhJSCnNE&IZ?hpo z)OE!(X7Pe5TzMrIh}%+uL2W$wmUx21`4Yvt+x7jlFNuzl>-3aNmu$>$wR||Q_aSF( z680i)EH>#BNqP>Hds$lkQyHj%?@PR%IsDJ%#FgO;Y}<1gL9!8h98)ZHmJXX~bsmKu zcu&5%`|aM3^Z-k7;er?(_j7sx>L8FwesAP^ckmd3A%?^C2C=*%ho%i|1-zuA9ly=^ z=w@BHIv#rb$nW>-$<3}L!X7pjEYBX=L#!|%RyN(0dh2)Yo(dx>GR}){++iB;=i5pk zm37{nt?ts%)9qf5F)R=BZLk^EKyF@gJt)14_4_?}wSoS>-w@!qESxOvewN7NDs<&@ zJ<%9@$DBJk&a_f;+?2+J-lBHT+_}pk1XfRFNcJXmoG^BvLp!t!m2`=))jYv6DWYf5 z#13?ZsL2@HH&GERe7rXe61s4JCKff{WA38aAsxpB4{Z~iA}jj^=i!8+R-EmrmX!7r zZ5j*@2l{ml=ldJCV`p5EZe}r@_r&(VMMocp@}k@5e-THJUuf$m?-Tpb*4RI*t$(2@ zr(bGHI-8EPFuxGW-`14>w4PhuJo&kv6P|i5TJ#*)7fD6!D6$L9sH_Wf5xF}yZtg|A zTZkg|RqxIwPQ*7IG5pePg8?&Cgf&R3)v8Vp#q~y5!zHc*Rl=ewj%cxJ>1nbv1t`fE z!VH8wiPcGSM$y*KUdFbEyfp1L+341d^v1oozKLXW@2$Wu5;X>(e)P3f(XcQZ5Jzyn zM^=~FtR{nvX9eoW6Sf<z$@56B8kpr%p6-G^(#G%(@5^J-IQi%gS8$#v5t^HQ;sT$@ zp`@E=typEyF9)#xBTne5fUN9Y4abOWMf%iEkZo1o2?5_AlHro!kij_7p4MD$FNCOX zxn_e5u@OPZs1{Muw@sZ%X>6QLa4J0i$Pv-~7L?{l`J0*3IaXbHqU2(^f?Sst0i@9h z4mq#fb$RfcfX97q*Q8>vW^svu7i!P?P7^tT+3u-hN1gOexYTfonk}_ujD`MnEOHUs z)?u?Pq4lWN)YJ?}zXi%!o(C(|r@PPXivsGX8?JZr3frlQi^*m@t<Mw-v^8V3mcGj} z^EzeclYcHRw7!b2z}LEDTs2lnWEfYrNz2<Io=WxtK5(?13ihlj)gfC0`;+W^eC5lu zG~@Zo_CpP#ftFpTu(RLBUVk>E6OiBDrfMUM4bPwW6)@&S@eu7{mlgneM;*8GD&f*X z9mHIZa)5i+J3gTGA+ShlZOUP4XiY|6l}b1AJnLX)lr1Wn;-N$|0{H1o;NcACG{c@q z^jN&SWpSJtoZp}Eczzy7!r=8qRaT{o@f>r+x{*A>msLGdMD-)f-;ejc5bt*ro7c|C z$Ip9oPsZs_DB>fAIIfTV)JNbg;>~&QT4mX2j9FVDW{X(H<l%lbV-V8sx-hALmq&~q z=DUSJi&3{h19)pDoBgyag3}8zopmO&91#w!T~TpJ<R3bvH%5zO4Q$p}Lca)G?S?6g z8lyKTU-OO~Oq?2t@wqhjK&toD>UJWvy@jzWj?~J1VsQ6MWwnan+%JI@d808L)4<aN zLv4JjQ!R2TbM;aQNB?{PsHOEuKwAjjw3{0E>K5?S37NXCgQh`y#XW5B0$y2?YGhPB zuFjO)hPNPD(%_c5!<O=6DmU`p6fp7BqvUoNN;LPjJ+J`cGqgT5kaf?Qd~@w+paIX# zrdUBpCaDpRcClY66A5h6ZRUs?i`J_<ZWCEs*zF~`EBa}y6`iBU>g3Esew6mQDfR@^ z?cHGBE1}JAwh`S`D$r3A=7=Y+057=(Ul<<R;XWr?J*ica=0%Bb1R2JIMsY`);2I{k z!(GXorJmTJI6eY-Ak5W@Y8)EcAqVYx^+X7&TQj|GNj}Rn55Ew?3eI^U*L;%o%IS7% zru&5F9I{q>?FM!dcQ)`SQ2eP@?{06@w-Zj5JaIFoYRidcc)fQqVT>9@Z7S}d+Vsrh z$6`pci9|M6hzAlCu$L1q`2H*ftESwfGDd+9@8!MG8Jj{ep<_eGCxXyyC^XW`n%m$d zSV2!7ftp%xU;gc?WC?$DgHanuX`5DIqB?iMIIp^Kve#GgSVVuk{69Ys`TYAgN`9fJ z^RNiZ?t$2n%sD@0p83c-L;Z?(<~8K--(F)5UsCC)I{b<BUbo@l!dcxCD|s~&o@RlU zE)(Mp?_tW5Wa8SF6J*HZdtMA+VtvU*)c(Lm^nr}%yBT9Yp(E-(GvVi*&y7#U5$=_H z?bYabcH_tffy#3P%MZdxVfyac;HE!kCIV0R#!V#sjGJg-Ci)aMEj}}we0xXf^Kb<5 zawc0BICPg%do3mTv0w91c>R-(&2cr-g`XZzHZNS(Q*r1vJT_?O+mUOIn{i!g%6%z% z#-Q!(yiwH^;*rBv+seT~+nmEAM>RC7Y&G0mrHB3Cu>Ao>&t)jw6YARbB)^7ANm%b* zh#XkA={;RL-Vto~PERqaq5@20!U??^oX7PyT2LLV+}HkkYuvmhr9Ie$j2W7E9096_ zJ<`lcMgsX14LA2{?As}YNc_r^M-nEJCRf;W*lyQA6NYEVxb07Ba+a>u>5OD6r>Fs_ zUxXVHwsXAN<?Ha|%|covy4#OaPeJT1u!GjziyTe*N=UcP4I+%dH=xMKqjJrj_Af-p zM=mQ#SjHa2BPT}WUXS*Nu`!hk-s^F*hlTFgNNY@GiH7#SF#^S#0T_VZ&&jW&h=ZTa z{tF`z*DeL}w?}GF<>=|15omGZ2u3^zNA2NaI}bJ?;Y0NoMJzI4R37-kh*?-mn(md; zXA@n}#n^qVqbwG7AMY%GXXKiZ1J1?5!t>ztNyykiy8eO&K+?4G@)o{-vi;af>YhpH zjeW>{un(CJ?x5vg`uvh6RAQ$XpWHq+z>l*C&V|6gRY5*^fiB24^PbFzA1Wh<CkxRR zLGTyC<i*%QwFeX7Qc4@Vs-V{=KGw0ke`r4{r_@*0Bhm$L=ROK=E~VNEd{BWERsyFi z@0x(OC@!eAJYlKId_3io9{IpA^g_I`41JpxC+5$f@}iqw>-l)`?($X|@XFPHqo_Q~ zcMt5v!krZL9<Oy5nj}AcMa_YiTER!9;2ZPM`56HI#yqqHoiB(6gMFCK=o>SmHA@RS zFK2C}B8D#ZcVy{|i4VI{Luo9t>d1c~{4o){`-;|92=b#z_WABl_r4lr!1?)Ll9B1p zr}pUX0jpNK_I?HCgwt)~cu0eWTBiA&uNYFUARS-RF4NtNXxCvS>;{9~QB^*-ryDlm zlR^l|slHLu?l$V;eiiHdbY}P-Xqdew2SpV0)66DJ4-CUyjbFmkhC`h=B>Srl9c2hA zI6MY+b+V1v;8btW4nE-o72yPMexi(5T;qC6Q*m{-foEtC+N5-XMGpIo2Q&dv3)$Mo zQyyb?@_tUJIwhs_cFkjqICsDS+xQl~pZF+Mw}E*^Mt6;*cOyc^yReBX-^+_Q((Y}= zGJ&=6LW1V*$*#kdt4InJ?cu;g(?aozohE2I`5PQ%I2?sLl$*`L7Rip5^ok}Y_NaEo zFeYT6mRQLE5piyWn9>U=^p7UPKmPq6uPN2<Ool%}d7}G}=ldzjbLKJ^)+j!pymeCP z-$nVKV00vX7zq7a7`>5GbyDC@7`;)9$F)Df=#L2ds6KyB`uu%_Z6J*}-k9PBtE<t< z8fUl~)|poBGpz@`aSGI%*a4&VfUGaL2l+aT%r~E3g^<zMJDH`mHLFnVKv3lvGSpO` zupLft0*meon7SB!Hs9oXM+2R_4S4}OU)0?RKHL=T+%tQqu<C1WS_Rsa-Zfx0YzVVq zTsQ=#5bK^wZ>NemckR6mQgDjn&_FR<YKrX~g!FuCifjO=8He?FaAH!vm%}02w$xRG zJj89ySiW-7Y@Y5{=aNt%aFgzPOmK~!nPLfs$B^bO{qEA907*c$zXn4euS1MrGgSC; zA&DBCh{Sy3dFu$w->i@2K&G<pg|JeY?YDt@ZC%mWlxJzutFDTP&enAkcCzi^5XBcI z1b&GwW6(E*9ZZ0POBvyUoZT}#8Ij=k&GN;(YC+h7jIc$+GQ@>h0U)I)J)yIOgg@f6 z<=^S5BRon*zOn2@%${4~gs_+|>AXwv>_Gs(rq}BISJcbrALa&vJC(_`SqSKRfR&pC zFPlq&qu?NAE&@N&cg~Z~?i2ssqSb$&f$uB+y(O&n9RuI;B>juDJ4;UQ{Cn&-{=IJ~ z|5yhL-k;AOMXDzFxl8a%1um{@KUD%iLKN&l>H#c&ylJt#g<Ih8V^!zZr*NNacht3o z$VYN$LHY;U@$ycZp3|S5M^g524#94{*47VxfoB0&tq<e#QW&(X+MjT+E-eqKZ542E z`&K2tXq^}0!Q+>@Kl1o}fu>(!DM0z}(DdhtXij`jLp#iHok_gnd$13or=c=${?q>1 z(nR{%L;I_$dzt$DNqPT%QaB|b*;q=cj<f*DY4Da5{z1$BDk;3!62OM%;bH4-Hp$?u zZ}H%z>+&Sh)GZcN`@1m?xPP?!qtJ4vUFCDtdOJ_cppILmGi>^olETQxVkzJS{9>x$ zp^46OjH&st-xN)_J+EdD-wIiyO!^V?qx5=kUx=**Z7N@Y_P%M@*1&dpcXHfbTR}Xw zR!q!rV}!ih<Tbnp+3})Tx#4XlNG?Za$|(WpsPOK#60en{Jmu+T)3(qN&JU7$4fEtW zcq?(>=rV2aq~AFzR5$S6J|E}X+CUJ<Syw<N30mzX8?mSJ{ZQD)-TiJL@`30I@lsov z%HB*pbu_w(ZftqJjDd*Sw<L*>Lz?JFfB@7xM^rw`BX-c6@E}r6x8jgB3gr|;JGWF% zrC`JF;PsK}(8D#Yaj|6+k8TQSzRr>3#4p?NkY~3ZXO)ie+;ZORjH?Y7+^V|4!WLf_ z!D(+?&ZsU1Q|shmjW6Y0|Myq+iKY`jhBJh}AI^BCj{mRE)8hX8w6LO~e&tU`f4qh= z7V={Do#{|L!X+PK@WtBha0NcJK?7v(>(EBB1Oa}BX!+-%4WI_+Z}jZMC>Osln{*Rm zez4Xam9?*JaE}^CaB!Isc+~Tg`S5XVT8_Cg3$UG%$-g8Z^J&e$H1@t+^|I!VtcNee z=L)CqSr6%~Pt}2Pwr5DnI0?YCC=kgVhUxlVpBh0bUkILyhgj>=OJm<cCGSb7UGIW5 zyz67Lvbh7D;B}xSD19`|^vw2!6kBUzvLLbRH#!2$1MoD$+e%~Mx*6Kt*Wt<N^BCrn z^K9OmLI@^#M}-bk7Rc?~Ny6Q&k6f@4cX@lfwkL4(x)U9FMlaA1r>+!jR40Q>Cs#Vc zmwLS!TV<8AjbqCurbKZ~9`d%BEs5H*Yr@xp(tII+HJn*d<>J^JuQ%QuxFBJ}-&7vU zs-wA(!e983Tzhe##&Mnv*?w!t^A*`c{7J^b=Ft|J<5$JthEbO8EUd3qQM|sclK8A% zl(V$5W1~M47K2H!1Pn~&Y<BOghmqhs3@wVKzfi@C0j7R1z#d_Yg%X*|J{n+4C<7SF z-%PNR%zQ_`n_9nguV33Df8k(%bFW{?mA^fmTGk0bExu8N;eJn|eD|Mb(;F3Xz042! zXgY!WQopbs6;}@e<$f{ZJY;(hkTG|;;A1c%b`W4t@Y<XCSiBlzz-(L$EpXT1FXg2O zn_M_EdUOO8TI@fJuV*Xr(@X?LP@&Zi)l`0KPksa<9-)da@|CJzuqpWc@@B^3K0OuO ztKs=vhrZOVjoU}FC`-n`h}{YdTY~rdJt}5?nLY)c^GU%5h?NMu0#>`wKQGPG#3$qK zd(&=_$=@*Ue&o-*zOiJ2bMc@)*6Tf~tyV!!&#c_A2lRxmNjEWhh5L>)?G_H^^<KIv zHX4Uf2M)-*z7DpNle3onoC!eb&n<tx(ZF&l<k}{Es%mssXeJ04dpm*WyAG!ZeFy^c zZmJbphPN0(IhV4XaK`PtL%bqvQOK8l9=496D>7xiZ=h{m^vgI?#@H9Qe~Y$0B>4fZ zm_w&X(Tets7Wi5BvX*d@<ER@)@GN_hUF}4IonyQ}SXvqaAP<c!q1jEbXQZKh0XA=< ztzg5HObM&jlcw_ZaF`<m4%wND*ZHd4UQxyKk28HkF!J(3i12Lq!<8KFid-P8D~DC# z?G|n1A$Qu_9wS!~g0l8bf^cG8-f;5fk%KEISHaP!$VFfN&<`89o3N=U*f7~|>T=4m zgmt#kA=PhNhqgOSDEIkNXXkjQmQ-<IPe;X=kHa4?|MOxJJj%WwH3-lK`^O!XWV#x` zCp^E8X?)`LYva+Qw|Obo`b=p0!#$7U|IL3Vu+ID_HglFpYW5z%P^sk;t3WxG!=j=N z%YTS3!JXJG;GPZadx60d$X*+J9wh=xan*ONpr7tp+GZZ*pv&BQTb^GFpO>PCbg{AJ zt(+>4!e=G@eC=&FzBOb1v-|OgqHLB8`73kUyA5vn!GS=}UA_1?*MVb+>BNa89^EVw zM+<I!43R)2Ew1lMbXsvfy4P+U>5&^}m!Ab`%We`q-HTHW4$c~1ol^QP>TVC$qH!d` zYSr1w{yJT7E<g!j+B1+2pR`svqDq7wjkapAKH&PQX(kS+!9kakUWzVueB`aoNy8|* z+%Z_HHUv?h5pfshvA@+FU-vM{ZXz5)2khe9jx<NodI^*{A+s6{jrxkvyjuxR&_3Z0 zO_wr+d~0{?iMHxcYkZu(ThLxCt}6q|A}1+On>2T#iM$Xe-x0IzEoCZ#u-wem_NZE) zXS%<)SA1=rR)=ZZT~)LoWe2USOE*l^aTDBgBX?w_25<mW*jZ?2f*M|npb>d+j2}nk zJ;3&hq&Sd=4@&Ws4W1uhW~dKMt$7<*JT+^P8fHl1f|UNO5=l(afIJ-Z+s?Fm;1}-J zliJESHI~lsbCt+qtGIOgo(BF;>iEFWDhubnMlB|0w`R2OWv9T<Wf+6+SVEW_2uoFt z_s;ACH-#ncmS;$!W~gb7D~VCIYXB&hZNyB$m>jfuv(Zw>)2xj}8QZu$h5=dLnTv3r zu`|HsVywv;?L$bych)r^^wSIh1LF!oYnDr4#-gp^s;f0XCE2FejxeMaal5Z?Y(EC< z?oh;=dE8M{LP7iv&Et!B^LXp{LNvOG=7PPtd%U@8d|98TU`GlB#;#gA<XWFoW*o)0 z<t*fCsj?B3tnWUP<#}Ya+Coabb8-n8Lcv|DR&yp+5`?fv?0_Qco#RnQadTEr=-psr zPOdp@mPjlk>O*-R67l5X0s8VE_7uT7!RC9V2t&x`U#JvE;2AvVuBh{3Ecy1&^r7@? zefTW@Bqrr6ZDtUoLg(LUv*)#cnKpZ{C<@S1oBdY%poLzy1?6MeiQe^*xdq=nX|+0H zJNJmMwuU($Ivu6O3BsCzq%N1Cs_V<?iU&?3$cGBH;qd<>?oFB;)wXoONsZj9$*Rh< zdS&$nH|^OQ>7~(10<*dUdJv5O(FkTV61@P?^Xm)bO}Kk_oQM<W-pau-i-XX1wAp)o z-`Z=fZ;cm?b@avjphXdm3q9Ur0b12{4+>jq;-quEo2m_1;r-^mVKO8HCa3jh*YxD{ z6tjMp<w{KXGH`Uea&kL-p&}K&fylgOlg#PcVCRKHZqKr3YQJU|USys=#%4a*g@t*O zQ$}tLd^SN6Vhyj-#w${ztew6V>AzZrQ6_?lxR^iyDMYB7aBa$GeAkgy`9tN1#U4&j zjZQ9|FZMTw&-?JAPrT`#M~=OFE_N!W*|NKoD0`C5FY<MF_7&vVVcCw%sU}pO2m{*O z?z;oWN|}sd6Ydo|<QAg2){-W$-If8Y28Uyl^t*V%a+E+d=y=SOt}sy`IY;!<e-!e4 zwt^|VmwU3#Y-dwqUMawW05A(A!?~XBbQSIIIKuq>>Hpv^<+DoNOZC}WZN>;+WEMID z`{p4-yldm;TiD={UH+jh(c9<$vfS|2bNjVD5^b%UE`6)%qKQ<uzsiyQ>Pc1-@_Wzn z`Y3BH)9|LY@HitdKKIN0bS)*R_r(RP#@wU4z?MnvHPE8yu+>{{$B>UK^A8P>-hci? zy!H29+c5nJA_6yq<IME3WpFY`x!m2@IEMLjngxD{X5k4d!&u&kG2KRLWt<qE5>n5m z@ZxwXaPpi;HzO@cKdcwXxN+IG(WB~4_tEim5Q)v@5c+Kd9NVhm`9jVqjyBDk2RnKt zbOv@GV^Ag8$~sFH-7lu&1QVD1(qi5A>fr*B`kA>QHA@L^gkCHAph@%|%PkQ@w0zzL zhr*B%Y)5lvUNky?@$R8>S8)MkAx+wVIf|MaD=^TZ1#%))`mxwH-LaxGmyj+Nkrp~N z>^Dbhuk)Ji;n@m&{($kkhc$eON5_k_Q*zljhWh1@^uFpvW^YJ$j*v?;(igX{nbiw} zc0&NioETg#rM;{uRlg2YUj}xcZC(uyBoF3~49<t+TVvVymntq(=eDm0E2BvkU+7fc z$#>cK-Uxkp?<)QMxci64waP`Vyw2>;*6Ao(lzH1`9(z2g7ph8fQMfG>V!PMoaiMIR z8`;!`ikOVI5H<g_*@`}7oto(sE5rTn@=h%!^9&d*lu5uY2vc4)u8UvXYPRLvivlS# zT)8!92qx#d=tSwXD<*BGnkr`r`Fdn5CJ{O+-`ySSMC92n->RV+u!{j~E}|LDKD#)% zzT4;Hoi#o2mU=D)7qzK&QU(Y-+@0<=dOK=iTlYmSp2-nCwlH8Wzakz`##hb&;*sIr z2~X|REbIkcyu}LKg?DDV9~Fh%Rc8jA&s*Ujp>c6OU(VwQ!OZSvJ>lCcV^cZ<LEh{i zcy^Eu@v{80bF1*Fd1^1=CAC0hR2O2sz_v$}L0`!0aqYi;Zww!;g|*)>hi^X6VuJLy z&;Rw4IgI3&Ls!~eBX`+Py8tj>)HtmR_>axdTNTt|hRpY$zZ#*n2uLBn_1j#`-b{cp zZN5_v%RXL9KR(MZo5UYJ%SV&AKFg1qz<hBS+N%t-;;&9$kI=#g?CU1y2um$(0dq1X zlcQ4DX6%k?qFdn44&yt6FIg$gyUQZj9s~|h3<+BWGH!lzs_y&6d&i=*y2x!m0fTKW zhHO+ZJivFOT3@NF?UVR<dpeN!1hOwWC<q#a#L|M`Q%fEFWoMaG)R44sWMVE68M&Lw zSu8|nGf*J|<$yo8Q{C+f8uhoN&DSo1$;#GSM%Wgat&co)fHJz-xzUiYgw@y4h=4n0 zqc#?HnW1_YCJv1dm!QEb=f14Mt)$$Dt>EsTh!Z-<K`Dxx@v0Equw&+Fh=ovMZ)MeF zfX|1TguxEdhbe6B+slEkQB~-+n@aGw@^pD3VE&}maSNP#W22vh_&1WFc?MN_fs!qS z7vbG*VSg1vT8==Nhwn0PzPNW;QLlIZnPlkYV*<E9;;vm79ixu>=6<-`56$tKE2wR3 zDN@}+<qmElbAKkv_A*k4VBUOQA^|KiL<cl`F015OF_;D503`}_1&N~bCfqrF>>k*J znlm94NAPCd+)JLH@{!xJx3<^AqYWD@PNa@{quXwo^fg}sblKgG2Crbuj8RO?3hD=v zS>63wI?j3?>g_1#V8asikXJSeb@$1=HdU7ab>=E-bldkQV8eJIG3#M@mXgHAa|mtx z(RL5r1s1PZvX?VzgdAcv7sQ0ey*X$eTBnCP78bKCVA65BZx~|nwDp~oja1g#pmk0x zzEh9wiBd6M(RWwHuJ}{#IiOXuJUT<deRH2nb$<Ha(L|{3H<;GMWakr$vHZ6;V`<)! z{&H_#$)ouj))F1~w~iy$2Wx5hMZSu;&F=~1BSb|&KW)av{+6}8$mH;9p{)0xTlThE zrGB1>0Dd8E&zU*kTE3{PLd9<-JHqzUwLyQUQMvdUuPT)(VZNzUnqlrkZ+Sq^b*U*4 zyvb?3`+P0qu{!VU=D|&Q?#E(0m*@OaagOL%PlV-B>DUq1^Z>@DawYqu^;_?BDZ_lS zC6M6232=hU<V~BZF(-@^XR&@%aIEq6peBp83^eXS_7-*?_lrYw7Nc$D$|CAcGb!X3 zFi}thJXO`?rn@!|=!lKz#lObxvbI0xtg-fDPLGj7f*1YlhXz(U%4TBObLmp*Y>nG_ z!0N1H#NxC~AxHHx+oOj*JTcC7ijnHF7N4;DOR>8~mm+Luzt_YvVViwh*UMHd?tr`V zP|52OK~OY6)p`cn6!-3$H2F-I`j&%N)y(|?<~>AfZt;z)wJpywk_&k>V8kIE>_bdy z?s*JcF7x38=<vSPytql&OBH&S#{<I?QMmg+*P_i97Y?*xn}|am&F%HvYB?itn#~R} zWw5g1DSNBF9h3})sl`A9eXs#BXk09n#a9*O`ZhfBWO}yCR>roIjAA{dW7|R!Q6Ivq zJ#==pK3g2Uwk<LZp(0PegCrkP=Y_moIOm8Fh`YR{{D?~E)hzE(Yxf8z2~$p{rO%$A z!ye|jJCBxDm3Fk<hR*V*IfLQd#T~$7(nZ8XRPZR;6_<(cF%wZH`)V7PIE3kDk8G{k zr63b5>&kEK%G{2sXl<smf7KV%AQPNTpwtvb@3bY|PUI9<R&b4Q$28IFD?DDz=tdyJ zbh;GT)wY}JSaZc8h&Q^gv<sQP#a%m&_>7!Jm9x+Ib*m5zwLf7;5W6L4sTNrwW7hsh zc(hpTA0QH|)O)F}2U<#mL(YO{4x~Yhc2eYSe^Ju^M|b}Oj~3VLQ+bYjPK$AWzIF$l zn9e*?)8|Hy5h}jQ3q$b+3Yd;fTBtf}-(5iu7Mt(gv?7hLO=n(}i^ZTQxy`vG>#);x zabUpNAh`t6?zwmF@p<FMDhO>GY#iIM?swN^JqSY@7wn$4qZqC|uMejU;z$sq7xz8~ z@Hr2-<L;pD0fR&KY@joy?aM0TCG?Fn=4qkggmUHqv27#jFjU$coi(i@`d%szyna=D z`kK?$%}O?!k>0zriLqhfPi9Tq0qvOpI4@4k>^KE#s%(gKk~hNH{^k-6BrsyS$Bl%; z!bTkLFjGzniL@@_qh~YLM-3IXdUCH9^i*$t(go!W%Qx3Oyme{Kcf|3Mfw%GAL72&9 ziOOa3tpS4|%|=$7=<cKdH$*FroS1rxg%RM>A!ZHdtc1HgavK@2Qeggmr?b|bP#fw^ zB0}wW=J93oAz2Hy#ePs&hbv&36&Jk7O8rvRN2sFFI~>do2VQC{Gdu&x=$%TK(+tcJ zzSjoJNZU;$Dj+NgY(~pu5leEixay1!T<=XTj#v5S79Mx!5*@&Ag*gst0*#fcmBmD& z-~d7T1;11`!i$v_u@d)$!56K4B4`#p_(XEPiZXsZsDQd-%nI8;=S%Oy<IIHZM(BKq zC0*0M_ao<JQ-3<D$9n{vu3cX|{oOx|YGhYCSDf~0d%jJG(~aJ;<gH1gv0hMt`+~)s z!oiztTkf6BDHoDvI|Iq^_ox5sOSFG@N5P-R`;T2?-ah{?U*P?lhr~X@+R*7|n9u(f z=6~p|@yD3|hh9?potO0XJdfs=+%I}bUp~(&pZV>H-apTR=|A$6sAo?}SUsgjw;F1; zSc#mB{RD^8XfBISyj;csG|O#x)%U@fJ?*$U<lFNdEDJzMp>rK}T-bs%gDuZs&w@l; zRDXw5h;pfGdx4Ri$eweR(n>_Pb4DA8aT^#(#~yAc>Jp7`$>GYJF>Ohji*;n#%>uU2 zK84~~QHGjQvu+r;LddkhATHE4?~fo;VWc9v!E_z!Dh8w)Kkm?A@l@7>tF!NyHfZk< z`52QnZl@)t9ct?^O#8Fu1PRJ)xT&C^UCpKg!JQetzzKRd`KQKeym9fdnhWZ5=FUcx z5eDXNGea0XsKb6IZ8;7AJWW2FaA2Pv`>+n@vj&&Q{T#=&90Zpx9sDOkRNT8Id$o;U z5{YcwSrjO|YTe!l@A2Jgqx;?2HM!l38EmWIhP$dQO?g!|r>L>m%EdxQzLsy6n*-NE z1?Daa@@~`Xd%<FeJ{Qorfr^Wlf|2DXpdcS~i1khGuEJpCosB*6L`;C2<M2G@*N8gK z2RlmhoxKP9MDKvoH%b86D5-+cm`xt-g%0m_GQdzBT}12*yL!6{zYsUKo`--T4q7F) z##UAwE!Pi9a)PwZbbOI-Is_=vGZ0&bxHonBPBs`@w8AMWgVnx9*Q{CA1aStiCR&!@ zsjh>`&8wlek0g%zyFH1ISCT&jvSHAB7Hd%&$}&nSz{`X_5mF1e_qK?YJ#H%DILp~a zCduXJH~E4PaWJu#HrWYYOC?Nw5f6AG{`i1ShcW0JZ)y$R0Mzx&9mc&egZdn4;upQ7 z|M2ebdr3Ra4|_>3Hj4AfF;QpuLUeu(l=E&kG?b-?K(x0HEpebjZ`w8X>AIQMTBU3* zPk0-U0hnF4w`&PB#m2fsb#Cz!5I`)$GySe%Q?yY6HwBbI+YNA9w<I<zytd3F#B?L6 zdq@|#sIli}QLDDoj;SMOxbUtSOo?L~X+#iE0)huza5tT5)G~Cf5L%D!%ifH(SEb`I z@oo+W8CbqI?rrkEDg26J?wpSEV~I9td7tKbI`6t}Dsfsr2g<>QIsQ~G%q0T&R@IF0 zsQY{EeyR?kEOub;lj1?WGPgjiY624C8%UKdL{Au0UJ)Ei@peRw&U4lnh`BR2YLE4& z>xe8!AYzqJT~Jhlb?HvRD_fa2Sf#6K6`!lf+32B+W@p&oYPj|1H8sYXW;D1<u$;y$ z%%n7aE`<~;T&6q}g$oIrvo7t9keQ4X6;+sSgXqP+AlF2#3aoe{pdrJK@u1VS+bF=L z)rFuD<*O44;tX=QXzd$FJYAw>6U7x8W=f98FzGepUbtz8!?L1|RHB&nK4iJj@Od^k zl)4gHHGC~dyEfr=vu!RpAS-Yvo-O|Bsk(@u<i4i}uh}>@|3vIODYzr3!;~s+#UUxR zW)<CF7pHkR@NTL^Ft<vxxApxX7|FfAZIzShnwWVz9<6LxIG`pI2l054yeu~0&8%3~ zc^vz+Dw9#JTBbN7yWY-bkKg$dPmP;<B-mi?YZ4S1`X)I&{WsH4{=+;m`rp{(-n9Rr zT-u?$XMZEADu0pBXjbi|^b6E|^L8n6^>wv(9T@_H&KIwhUuELh`<ki$5J1(@LE>MD z!)TdnmX~EIteHP&T7BKbyD_*=8CPqbSa8!P{#imy>3xH_%hX#aCb2C2C|Q=FTL;ct z$VNy(Z~adN<}E?(Q8N$>FCD<$m4JY6Q7cWCP&t7J;vi`{I6K1$Bj#}aQHzT53q6o; z#3@>dlliXC2hR*!W34<Em@)PqWfB^^Y6X5fo>-`ZWj)Fj9zWzc+w|-H#nLiJxrVFX z<rgl5Yt8X{QG8p=_r}G*Rc~E9mgg}N$TC6eq!;<LP>%&J-a5`qB>7tK_>!UbqFtU1 z%1mr!BLa`KynQ`7ez~^L2W8eC+U37HAL~)|^NjrNd_0KQV@7^=KAwoz8Tq66_@m?N zwT$pX5A%7m<>D>vTCq293fo0Ro=h9bRLS#&zNg|CR)=y=DdKc?0CYRS%S<8}-Fa=5 zYC+z{`E4r^KF2HZsf#yrDEIF1SX_yD$D&k9o0o$Dv7@xvV=7RfY)LH!CUNClI8=00 zWLcQRuHrG_z;9@>&}@O4jgxm`TbYcn5F>23$jTgAs={-3obO;vtPa1!7Ppkvr~N%f z$B@}GS=C<)^VWH~HTrq4?F^PbP42*o*@YRb2BaK{Yw0oKhRdyA)`K?=iHh|-uuHXL z*21j2KY@YMp?G^<@GI#!pp0t=2*f@Q4&q%Ww}h!KE+H&sKDS%;#iZ<$h>>=?(*m%m zz@*5ewBwX2#$X}tp1xO?{4JZ;WroN>Ka_8*Z8CW7R}QsVwdSU<kGd{LakDF-_<Y3_ zHC}#2tYI?qJOV%Hg3LIBE|OHqP@G!4S*Nwp#hZ0{GfpRbJ&a?}_F?NXw+e2{C+4fI zYd^27&Or{%=auzDyi=9Ru<0y=;Yp7sNt<c$UToc?a$rq?GC{|}Y9c<z$?gtZZC#&- zJJ9pU0=$A<q0@xxJ`hv9am06fYoE(1U3)}};7rAj$R?dP`<gN9E6#S-X(9Q8w3m&| zW+%<fVxy=YB|_ak9t}|!M94W2u?wGJjOcHQn&d+@rDuk?#_rJ~+pP*wm#VGL7~4Se zG77q8)?QVJl|4)xyBmnH^uoC+RI5g|#$7g??nbXFV^S2d*OGT5phTI0PDPei<;ewW zd0CT;9b%`W*AP_TNw#AuYM&;%lk2mBns6J|AZpq-<q~dy`wc(i`o_L0c#k;3AaSPu z^zRyTS3D`7_B^|gjnk1wO_{LoyDCi@9y>8l|3MU&`+|ReJKpl-cKN9|A@`M5)0-Zj z_+F3ido@01P+kwmCy4xizqs*(;NVa71|KTi<@7U0`YT8eR!6#gwYGnxuLm#g@hjZ6 z@9$f+wx92OuOax$3W78NuPIL-Gz7<^vDS1v>tZ=y;L0~~$I@7}9oJIyAE-N~1o)=z zXnvsr`BKTTqVcb(0{%!7a4l<kS3E05{Z7<ZwBd(MRu?MfmN&{9b85QQ=c~tn{*~Ck zIUM7Xx2(-go~`}ZigdrDxE?0+8`GMJd=SRPkMBn#wXJi``8v250QP7Nc6AXg@<N>M zc_EdlQrhIS(p=I;-~(_*c<z3!>IE6lnVuw^pY}I$_IoG2u?wNv9ds>4qM1{T`4~lM z;ih}fG#Y!MxwXt*cXSZrb^!2VRq_zJm4eaIw|R%_aYM1uUMHqt@p2CO9?h82xiHu3 zH6;)=Cl*H<9%sYb)|@$97XN$TE^5LQS2Rw|iD;vV!V7n}=y$`|sVWxWT*EY~+QNc! zPvlz57G85i?tH#sF5BzrI>=Oa5NV+dGZnfu#e!{<>b=TSe60!i+V=Hhaws7pZV9+s zyx4+^iJDc1IYZ#A$~gL+Cg3u{AKd+374S!zfbZ=$i6t#_<>(8AO36@eAUhA@#92_Z zY!J#x+<4Xr8^R{gH4Ke$>b4fMRjIGTK@5hjH@E2Vvfu9@aU$I$@uxd$NnqYvn{E>P zT2E%O*(2o6%m<G2&I?JBh2#1zq_pCvai1+~)3O1bEwRNLA=qQRM+gDlrYcH`qjqhP z<DnlhHo-2+1Vx4ra)Godlf{87wN+k)Fz>kN&K74}JFq*-7ktiKO+8EL<z$Bi8r>O+ z4l6&JZYUCoAr^;%N5RvET6VYW^NQom8`r(+Z6oVfWHx1`9~(uun_hvQLY|7jW3l(C z5W!QdBXRD&$4~`0kGrsW`u9dHx-OmZaAD><Hc}#KHfn*`6&DDa8F&BZ>^J-uOzw+l z;KSsiziV>;q`{niVlY=J!Nm;9bo3_-=C6ta{wVtRV(wn!kMc<BI#Q`7sNv-_YQZf! zH;vNnaX!G+!7`C>jOJZqJFM40XsZm%!hpdYiQ$*qmFKX~K1Sz@%+pNT90H9NR0E*X z<^*gz1r2<Q+v}~+9Y+|!VprU7JsTuXgxI=#WQ9zWyGHLgCZYA%6**;gy&t=ng9<42 zB1uCoL^faP15Yf=rE(MjXG7aBnPnyqW328EO*ynD5JHAkpy!TYB?DpJahJ!(T0G?( zIX1eRq*}<dU=y~fejD%S0*Q<bzHIYSxJf$ckiLbIe%8aoc+U>xhGjFf+iDrJIqNsw zxPs_G(R+$GTEawcj!bi_Xe{3^r29N$A#yoR+>VOHM5g@&C0Qp7s+$Fk+c56i9fv5o z0Pn%N2P+HiVtk>@U_@TfT*4Mpqh9V_cbg-d3(lRDMv35ZE<A+{6FPEBKvzDiGx6Xt zB$Fy0*w05@w0BqG0Eje_@Ak_x+^)BsXmt)IC}*43QsvOt33Hs1B%PCcl?S0PZ{41E zg?y5IZU`8e^oC+o4iMNe^<u0r0=IXzx5Mx~S0^FMoO60dd2WdIqJ$j%tzJZ6RKdq8 zPsNP^+o!8#1w{`7k}`AdVrpP&&x`Oup$|#N$T4{n<kA{jy*cm102`3<76ewmyntYX zKs3}5L4N3Lti7TbaX%tY#75dKSP<y>0Zo1KB6dP~qgkED#A05n=1S^%=k;4kqFW@8 z%RA0PFv`cbS9f9|o4=S3!!i&%*b&+x$IA1VXuCX-P&3O2ta7=0uSE0p-H&@eMIRsX z-+w3$=)w)xSvY%~DoNg}1qZjTkw|jqU$R3`+@6S0CCxl)Gi=W~2DH@yNjzVsFdw?J zxE(<GaBdS6sIRSXL3GpWqn&*|2~*x*8C%@8%f68K5^}gfj}BM4b`Pg*(ih$Zt~RvO zO8f<I%v5)VZfiqQ%Sj}yC1TN>FC9|c*f|ImE4TI=;7NMVNvIbn2$^NwMzrcso$bx` z4Cx#8939SP8jsrmk?OD!r`ZdebA;UxB$}`%f>I{^R;L;?nn<uSp-vq)qp1^Qquw`^ zNSvALk?3Uo1{wh1meb5bnW^pX*5ty+PF{AFkj_+<`XP7O+yGLV4-M(cjwRoeR7G}% zJX3`MZ1r}T8G|}pr-cMbMu?P?9Ua|PCKk6ol~4A5vbKh()so8j!m$k(=V;k2W9{_@ zdgb6j;_GGG-oPzgi3icV!0|qxO3|I*1;mLo(zb*WLrR$|O(57599Iz6%S3g+J-3R7 zpw$@{PLqYorTv)`CG18=+AKSJ@NS+LI#dAoJ+X28d~jWf1;sSJa?E_45r(l--|VN2 zdpo9Xcjv5abdUs(^`U@O)7`f6Ep?#*gnNX}B<dix?oDx2s)fujS1?F5Lhb15)kW*j ztuxB2c!0sm{_GsFc&jYrMAsMUv?{sv^$Z8%PV{%)iB72EdIFAMIdazmNE2pe<w~_! zU_bYr8k_<A92XG{cKY&q%%1Rur~gHFup;xsBT#35fBHYIr1AcCp~_+b?%?v;JiYne zt<u0}=l&6U)a{d{kguijk7J+7wY~ES<$3ogH!YqqlwaeG=4(ami6jF==^Z$K?OOPz zE57(6hE?SA;>+Y3jJGQ{c*t_C;7q(^bCxR_mTwIUH3oGcLk9t}m&lE6vXDyz&etXM z3x)cE1t>42psU-(SXpjp#<zlVYLttYM>_boRjVr~w$Bf~W>d1S`X1&&PL$b)mFlfo z-y6;^ZWQEJ!Q$$;rm|{qJX|*A)#3S;Dxa=hWrgub%kW#Od@6vC%9fOw?3BE(&%f-( zzwisz=YMJM@SDHDe{MJU?ic)eHYwFx_}1o&-SP}@K`lPmRbugmS5b;Juq0?(8uURx zEy6U0v4e1?>*i>vJP+5>AqDTs)e;()Cy5MQtaDzm`-WDBM%Rywy@50l^L5V>_s9;u zt761M5x`__qzZ71@`!59=*;&mk9nn)523Z}rFyZNr@a%g#Z(P2qIW~=02oy7OlqF) zprLN}xT}XT1D9Aqsqld2>H%NO>c&LC`+~JFj!#Enp7T30viteer7deW-VJY@b24xt zKo+G~%63Or0+6AbQfxTxk{cLG7~}?A_+LL`qRN`l@ia69mxH-kV}c}Bmy5ahBV37) zqq5((8)axiAT9EQ6=i~(8;&2z{<>zhhUd%9Il#;oK;Me%wUqp!O5kUw>Cw%W`xx2N ztfL^S>NEfWEnjBS-;n4Kp~{tRzc`3ri}0T_M9%<AUe9AdN!cA$?2`^ux3g#(!yw1S z)XItJtG0<8fZ8J3LRFA6Q|5M{KM^x^YCuqL0uLnxuA$2kbJ{7}T`%UsRTWf~sh)LI zB!z_eAVFESy><z3^*J$a17BTR@sL+7q^t@Dws-Y#+8Aa_rp!JGa5~MX!>nP*yx&yw zoY`U{x(5Ip@!OdbM&1cCwh5nzeUHfaW{*3fJzipW1a)7#k^<&)#TnjhuSd5Uq`|~E z(_xC!LYq0Vb2M+yW}+OZ!Lm0u8`^hknIL?mkd#_K??^5Iq54LHIHFk`aPxqYD6uU; z7l-{7h$-aKCBiL_$#FMH?bE*_I5yv8AiaCLBP8q#V?ijklA^Qf@Q>D3|IMKO_Q)8a zF1Pk_{~v$jF89K_P5<L>RPHs+-{6<VR@f(N$LI#5y>Og0a>SoFwsmd`M$g}_ljDc$ zkH&-2i*Xq(Mt<pfuG6b7!K!-hu1&UII;}lCv&D-Om{s_MiQdle5joOfC^^wgvbGU; zwM_0;18VRX2g?`aWX-%}Dwzu2Tb2W%JD&66GyT_Rr^zB-^c>QcK{}T+pYh^Rh1k4R zAyUMq{Hi_Z5c$JL#LIMcEr=qXcZ;S8%n%BW4JG0EaC>*1|8V_VYfEuKVsr8CCh|E$ zXK`pB_BUCTA%zdM-JXp`I>&;GjeZNM1QW^o_CZ*l;Uo+K@fvK(%`ZlL?XNyPGG|ZO za2P)7%4H-QSLp}g36p%0PIw(<o4L>JvbKw7J1@TM0>|c7G4kiOkmGaXT;}cSpIKpx zrTxho``HqEZf^Oji|ng4_Gpp&TdVAKkv)C2%zm-aZi()W;PNX7Dj%bPQF?SB5#`R^ zjUua^vWJ{D@K{_M3kh=Xh?t$Erj8*Un&HLxinNg<(#fY63uCb7+?x<SUgn-Z9F9?I zh(w1V{(c{zN7?KM%fPBTXqByZD{TYxPn1<}m2MYH*61y49_VA|)<+8+C-?I?Js6AO zaff-lsXb<G+w+;hlvf<_X0rN>Bdh-G*GTd>exqfMvMKIFaNLFw$ORTY^bvn)oAwf> zS?2h`7NtVZB{+mUThAW{L1+2L3TX@1SXj{el1AcDjY|@Oe%egw6&P`d@CUMwbUTMD zP)oEi0&=d=JTVD2wnQI+CnA-s>*XfKvF0D+K4Q}@-5!Fw7oh`pKQ^%M*!uE1VVseM z4WD&X76&yp>IM?SrBXCc1m}Y_XlIOg;6)`SZV?;$U_L`b-rZe+O+%Ik^F5$2Ns1V@ z3se9R5GNOH3YQO9Hv6e9M;^BPS1soB^R@f~quBfzqxkCvY5v+E{qDp612&@MfrGIU z7Ab<2y+{&XJRA=@SIdf4<A&=+>Oy!v(jFk3%5yoUUeYSCiBe(AlN2B-m!Krto?Cl$ zwAXz_2Kar~R+nnoQ9Eod-Lkg5JJTA0fW=}i`!0<eq8*%~vPQu&Za4dVC(BoZw%5k> z;ys1H+l}jqaucu~6drVG*{Ox0g5`E+@*(oa8}Z9cKsxx?AY_MHV{Cd?HKA1Vl(>5e zVg*}H?cuaH4iuUkRv|mXhj7A^0lb@!Sh(L@FXvdj_goF35@NviD0da4X2mvgkGE*b zFZZe&kcr|#o^O19A&>9?9}AGgl%XHbmlWr>lu&8gqXKJj&Fqx+0E|eMw2<XEW>QG{ ztevRvw9nFWPvZ^v^uMm>!(H*J<R-~9`pNqw<#(mWA~PP=WY*n+mHmPWYwNt-0!E!b zq3^`rWP>u5{C^H^I^>_ewY;b4b@5+M-%6ylWs1Klk^bT-J}9yOspoiojD-=rHl<7n zcnz`1v<A!bi<-gw@!HqVp#3BO_96Tw@5c3g8_2920@P76V0q~&w;{ZnD%cQ@7Pu62 z0~s_PjfI-pMORALC(3DeaV!~y8%evdVV~WP4t3ZbLMapN@`<>p@vI?T=J>Q@%X92= z?&hY-hjVVFv0|5d;5bStSvHu4!Q$AZZti6a3L>=Ur6{-X8dD0}JQEHWezBqfB2Rm= z_J9<lk!^B=ugw1BUKz?!DFzrSHYn$EgYL~U?00B>_Ev?HqrcCx32^ltIqiVDuy=rH z&Ok!1?LlL<!#8<RpM2-KwT!)1vx!uxqGwSlyMqB?P5dFms-$*|7i(NFGHx1`2`t?C z4yY^6KJQiKG+S+3g*X_^EPH}?c;3$qEI@VR!<DmtB6ur%34qClO#ZMb)(<P{o`_CB zj{8kub`8)q%<1Gy+E%Sa#;;*iIA2c?f1%=y6KM|Lp$nk3u&T53Wy77Fj3=~(of*dl zuZyskLPG%xa|~qDuOdP03UcpTDOF^*jWY|9w~=>)INgtXx&((@Pf3a%Q)8Kj@zh7$ z-Hli(Y~<TT?xn+2$bd4_o9#sD6sx#r;OG?EE(^B~rOlCIiu4{)aTPT#(BcPEX>Bmu zX3tWx;cN#0bb{OEV#(%3Chv|Fa%ne0XIZNhb?UiohFg`Li7CI(Opu<o<MK9vtDpmV z<&Woyv34|HjhI>QaiT3xHJjf!rNaUR_8a@wM)K~UF>y_&XHm2rOK`R3>HpqwL|vek zsHw_5NsNY!oo3B3d-^X_S(IV<x5Z2S+v#%eli<?)?fy9E;y)d9Wg+!$iwiV);8%UN zm7)j?v?{Na?ke|s7Dk4CPFf|2Kqet2mX<xx2Zc6rDdD`tcx2{rVuEM>3U8-(Z8^WC zkkK?5F9wJCAiMT2tAxJ!O^5C|QSK278tb1$yY`=WIj^ORza|*GWDOF-Nq<%6Pe^4I ztn;{sjN0_xv!RJ_O$=N7l|v@$<pO!!i*G}dEbYTk?{e6ql;B-U+W6(fogs$7SvB(B zhw`3?hn_L}O&9lDogcI6yA1E@{7~5t=Jh|t$e^QD4wspcR_^&NqNanwI}yo9X0q@_ zDOre&N5_IVUuFJ+^qcpwYWWVl3A4@WS1eEL$>fXw5e@p|;1OQz`I`U#t|9Taq)2(O zz}%XMeELqx&v=uYoBvdbg0v-WSG_|o6&#PFqMN5<pZP*Gc;RrmZ#Z&u+!E3b+qI!7 zl(wvO-rCrSW&=u6PAAB8unG2yn4=)Ip1>g+q;WF3`^b8G&^nYSs!?`Lszhj8AFQjK z*s6MYA`ltqBh*XMgRb7-bSe%c7gJY0yaVY>n@s|tyvy0tz$8O`;J1#8!vaf{Sr8WO zrA3#w87Vd<lpeDw$8y|7#T7*H-nq$n+>?NH@6^T=cN?T`X4`@AIaNxwSon1<H|GoO z>QBV|SX^*E+NA-e!lslI#B`Uo*pUeL{cW2cuoJQdiWF{}Kst8L#gN}-WXe0$w282w zSdeJmiHG1yATClUcBBahSY4>yX+Tc_ZS6}5d$rI<a-b=)|0Le`M3f{W3iHm!v}=Rb zShYvR{E8DpC23!HI1Kk=f9UdT{jVgi4M9qvCGACu;7o5l7+_`cA1RRQ-oaDn^xWOy z>7ge1F%oQjTTRn;O>Y~NngB~$<Yew++S{r-{pjG(zckujtO@&ZZCdZTIuy$@(0_D> zN3lLe^pw9-H`%6j_hWV<=7l$mSBJ3Wsc7$md9ouW?9yVNBMlMZ5mZ9jx-YQ8i1>7S zr}F)MgN3Y4_5gx0_mnrFJDAu6dxa^4H19Ls^Hh(WwxQs`(iytXiZnkFdJFbrN7e%- zgTyrAc?)?%w7XH&jU=7!PE){qrthjxhZf~rFD6Jf_7T9^y3PH_fR|^$2MlFPYpnJz zX61rRu{i<H&F;j``$I#Q7XrC^gvANKu8X8&HBA*m$_;chg+tvWYnK@s&DbpniK1uj zk*T&9Vo)sf8J@;+*j)NeTW%qLy7y8l&e4u?ILJfK=RQGNb*z!6e{Tvk!EK?5TD>M1 zR>O>fzHQHyQhCm*2K@B@ydcs`OZv~Cg#KrNk~Rp@D=4)ua#-K=K>zi%x660Hq^<^U zy8H~7HeUnNFThGBQ^G5(_%DZN`4>|8zX>f*#5Mhxtnp`|<+%&{??B6eCX(k;nH5|( z==qq-{SA0oGo8M20mxBCMG@rbdw^ajO)<l*+#l#n)HWdsb{@B5u38>nRe25UAk$?V z-kae%hSNzuPqvERG<+-2)P^1uY++=({wkO)JNX+xs6h_(6Z9-rRz=ImX`#fGzL#7r ztZ(OYvrVPDG-f79Vd-val%@^tejlO-?Yvjm0F;2fT7Fh0+rwRLd=1BU3z=kLq*D(B zH#N6gP9M*OO(nv30YOy0bfu8_4qPWKwb|UMa=zv8<e+z_5%+PPj^mIm&^9P2-1+rj z1|BuE92J5TpzNg>0?sn0l%=;1<eJ;{{g$cW1g`YJ0(``(b{B8UX!9z#mmGm3r0=%M zXw)FN1y9F%LZn(n$&;Bb))?VEB{>1<Tl)xdoGEgA5kg$%;RRq+B{33NBBJOf6K=+3 z6J6bI#K8fvKZKW6_~lc3Cx>~>gWkz}G#q!twxlndVt)?^>q=WiSD5Fv1h1>g{E%0b zzuU;qxzBVFDLVp+7o=Vf>=s&{VEy{ZBcJ+VGoE|czwW`Gf!23DO}~jf3IXY#<nWp| z=WD>ma<fslz){TEyg(0VS+jei1vK!9*w*E?sRDa+4!v9(ReHSizy+XoQZCiC3U1q4 zaZmidE9v_oNm#B&x2dxAN#X=rYLTuAPFr#l<)@jjgj&u3LDMdC7T*EkTn@LczwWQ{ zhQ)S(apvgD=8Q1Yow>00Y36lbUAsR=VjtY-3o7WkM4uUaFUYgsvYj0JO(6$%0u_ZK zHmo(l2EHqe2^S3X9&|?xx|?Khcp^M{2OaWj*LD<)_*oV4IiBv+p`wzE4NfdhT}^Kf z<maQF6F1jvk22yWhQZmqV8(R*1SRvu>%7~R_ZM&1J;ZsBk+a8MX@4AhV%Jb3#Opdj z?)kX^PTAj|{_6|OJdPMI;deNL{b%8fIS6mg&uG4CIKSHSfBnjwKX68S;7s>3oB@6t zXL6a6UvVa|-c-Ur_hbKPjh_#66#5Y$dBz!<d>rT;S3UfB{qHrD@>eJQRoL=*+J9%( zA5}VE&ij`r`h|ktqgBzD8hQxA_DUWbb}zsddm%<%6NDq^T&FVJrnbi?c5^%0_NtI~ zIF+PD!O$&q`O*WWc#jyF)w7l`9>E=PEJwz_Pr+I9+<mIb*i_2iCPncO=x2GYnBq!o zFZ6WQ9*l6(!5a~FfjZ$7)WTg3OaLArC$Nex>UG#O)zBU*cd-`76Sb*^y|B2F*EpQm zXet)e_Kz}faP9$69*%ho@>7eDM2IIFmY>sFCU<F<r(VmbMdP1vq1my7zWfU42%|H% zhc+9bu_XvF;O36QL}pXQ@z7ylX51|l<+Y|K7@`gRcHW)tTyskLTSofC{)x!UJZQSs z*!9U(NByqfo{53*u3+9?7`+F_L4(BbV4aUfyL*-LMfrm)@Q;xFi#+GEULJda5_}() zxfe=nhEdtf5^<SAAE&=4u+^N$an-UOzB{m@$mq?L8HP(rU?p8gN5O%=a%C3#`p%ho z+cnJ3ux1%iB;YvJ-&N3kg*Cr2;(~Qw)b4!C{HBErW%h}G>N`O@83J;`ly>Oy3&$Fo z)}3%*LF|Q2BsLW%%>kiVQY;2af`uN`E|M0ZmB^DC*N93<o|m02hDI^$t5Z!no*f+T z6|y)V)oCRAdEmT_?#>*jVXoWrIItKdF}s20W>V6flDUjYZ>W6BTt%gE7-y)roK0J! zu&}3;41k>WH|NDiBl*DSRLz1644n_!&I+l;v#@sr3q)~TkQWLo2YzqNj3DCPRx6dY zAHrC(<DT9`f-n}xvH;W8a2gZL^$pr5^5WEM7CT6&8HC#%;B5C-6BP2=JOu#JPHAv^ z+J>ZXGvp4=H#hel#jTy@1fp4dXjh)#A5%`okNC7bx7&C)Hz{+G$mHo?1@-hdD)y4w z0yGryiZP3E;OS4)+LGU$k4FY&9^y5h$mj6RF~0R!TWIS+E7)l<LyZq_V@W=tgY+6+ zpR9w)%4=23+PZ38CsDv#iO=P;$32=gzT+a~;`R=md};>q<?vo&sY4a;3gO#qwJhZ2 z-|`xDjI?QckTRzeD3X#4XE++dR-w)5UC$(mnX1PhU%^9z<5*fNSls6(tUv4a{;^4G zmTTs-ou)l*d62@&q(|G6=_lFfFXfq^LS?FVCTER<V$UAB_22)KR6Ii`k1x<k!ta#5 zzU;2Iz^nc_==UrQ`_{-sV1}G>&WF^s$KEfPckeRz{4P708eUG_s94TV7HNFT541pm zCn2X8s{oFldxCx2(9uF58!7!(UA&H<C7*xa`tt-v?oOvICnZFrB_RBqhp$0$`mL8Q z_2}jM{P$P)ODUbTqo@7c;^$}O2Cwh_89G0|{Xg^a7bEk~9s4%#l*ZUJoDTs=w#zdu z#&7xew0)x$va!&IRsDoE7k4#&&c>LO$JOBN>Yv$XYZtFS-f2JEXp5WpC$`#GJ8dl# z`@OyPI@?dgU)^kL{l|m+{Pu2J2;mp&@fY>N1FJeLZb4INQGbY4^VpWvg7nlLILVik zH!zW-fT{#c#Y=i6%2L}sO0BXgJlFt%6vjE$PHO>t>@aNLaAA8e-P*!oWr1a7w$ABY zyJ{)6PzQE5SeU`l!Z>;Gro=9^8}GI^LNg}#wjLX_=s@0^x!jvqOu+lO-`G3$v~f$S z^YE68X)jS<^+C{Q8cx#R^6-}t)=YenYh|C`svUE#n&hsi<0&4pi<2K7Ld-_=Qrflr zTVG?CCP%z}=ZX00TcsnA2k}G^r$-iP@QK#5=KO69FZO}_w2HskEdI02l2W31t;0E( zk9Bcad-*;xWb=?BhtNJW-dQQ1+u3|0#KN+AeF}Y7!d{v}@w|Um0ql1>cv)3amUVi9 zMgk+WN=j^(oeVw%u;12Y^I=^sE9wOXesNuX=vKbkw-?3PKWpKhl_DRG`m&-Pu6jnY z5&i{6uC$*RXyQ52`7X*SzfjCI#-c4Q5U?R+?zz#<LDMst6W+B>lHYeKrisWA-a2Lu z@|xfC?M~+XofP4Sk2?vDQr7lP+Nb}Ip8tP6PFt^(%c^~UM@f@3p_5S_#aTqgeM~*l zT~<O&1kZ)TI;fMRJUkJM!>C{G@aL5EaIr{+OmgJc_M4<uXYKvp0U1Si<`0<h{V`t^ zAPTg;H|LfumiANs=2xE5uekU=&uYQGfQ_q9t*-L5A<|<lu5VIa`y@=#OdubhOJl^? zDSz~ro~_)Yi(~R_lx0=XFR=vd<7hG?jZXW1w1wWk8}Hp{lY4gi*OmP|;-fnzuweLy zU$;i>w1!Q-|9#*6I^>Wq`v-CCk=9K4rEwRw%!4v~$R}I8*3uR~37ovXVQGD%v#{W+ z&<7m8iUce+>fI9#zV_lEan9h<L|0N%B$hv(#dYL`NBwvfr;?^3sdSnTBfy8)ft$DI zdh`)}d$PBg%oI|4IdGGPcROiW*}_abVzTdgna%_m$I9DG`mizw)Vr1Sr4z-G1fx&# z0b1N%TRf25lpu@uRB)Abt&o@yH-MjKcg*0~6h0rVUXzkC^7+0B>I@~oI8t|r65)Jw zRo1>P3Vq;ch`0FwIe1a8HX*tRP6V~^o`xnwNXB8Vp(9WAq3UP}N5IH$Xpsp;aHx9a z>A(B1ci(GrqSL8t`|uS89KMWL;LzwPezK@@A>vQ}+ruf5h!X>S^GBHUALNhxX&2%f zt&(18)q~c6BPp4nIsVj2e;a`QJiz<!)4yWo|JcYs2X^>fx*$Dt`~|N;_`+-W4aWZF zF)WNd|G?N~%~DwT^ixE98-e)U5k7Fe^#J>8-|86&9zmv2HiYFD5QAc5^YJ;z1g+PU znOH5)^VOBZoc@Tz{D{Fk{ElDZFh622&-v58!eM^IU{+r7k;8oZ`_1d$f8d$Hu!e3h z-*NMWUwvZd-$sysbA(^_rYQ0@6UvIn-?aiE(>2-E$*8s049YUMYUsl|&TTcQqZH7x zgKp!sjd0pe!|92bd4B*;K(N0{=0$%#@g|tpXsH;p*CV3mK*dQ7!EJ}a{Ux!pvt0zI zRNPL&NiK>(jkkB4UE3r&g@ikEi(k9BJ07b9=cY1nka*|x2Nm<6+0`i@hbi5dOJul} z`=PRG%jF$&Jh;59JrN=ei2ZN_#squoUQROW3e!JT@)-AK%(ocE<-9ltU5ppyUGx`B z4@s1IG_=%SDr6U)Z8Yl|MWP>k>+zk%Eg|$edm@%Q?Z0AAmv`|e{m#!`=R<DfPx+mn z&GUk%zi6O;WS>_*^v_%9KN#VcP4qV&{(OY{6%+qe9=`62ubKF-^6)3(M@;-zdH5r& z@Ff%fRUW?B>aUqN`5g}@o$1{>A-_l^Sk_s9SSw3UE+_H_yc1hj+j~~Ia(;5Kx#H)% zQl`rf9CtBCEt|BT<>bENxNoBzUTx*ekvjGmaY4OTuVBz7HYI<FHHQWMvL?67ih3RQ zl|QS*hu_JQ6lP2IkALObZu*5k7uK>`!`OdA;O8c+>P+lDzL$uRbm#ucf0w=a1&4lN z(1Q<;m4oE;$LA#H=e9x^v;4bW^*Ojm+7WHM%<J#W>PuLGwj+5*jB$d07{w{r)cgAR z-bP)$hUazm!t?ugyJ&gkS}IeIT+p{&Cok`gf!}6!4dWxq@P5txs85=R({~`e3saug zaI4C4vOiel+6>mYp_X*HxpF9XI2EJ-)ZIt3hJ%diOvC_t!Uo6oCQRq+z6CPyibTXl z@v<!|01{KBi-kd1$S(I~sy5VcFgVe#XM~)~rUp(@aqzjiIMU6`TA-z~$Nb0(NuFI# zDUNRSNh~%`{}!k6W}D!GU*l%R?AqpJ{{0<8N1hq_GE?(k<iT&>|DOC!f0;f`|3vz@ z#*abeBYk`{B79u^3-s~NQ8)Q7Q@5{1_!;r4$Lof^)0{`P2F8k;8*ukFWn++1L#DIj zE>0tgD|6~OaHlQ)#!$kf!icqgH{u}Es@X`y1y5Qh-Jty;?(A*t2rQ!V7k<IN!m^zw z0!!1zKY$T|(S2R3oJ;7sbdHn~7t3SoWX7?uFLBZ^lbmqKT*^8(pNFEBd2-YfYk|qr znIpulNjCA%h}Q|t0IZwvp-F4$cIi>rJgvee1>4BV4~f^;-+FxKjd=YD$}<0D%JPE| zegkOdk3jq9&9VFm<X*4-6Ci5($3v9-Pl2d4l=3>i|9pU9VJj$HYk87Of9TG4P?FCA z5H1#`Zmz@iGN@x+boa_p!z2sP$Z4;M&#G!24-S7c_ap_n;hBrWO~tbD*zK!xhp}C^ zHY%lemy?etDOp5Sdb>fu9@thpAZvm>1v(^k7)(Y{Q~El(s4xutK|OH#4%}M`)k6k1 zJ^kAqL4;j*Gp#l|GJA>DE#mJ_|MCVf#Je*fmi|vW1NtDD^7#zNB7fn0;!WLS0gU1^ zKxr?2Gyg|nv0rwZUnH9UMD$qu$D9LwP%!x(hP5wF2fl--nP^`j>WMgHpFqlF#(!i~ zM1E0nVMaM!dsH!lV#w38l8d=sLmoi>d^P=grhXP>e@p)QVVwPEQT8?F`NwhgpS`Qa z<o)Dr{p?-6=+FGY+xpqNdejm9mBY=&^#5A9a5F&otXUEW85O`9f>eVRf$7^6TYwxc z!VrTxlm@!pe7K&szzO7Ud45(H!H^=V?Lr9KDK#WYB9cZ0)Z(}bwbaG#P<vSWB2UMN z#rf^ki#fewl3F@cV~rsA)i%rJ9rqe7>paknhy7u=cq*0pByiGj!lR+f=CC}&g>&fy z#9gfF-Az`^Z*t7h0u6$|7@wp!-O01JI}U&)lfGTi{S$$x`zh%h3o+IhE6cq$RbA)^ zzFNwoLwxtGZ{MNf$2m|?Eq8MC@2={<_BM}?lY>8wNdD{`WQ{NVaYXWG=OC}~#vex{ ze|8SCSeTzgB!6}e@<jYBBKc)J@<AV-k!K+jKZ!{GEFSsp?BNFy$uHuO`_Jb<8Y9J@ zB4I!Av%md{ZMv2*41@NO4leU7+p_%&@xH=>*LtcKRLS0~*f*l7eIlAPVN0J*e4eAG zUyS{MXucoy_$fZQtSAb#r9Tm&d^ZOV`nskYo3AAUoy-k`ToXe{?YLhV^<BjB-5fkq za4%9sVv0YVEPZtz@>R_9+nf3)M=bf>E~zJCSy6&!QLXKm+L(>*K-q#)Of`5RF=*qp zH9_JStuSSfVY*~f1f<;fwv0-MsqMn`F3U4`T6siazT4f=GMiOve@r%`(q6B-bnDE# z;BEax3=V#IyquK^Le43B2ioHep6&}#^rWZ%&cpttjZ{M1D=3tk{MNy?rh@XqvMc%% zVV?FU?mb<7I}ll>YWl0B@Yy3Le)Pd#;!*QInMcj$@CA=rul_cV`tuw{{_7lO{>Wi| ziGdt<1fe>fOYK^(N$AGuZu=5&+qPGp2yWkTWO9JzVL16_Y&)S8M2VG13ls4SGABSG zwWX0qxZVye@T?wr0L$as#iPl{=glX=s--E_ITyIPI2K%)b0jPd+(B&hqe!T*9HM>! zZ4F4^&2E`0Ae1#AGIn$;%BYguZcoIHx<jcDlXXcIC_MK+V<3EmpH1chJFdJ1r+MKp za1P?+*+vRY_#p%N{H<@_5#zszdee$}e+hA>6>+}D3;Cad7xF&?FQzxV_+ahjf26hl z{~<r6#04hsmK9arz)RAMm&>rwwSmOLg*VG7jX?xux}+mkz)jI|UnXjjpe+85YbsrB zqeGoo6)G<?iA->_)Aq<sPOj5d`TvM}lP1TtCQEFiMJrZiQk6_)lG<L&y3AU}1p$yC ziN&|TJP`8?>@bj+2ZA7`U#|;BL}p}U<eRF>dJ!4V@E9O)hjZNhx*umwVSAgZSU0Lt z4Ynvo&X@tvUh52<J{rnmNIk7dG*xQj`GR}K%fI1QN0$s($iw?qxAynFlFNNmSd6yh z#9j%7+8tj0_Z97*RatS<eXGm*x$D!<Vcvo<G{}}r2qiw}Icl~*>DHv01wDzZiM!tf z$LS{|nZ6UlfHey5nH)$@YkRi$XfOqRfO1D(4}-uKqFsrwSniEplqWheYx^i)&}qzA zqbGkgq(Js-EmwxW9%6o_J{3s61ome_1poPEKP93QRv|McjVZuv@g*X9caXKWG)YD- z{Z&xT(bH!to=JNW;k{FU`VsCuWED&WhW7WqNz_`Oi9X{uYv&@nC}|UZPSl@D%zsVh zdX4`V`_~#}9zYBrHh7KyuU(ziGl6f{z8sIA6Z8M-csvvHwYLfVTjTLO_<fBY|IP9E z4t;+vb(*|`J45wS-|~JeCvo;(aMw=@0WVH`s7h?;C?$X3plh;IZyYb$S&>k7*tqtc zvn+{20C1J)P>roLM%msoBpA@~b-U@fbQGODRg#NWYJ#<kpuFD`CVWbEx(hg&I3H+) zPO}Py&kDs*i;2m7%3L?>{sosGn`Iw)N&d(nMO;#K@bq0X$M{?7^iRO`uGmE`%eX7S zTL|)zRWL5ms%n3%j74>L;t?wJZkDl~pR4q_-r#bQm5F5%u<lO5^)h4CKBGmXitm8X z<7tCzGYi`X{02W%1eC#ded{=SGiJ9_E>YUSb(@~(sevXFALnA2#lXLhhJUCOynp$( zNI*}lOm-VF-4T#KouWqc;OVw;&lapPY9sgL%fGC^WoFgo-Un5i)yao)pZ{s;b*45- z$$MzE{}x)c*|Hbc8cNTB$7eeCAC`N3yAk#IM(cgVKYgP=5ySW|AM@>ag(6Af!2XFi z>;69`&I$p0;C+jCAL6VGcpGOe4`ur3HQVNo9FXUoUS$vfM|6MpPA~YMt!?`90M%R% zOh0AdJ+Hpy-(CL90qR;f|MzML-t+IkM*N#K1n*q=p11w$8Uh(uSKn#~pl{xW6|4&5 zI~~}?p-VjLs{Y8s`+g_shdZqW&#(9Tvv>Mz1*~f2zn_u!N5{f1_#oyF`{o3>;vsV+ zWDOe-x!&2AURdh!K>Om&?gg^GoVVv~si)5AF!M}AbcJDP6AplxzQ)a#i1F4*YAGM+ zcH@&#<oL&3xILp*fmxNoviTVoox;hm$BcR5s(S1r{ox!i0GMwFA;4und`vmH+j<uo z_L1sfH~ThMrbIc*axWziTipWb`>`;;MpX6F=Q3SGgn-RTdfZ%VwM|vT9-q&dddkoK z>d)Hv_hVrzT0J5v0e|y?|MIVHEQuRI@zi1e9RPkmJ^1qw{5c=*1^@SyT&{S&R*wDq z5Pd!F{0_x`2hzcM_yW}b3Z}o{e>Fqzb6om$F8)e}-siaVv#r%%%Fz29mtK1k|FsOg z&vEHz-r--$(EE%8Zz#7A+-DzMNxl6Sb3=R<D*%T4-^|o|&(m8|5&j@kkH$Y|>Mcyp zeETMFhd(~Bw?1L%t8YzPKWFUincV-Y8GAot^|v^ImVAyrSdN7J93^<ce;IxFiNoL1 zBMAI+<UXOL$(#N}9OXSd;)ij0o=?6fN^IW~B`y>JY?ezR2l$eH827Kn=Gl|$kH?*Q zBuaqq(%k+jrU6Fs4<bLG*X8HLi*;}QxzvkaC0@KoNx!FF_+dvZfLBVNJ0G0!Ff7f5 z!PC=buRa1q(4j@qdxt=;iRrm+v3tP}k{sS8y%wD`xR)n|96DKDTBf-)$TEAEcA}ff zH`J7#Oj2^p`*z8$TSq<}**3K|{L4RfE$bZ9IP7ljEn@AxNHPTuU~MVJjinlh6ZLr> z1{f<FoKbG0<EC^@cq1J+>mM)wq&*rth_m~5Vc(y$PXt-gi;;P6D>D&#>|l=P=q(_S zuk>O>dYYac;>5q0Blz*gKed?q=dW(?>@Nqc_PCh6+vBv-`C7L8DbX3Q8_Z9E2g|hw zyJCs}aNbv<Gr$IqKK7#fE;#c?iHaZZ{Acg*USEzcl3A>MXBKIZpRzTt*P`b&u%E!y zf6vqbg#I*ne|7EvzW&baeR+pZvNbRG2mA6T*_r`i;nG$P_I5d(sz_AQ?n-+^QHG{I zNbq6VdH|WqY+u+kUMOl5r19jL(xVWdvkM6uD!Z!%7@H73+d5a{bgYO3Ox-&803C@b zVe$c?R<+BvLi8jI_zt$GoA(&pKF;A25+AG}??vXs+4&JJ73yX~6l}}I@p`eN>6{)t z$64(!ha|o0t-oH|Z3`E>d$Kdy30b^jR-RL%VHvfw&|VFE?IL<hWWX48AHl~o%gB$> z16WVYhMD|mANx7Cixb+PIf!z^z&3QGqO-7>k_p)>ay4(-xXr=`8co;1xzxB3Db2kF zJTryNq154tE1EV-z+e;rSc&oyE}=Xg&SSqtBSjI60U(RE3AD#I*&3d-fQ1WKqS&u7 z@gL&AV9h;GV7^&1YZN$p#rkMj1!u54+To7~taPv*o@-qm{K{X}Irm@1XMUl-y!y+p zb_kCHt6HbWXV!xJTmviOzS=w+2geDC=Z@gV`d~?scuo@2bR#B4Si2LfqD)V_1MDbH zZ+wH$|6qNVJS(6)%RGR6G6EkG0>9H$2w#R*UIqUr2R8Qz`1P^&VhRy2iPwhf>)oAw z;<>~KzBl)PKMB5V<>80Vu6`=8c~)Dz2j2^x3NSr_f&WuJcRRQ?_!$95h;`@LpZ80| z3F(^pr@!iglCO7oelNqoA?&B%PRq~YP{go4%LVLF9@wHU__tY1XovZ9s<F=2>s(0> zuY!K;+{gD~c-fiY4q?Hvml0KhdMx+)ZQg9J63;%qodgBmm;?&`{uiG=|AMc7(ReJ~ z@+|RpuxdTe`<iI}ELOd1gV_3C#i}IH4t+^JwT*x5K)A9Fk^fpfJnJ>9zaF8gJ=bh( z6sxQj@8ww8Tp7ODIq{&l9epeJ*uKn0(<Qx><r;P)w=8~CHc+fJ^l^J4wd)Xy3T9kS zFZhwf%v&uV@dbv$hv(FfM=9Qot)0c%895(@;vT7t9cemL3=N;?@D4y8dSI&jdbMDE z_3%=cGo{N`JNq@h$Yf%x)A6CSoO>`TWJ>tGV0Tr-R@BCgIQP<z`bDN3H)3~iuy#qQ zH0hAtCrbdll(=`3FWG}$&FKR@9td_4gxXM{TJ08!%+N>c^UZZ%ZY^bd>7?SOIRK*= z-aBNRa=&s_%5&x8R<UPQpqvsi>R`MeL0*#8t-c;e?i_9B%-UH;$<5HZ)8tG!zx=O8 ztsxujAOCpy-#*(hzab(p+y5RNeph;<=t+2f=AS>U72;cS+vf=hy7|_pgV2hvGIK}W z{L$gp3TS%4e>nO1VMBk{9{sLxDw%Zo;WLb|=KSxT=R*lFe_s{u^7T+&17-ogdsDbu zp6v==uRYC&k0Z0DME(25W);-<X>i6Q;eXX|j(lx6$FB|Nz!o6`Fb+~HmN<lq!HGJ1 z-AL^@zAHvQ)git<+*_S@7je(_QhLzzZj+~(7jOf*0UqhW6{eLk=jIUez&zsM)FwFb z7z_#8pR?;=cj1)n$mHH{FU96KcI<tk+MOh@%r=d;ff;O3bfxWfsicipltZ+Al=qI~ z8hGsedVA|Di|_k0i+4vXPR7&49C!#RuJU#x-*x(AMDZ>Ju6ox+WY92&o8?gZ#H}87 zNR-q?H8>{cAEOHh-_DBRnxFiDd^qDC#xB9cc9dE%2gn2fx54%0^r$zkkV%-U48c*~ zEU_K|6p@q*JD0xIYt7Y#Lq*e$@txjsm)$j_H+->$To-=9i@T!3-i?O@>b_%lmn;&> z$PYwHQ?XjaLgqFb$fny|+_^s-wmarNgo|NysJ=Z`?KXQ^<5>r5oOm=jU~{M=L@8FZ zt}syr){}zMa&}KxO>E+GbnWk@;|~NZ#8W+LXzKA9Fh<p$ZMJ7M;6h5j8?v>7bR@jM zz&~VXzVl5^KEYf(r}5lOD7k~tiyO`gwM9M3B@qS8=5|q1>e$>F-My7Z)o40rb82RX zOK&RY%!OxO(6p8o@TX3l$yjpJrQ~u>p|Gu8)Z$+7W{&h@>S>qb#2Cdq6Y?3%o{Lco zr&w&mOV>(Al3Dc`V2>;}k*Ban?nIc<hnuVY0F*h~$K%3sQMSlC2-0R*D>Fji9WVeF z0lFTQnaelIJ$AmCPV5K>vH!I;Ect5qsNYm%e{AO$r0n@*(BmR#rgiKo1Q=l_&DVR@ z3KrrU(fSKfCSQE|+h-4q(9+QpaekEIUoq#eA`W<qPnh!!ahhME9rbK;P3_N2fm{K? zl#iyXnOGdH!QXhM?y0m4mfc8t5*6B<&+PU+Ckpz_qF6GvGdIQJ$IX#>+~wt^28Q4^ zlehR~kCd1!R+l+dcfPJpJB#P4t7r<8*3Z^9MTjvbbkvhJ8<G@S+0NB{#(@<KNSE2x zLZ|Memh>!H+eNmE6dAL}P#fJ0NiQ^UW#`1M&EvfU^O|$sZ$0lg2OT%C9C`-$;Hc+3 z5F9v&hJo>}%Tg2j;eNgm)*X=Q!8`f)64FjkwWW+SI&TEZun)X%1y)}|+}aKR+O(E@ z({f*D_vUddRS1k#4lA1!--r6cPMc(5F)zPB58c~32VdHvNBX{7I)`tT=mk&3dlq8U z9+f=wpc%X>DTf1WA3~4x1=YUe*I{2CNz2&`7<i5;MiN<ew7e5jyU|oY{HDeGJ2izJ zJIFUdDqODFKy`)QaPuyBKoBH)m6s<$IxhQz3Oyi{*b*px<6?i~@(wVesu16k)V6hp z0wy-5;8m!5J;Yjhy&Ps?sPY?6OtD`LyD~j3W3&$|(q)ij+|-^SLTl^ll(tysFsEtX zs7114n{4O${-u_#gMje-zNLLv7AHPN4h(cA%N<Kt3)|LPAM?7k(cC^zOfH(wLfHt4 zOeye);7q6QTl2=5JaTShn=QBeoffN;O^P^f6Rn0Gq5A{tH_8&c{M+fGOh;^^=dgAo zXO|GtnzmA$@neUim5!7m=<$VFvHQXjcs&+mYm+1i{(HwRBJ%!%CGc%mEuVP&$=&jg zJZ|EMOzEF^{EG+s3oJ}Yz&M6@;J_|bC99@+Ic|>8<*F^^*$>r*X%Yw-VZU%qwdfLB zJ42+gu-(8TdLSnWl{SC@_i1}H(>UHYx1EX>!E))-+es+fbhGP^$S&V5Sj3;ihP`a? zK=79xS5b3`Y>$j{v8?U|=TP)uD_MRv?WV;Ul{#n-d2<SfjiS=$biTo<i^2vwOZDPl z%^fKbCCuks?Ym?0Z~^L`N`UsJJY;#OK!mJpkJy=_bg8{d>0slueR)g|ba&Ksv|wV? z#=Vh@J6CK>j10mN*i?g)QcM{L=R8h}D^hjE5uYc$EU8#5l5ikoim&-RBzF&@JJQ(s zu9r%kA;*5k@39Clv+~EovFd~1sK`Aa@>)8Bt$!bwj=coboed8tR&VpmQ5xG8&2qt# zhU1pG3J;byujvJ>hyLm_?B17<s#f-fP@Htr)|J$9Q+y-lLIdpVM=6jAy0?y+Z8&A= zwCMFzo2T<R&H+}KD6P=P<x&RbHAAaSC)}4z*}zF~0tS&_&T7aV+sL|0_UXci{7FI| zF+$SqqqxNx)dR%CX;%`|mTiV4LR3F!&z4~wpw7B6(^<+Sbrg<Hl0}Drrx+`BBsW#s z(`^@ZX`xVHr$>kWo``uvB8J%rv&3FE3zRJP!U(vmQ?k8Fsv_Q%ivu^-WeC*K-ewE1 zH{hw;mKY9JR3-w4QZGy_I9#n{b7Kn$K0eqeDwhX!I@X3QA_Inh*WKlbauu%g>EBV# z-M_lH3Bhx|#B&b#F@XcO1`}vJL_Hh_dna1^)%_@Q#*={IS+-#*%|TI~vlrm%uMRwL z6u)r8S-^6&yaxDO9R~LEuY(B8FF1DoLe*M(4aygZ_|-8zL(HBdL+b$_&&bgF9HC=B z*2C`oTGhsX3<dDcuR;NG9kN*FIj>6fA|gL4F>&$Rk=UC$@ejcQFqbr7UTrxZj~P}= ztp~gEV?TrMmDsob3o$?HwZuC3?|r(lszP4~=5L2<!}mr?_#_!0KLhOBx6rF*;0wS% zX8M)$p3t~?KoXH&4x=gz7~<QvSpsu$d_*mBm&3jzDI}&eU}GFl2w5vIoLKWEtF|7` zXI(uUh#B?ABFNdP;F=oQYkFo}DlivtvxkqHdpazSwS7Es5A_(itD&I{#;(1$<HeaD z3SA!%KAVt^Y2eUL0U`{Bn8+ePxs50w<96KBhep~JLVHGzi>f?u0UO}9(Bv9!ZATky z$`<$uY@JiK!KX_zTa;y#^Kr%M>vrPzsjdjy*)M$r9WlK;*ak(zg5_I?8g4RU95v1h zeDmBbQOcUA(jgUgYuq4~U+^B?lU*(0v)ttPCPxox33&-=%`=}2tx-FnLk8W!zqq&d zR5XP(#LzZu74(+Gcy$1*M|lK221k=$;_G8?pUx=%IB<tD1B1N}PjxuM@vVMv+oX$) z?W|x%ScEk}hPir|e00_3K5@nD6oDEMotX*!@z#506lz*TcZcF+r0}uR<MfXC2?-r2 z&-NO&(yOfDuDT=~?yFRg`$)FV=;gH<GS|$M&pJ7x@R@i7!xjp(Eq9K$_hXK*`@l#X zEzvI#*+hJtsrwMh0Zz^^qzR($O@$m`a67O9Hz)*jK8oEnL<#oj;`-f5`Fnpg?{ri2 zd+*U(x3hTHv+1((g-|9XGv*82)Fr$Zfq8Gr)BYHkVR!S<lv}$;HNY^fwasNL3M656 z`bj%3rXf{V_x$p&d&5qpqu{d};Q=qP+b2j%_su_E{_C@UP1eu9kxf-S>MRQStf{{r z_%QgVFiERPdNMM}TPR?}Ox*$&f$`Qtlza+)Pr`zi@0l%J`P#bTzdsxQ($5C;aQGpV z_S|!8pV9XYkI$-r;`|3WRD|{k0L~UgduvLj_YMH61=*AdL2n&bwZ~dbDH+iyych6J z=v$MVa*gP&O$GY(tOET2nJeJxYcl0qU4t|#tGX?v&EN-pt!w!48kqF_>!<s(&-RQ( ze))92dbVfo$OmKYDO>Sw&JnNX9KD)z;2OEyx9Ao^km)%2C;MPYp&Oh<=&|+HUOG12 zc$&-83yxj4kMo0S4`Cbn<T<FS(_P8@*3ukb=ogz~+i7ZBjB9uCm0j4JjcX%6?xPU* zg+n2LXE+D;06ip1ZS9I0yaRbojUBze1hZRI)snf#8?cJmiZGlM6H>XGdI1hD_U4F> zOo)Nmyl)D`JCZzhk#DN%3E(eL4&P&<cjyJ^Sk6Ax9N3d#m$OMqV>&V(Rvb1Qn>zk& z3xG(ocl;fKZng#7HLSVp3mijEF!u1zcuienO7M$19s#Nt;tt_1jISF^Kw#Fr6mKL4 z-eT&wem*I-C)4Wb+>aZ1E1K@{4iC2u3E{1+p38#^;5haXflR;EaH<QA{ggYDL{Q}i z^2|7hn^$u(02dRJjw5GKKNINtX~=pm|L6Kq1~l2;#Gx=@7`=}U%pB!}-VERwigK4g zv3%POk4=Rj+iA3W2LL*!k~^f)J-kv61`!VC!4fg(=AH7*g+0JrfT#*dUxE2;<=DPH zV{$j-wt3nF71?N-SsIhP^Q-YV6eIh%XUB6u^MzK;<9%s1%cTwsNweoojjlDC8^fZj z^Uh>l_f)s?p*cE^<Cc^t+|koUsRVDbs@_Y`sWs0x@e~MgA>;HU*J>bGFSwvn;>ql0 z<l2RX9UR-?P-~`3?fEpFy`0-Ep2oMSf9Qzw2KJl?Io!=_lr`3-=iCPDZr!++lEhq2 zePhnEO<KMU*3)$7qY2U;u(@3nF&{C2%HFUX!pH3586)nnp&CVe?MC^37)^cYg0~LO z@Q1dy#V4@z1xb6R1iXQzZ$7i}EBZsE1^8`@yyjxhp7GXqd7+d41sHg*V92k@qRF=d zY(n@F3~}@m+^~kZwkcd8;SF$DYdu<Wh4e!#m!c=){h8i7IX~sJ-qSMIWDITfH%EDG zDF4iC=ciBC0Qh}MSSyN&dM?t}7;<A0<=Z=cu+V_@rM*8B_|CiM%F9~9`nf&sw^WL+ z8{~?Pz|Fntk1e`AS(goWIvrzXgLIS}8Yl3}1dAWR%7Piw9Kd&$70V;<?kZ&o_UuEC z_Ojiz`-Qq4D|g->2X(UR8!yZrEUB6|VYFZsFL-0$N@BPfHo7~s8aKz6;yN&1Wfkb2 zb!^YpWN##lN6t;QyCcFFI5cv5T?Eky^wxd$w1Py!a!csS$4NJ^^oH8p>JAG`(mG)R zlU|ouD(~~|k=|W>E4HKn-FZ@ReRD@7g!qErV*MiKwnknRyxeLMbJ(K@vz4V29nDQm z@<Ytf5BNg2O6(mTq}bJ+RxtDoH381^01WG)672<*rs>)P-)WX$Y`)~F17AYFORRu% zZRQ>U-%%e8dkHZ<kKWtku0qvoH>j^0<m&GwVq~K6`i7UXt#4)w|5IG^bxnTV<AW60 zP83{+h4x|5J#1CKH%=Vy_s%h;0cZ<3Vn5s$+`rxpA4_ho7<8z8r25GlW+m%5ufuLr zdxC8&U$A-Hxfk@Nga)5y95I>x_J}(D72?2K<dZ$8q0}DyoxB}Fm%tQ93%CZXX-p5g zes5srV{?_v>!=S5-uJZBI_BPk_bCF~3h*@+2B`@m%{$*zZjwISu>MLMurAwW6kBtt zEjLZY9wzeuU2J_{Cmn}(Xa?!v<q>NDfNXc<jlWEGm06c-QDP^~?$X=-u^{TrYxc}F zULdATAdehR@?u*OtQ=c`4=%{ePy6u&UZl*L#Sm8Z{#HGBk9zBEMA0$I{CE`w<-Faq z5;bBzub1v%JHaRDLjDt=i%-5pEzbN;My>fP)WUabs`XpX!0FSq|47uz{xWI_xNGE2 zJP){B-eY=`db=&1VS~HDMbkR&olOJQ`Jvk4(<JBYY1T2D2+o>GNHk0dH(J|H5qI4P z_f6um5~91?hp^w=wq^;R*f}&~w+3W~HfC;boc<h{kdozf>po~17+zZ&MTS7?t60&@ zOlq!rnxAa#D6}x*pfPzLHjfj11Rl`NtZcJcgEO(9NjZaug5^|RtX>!NV2<ayVfT@W z6WK<~ZEoSaA=9{obfR9j7K8>K(4ch3T07fN7@(2awj^~@dxasVeGaQ=*0jea+H=fw zgi|-MiG$1bke(dC>d2}5mOftaQ|--RcA>^vU3Ns?^n!YeExjXHJc;ZJS6`J)BjCB} zHN`$|QsgB44b(Dj<jvtt)6K`fgjz(fG*^_VdE18WG2#z(lX>G&kn0`h^-yjM*pijN zm%QDbM{pmmlq+^JuJ-iUZFy=4_OtFTK7wJ7g`MgfCi42$VJ2NXHNb?5hoej#gv}Pr z1x52wNXB{WLHcsmCcr+}fjD1reaV+{VD{78hp;$v-TtyysWW|u$V*$RM*%?90Ne^j z?)E5D6h(~3!O*741?$k6fr;em(Vdp^96?4N7pOvm3^zZdoc3O&8~w7a(Ia#|0Ng7M zmj!tBrM&pMKV6PAa99pU$c>DN?w7N2NFKGW2WxTfQI7L{*T6j(-Qto5wZ9-Qu-{o; zhu<oj^dJf2!!g#kY<^S7Lo<i{a587-ikKo-mhITdnDp}h7<~}*zr~mCH~9koErU9~ zd~{MjpXH;{Tj9>zd9`iEER)70Q9QeURyO!8+&u>^d}luiIbOU2+W+9}H7k^Xc=p*_ zTk)N>9%M?xN-x9gdjS23a*x$fy?t|3X<W#hEdSD3wLc-<${^Hhnamr~+15@^Jy%Qo zXXiMyTz`kpL?Qbj8Qv(oC)7>w^L?HrDqp?Pw**4%Jw-65XUUA#wERV;%JsZpS{u-S zRsBrXr(tPHgTFJ5PXXUANAk}O<O}{)hq_-S`c?87wshu@1fErKOh4S$6F_<z9AQKc zcck$+(NCS;PRVe4DF~JGH^=ydbqqzEFS2cIMDoIG?6BL(Q084-avF~?aCLhdu^>dv z#nFrN3sYb6vk_el6HQDq;5U4eMOQ4GY1RQg=gEWhYrc_OY~Lc;5thJgrtT%9HuR`( z_Bgu8n9fE433E+$ATO4XHZsFs#R?)$6~UZxuDh8`a}N81<{w$s-2m67XJ{1PZRXuA znyaJE<<$l<ZpX4aa?zdW(?KYKZD<JnuBqI(Sq!^3YWxUiw;lDMc7QL4sv}{e5ku}l z54Ok5d%r9m6Ma6C$|Ige5<qYlPI*QFXBFu-{PuQ2=^XOs{zmIKSW(8eCNZoo@vI`@ z<6Eoj?<dvf_!b|hlTptJnn-k;GjPDUYqwA(`7w(8xlBG{`!?M9U@fF&;H+Vf@x7eA zeQj0|Jy$RMmR??b-DCAfrN-uZd7Qk14$(Uef5DGJm_Un3h$431P``GT3D4kcS3|sr zJwma`GC^YY!UnABZ8toaP#cJ3Y)`k$Xjxj`p8JSodYztodCb~$RujQd#~$x^^y-&Y z5@HWmuo<3R*0$!sRY!=i-Aa5Sz#M;ar&wm=Hr45IN+<)~s^(P;N6C&Fx)GLxDGhDu zKqSgqI=TR)ee7CNdSs7lw&REMXbo7QBKB|^ujQe<Fxa&XPf<vbrj%cG(-f9uH`lDj zL)1W>BM0p6qmR>LNVi2~jp2!Hqg<7?CFC&ks0WvLv%NbMDQ`|Yl0S$U(eg{_c<e>X zW)xAbcl`y=ge!6tVevuYjz2o`J|BoE-&m!2<=n8kAcB%I`|XxuJ$p3tlT?osXFgJg zw&r4WmjBtL$lv(7zjXDb{|?#o=Tc~0lyqyP-&2gn$<pZ`DwQT+LWb`#MTXI7?X6nx zpWfv1e}HECyry1F%J;nQ`&^n|#}TPd&`g7Eu+X+dNbk@r*|bi5RAiHMXk@?T8S0;( z_2WUp_bhElqAXy`0h7dx-?Ow|uPylV+Q(6OPrds4<Fam~9|z{I0PQ5EYvrE6+1|8u ztAG?K8o$J1%-2>R;hmTW%ywjTrIz<{=SCN0<7wlPHpk5{C>a9f(_LP>gtZ%?jkk$b z67?p<?%ZH%L>z4A-d9jIO+q@jaSWXAwF^sC7T(T>DvE;B!($tzhJqIy8fv|x2XU#L zVaE-J#8s`Wc*8U=w_;I6-8?y{w9-JWt;7gqQUao0aCYZp_!VCwcs(XuLSw$)FJdVm zCsUiyIYRj+Ar0EGt8hzPrf|Qd)Z45T?p@wSTw_dtXy`ME@E2BIRoY5hXKQ$Fy-4OQ zS8%TxmYwQ~%drbEf12J8v=g^I;@a_;ssjY4!4?&}<M|kz@(37SI>aL7sp!dq&K}31 zYCjxF3QyO89_b(Jn}}Di!#Usty~Pl@QNKvcI661NYnY(0DdMxBF#W5w`^=F2Q>*v; z+NEU)zrVXZU|(pkAc0lez1H>M^xFD7!B1;V&8vH5(g;}JlMLSoYOUUR28nQZH9n+; z-mQ`GFOCVvx4vQkZJ7qQLRKTDEy9Gs!XquJk-WwC=H%R%wHL<^hl=b};DDG@2f5Gk z?PB2>LyFePzNOk%)CK-lc)?$jE_{rD00j{ENTqk)&aH<;HISGCw=^-d-!b<A*=wgo z-|3LXj{dek01qE4<;|>Ic~}q8<~|T)CiX_$X90Jw*g8V@P?d}}8Y?sO2o(1PA59D2 z>TaNT^r19aZP#Ag^p^SY2r&3Y(%Xr5I`**7Bl%d|&v&sWu(L8FCj{RI&S~?4Be-s> z?NPGh^I_Zd93e+%MEA}zS;G#Yt`S<*E*=Z_IGPn#2PCnV!ehQ?vg<HbCkz-pH(6AZ zV-Y$_s6ez)9U)nt<*f=lf$l^O*y9xTx=SDSDoxrLlwpOwISJ0)T=tRFKreW1$vC}V zuu7AyOXNKcO^{-FXOZCo4Qwc>cCOh_aXw~lrd{^t#=q}nnZ_~qSeL}^^ztu(CT9<! zU<ecLF2otTvsn4^Pb+CCByGGC2DQpb{X`fV@YB8>M9J$io)`)@-YrlPeuAN@LALqM z3k<s^;^5!v^J%rnu<7fN>hvWqND|(>?=5QGTavDg^t;%=*gtoZ3CYZ}2KycTfyX47 zt0SsV5-KgPskidmRb>?m0oVS_O9CYMgV+2clz;gF=Q~zJJcFE<6UC<SHNRX!>3HSQ z;%2yFW#lRI;A(ooT2-apnZ@js>cHBY(G)ni?f#%jgI`m`bz~n>gK+9W%Zm~`U=WCK za9VbE>EvKso4}NL5nIkIpv8mk;12rP+g#LiwAxde=kzVo(@YjF-PBZO>=q3vaH(7t z{Zu(yW%N<;><R^XHl>Fu@I4p@mY8_Y9qf$F12=SUi{6Z*M^bS~-b{?Mc4j^vrx4m} zlSXvg+h*4ajgfSm8lD*QoUeFzQ*V)+o!#}M?NN9{e2JAXputDE=@det&_pLslR2BE z2`Vpe0@0+0*h7-QUpHVJ%vNO`r7DxY-%Zrbzq@DK(9E%)hr<P~l->89i=@0-B)RlM zh0DtF-+~ceHh4}h<9po;fl^=(n<TMYP3(rz5~Fx+<vk_<<$lm*TGKZ^Rfu}uD*t=n z!l6Yluie{-)!;@-DBKAsF!<xoRWk*JpPA&jxyI+7(QiazeM@EOUMqNkgIBG=xYbn+ zE9;q_5<B{{Ir&;29<L7K>M}wl%~SM4%JzUWk_T`<Q88b;G)TsN0w(B>%9Y?R{aP48 z<68<1!L!W#QPJ}K&R{Nnaqs7y^Iz9NCm#Klk_*`NAaTiPoC&s}jY7-8XMSeaUn0O; z1<6ks@FR=9ap=7vZ^@bWs*er1U+|Dbhi5fU^>K`~htJ+u9j!8YQ{g(lt|E;fwUKXd z^;zM%;7^wxAj%ucB-+Z&(oUlIRYL=nsA5?|pPw1m6CLKr_{BBl{TEz7qSzvAF*@vz z8=e0#jfW-S^fEa<w>MZLt~ndGjh*sPwuP}eWh-X_oa95~s>Q@A%O>IbkYqdr%)vn> z_v<R6i{Ej|$gZhYmBP@B4vBC=?xG~rSoUDtOA1^I!E{NTcr-_Pk7F!yDrDm9oa?C1 zk5}BSBp0u3A_W|s)suq8+&SkmewhZd;D?IeCL)!~)YV@yGOg5Eqc<@FL$h~}@Qvlo zI3ua2hfCOq2Uc|uc;V4M4Q9L@9;_)i@NmL!Ju~GgejK@Tl=Z?R-+NaI<F#^AHHS9f zVBV|G7_O`-k5!nkNy!rCz`?!1SPw%!;KJ?ddb>nr4PXGDjr09N>Ku>m6#g{zsg3Ej zGUJwloWfOE_ZHrn(13B^)pz`yC08=6Oz<Tn*<>tzCeV$wl_{4!jSprk(ZbHJ53oUP z14Fwe9`^Dt#95}q81OkF-b5J7tko_1KP^-KjlxZwY`XS0gC)PaW;|<tTJ*NHe2<iq zghbl5HZcosziU%brJRPGmH-dB^VjCQZ!!x|yo&9t&EU-+ldf!AS9lUibegd=OCF*c zPb?^=p01m_M%AF;ouV~e+)EjZ<9w*;(A+k}t?Gj>&lA>Wy2bKiRNuBgx|)fJm$vmm z1&pId)byMQI7m|(AXf6JuR(1uD<r)ox2z?KpK5Mw%gpeer**=n84AGY_*5q2wAWl+ zbj`<ot*^0mQqDYMZ{M!1zgOb_H0En{z~3Kr?!B08jo4WMYbCb5O1<e|*kx;tEXG_s zMAd!bP5k=qI<o9%IYTgcD+JAdbrRnVkS{xi{N!lvHmFCR`7SJ7I`2*oJ*n$Go~UBh zAHX=ES~y$-B;nDL@Fkq<=3$>~6jp-_eKX&?Ks|^xwcl08qh0WJouScjRxf;5FmV<E zFr%QYGomvzw3_d2&lRn8odsplFz~H1g$;Wnr1oAzEK>qpq-JZRaJMAM*7gT^D9^xs zqzym^umTtH!>~Hh$1c@`J7}IBp{=@=(Irf3+`iv)DPv+1+DoH5iXx7w`&pff+Y4U0 zgLk^c3I*$@MnmxXSWx2?i+1)QP`mjt(3f*?L#H|2Oy@4W0vAj?(%uNUyf{Bf2hhUR z9d^5H<At}m4feDt&P~S_c6r<lhod14i@S#<RX^isq_o(`Qh;<%M%KN#nJ;*=XFIUY znl~znskidPDaX}UcTRdWJWOEv0@LKj2%Ekt)%EPM?6$xgU=!D~m7+gibRBry3y!@D zJ;?bQ(l{s58GoeN^i7&!m27yyzt~N~x{5^W`|ep+(3>2??X{}KdP+=wYI62T=mh^k zN>d|#<-R?I#%tM+@T|EvL*%UjRP9y~cd(yZn%F0Ex9OO96yzGyHU$@Tt#=?ZX_MKt z(2Um}7@o_e-*WTDvdUnWH)k7996dVwcke3aY@P&~+D`PryXOyy=_IAWXU`HI=FP>0 znVf;)nPVpsRftI$Scu=9lUu@FcWAO<7&4KJ062POAdx=2ar=bhcgtb}CegII>bEv! z*&J|To%<cFL;q%!nAGnCIiImEcPHB4#}5?W(nYM~eY~L%Vc~4PQi#zruFx0@9gP}1 zF!~UQl#f1h02H7`H5Ma16lRd!p%~njhn%sCQKi)2dTHq$)?UcH3{SxoLsW#2RcX6> z`Pb@Nv-Z{$Y_r@yvI(7ofIn|F{^kE#$;C2goyod;{~KKeS^1G)9{*ANB7T4-M545J zXzHJf%C=427TCZQz+!``kS)uw*4`?2?MKXhPeEJ{F#Zcq+fP}WCkZtx3GHt`k&qw) z5?Z`caIQl3OA_kx|7;RkH`+T1fj0#t1ly}@qh6JBw9~*c)sU(69Ta-gi~#Shf6p9T zkCjM*d8!>z--v3r<^jkHzn4uSc){PO>g}q8$YvV+mFfH5?c(>S>i39h9fv=rsxOG@ z+iCzC{%@{__vP@{4yf*V#k8t`-5~nt72dA9@4$NSIOn7|E^4GL*L1aHNYky7ZCTbU zw57#zDe5fCPM539PsAx!dR{JSW8cDoP)xjoz2K=$!<Rr;0)aSk+^r~XiF;}j`_!t^ z9B6YH-X(J(vFePA*~Wgb2Z7zx*{P{Eons%sNQ$TJ8L5z)xOv>0P`=%|=;1Gpc)8G* zz{@kV2<7JN-iBmQ;=Daj?Qq-5P3<i3!p-8^!-eHoQ<d9$kUZF9Xh{ZsANcb*ziCPv zUj%JCI?nABM{%S(@$Ha~$gmlJk3W#b<Xof|9PWKf>G$VWY^eAsm$M38pq-xG()=6< z(xvh24Q9pVyiH|$P9Z2aw~`n>u!<iUegt#zy_ADp{Z-_z(it!aUXP4~q>8agfT{>5 z#GWDetGvg$M1NdO!h5GP%Oq5ZMiS#d2x$>N>Ffo6C$wME*_SI#f9mY5J8~`9cvIi2 zz&?CiXK$nzzmp!5<JPbGS%6hNYQHG1TvOth-m{U4t+CS$>IzAn%y71f>E8AGoQclk z5tw(!im}5{+bm0cne<*L6|>{%8A4lB6Km1Wug<MG%4ekXB2T#BJN}$?)(y+DVM~q` zc#Elz5`uP%#lwk5*L30iaD!2CgPyk?MtU)mwcC&)ZFxQ|^ljSiqN`4OlU|9U)}S2l zZn|r5tsBMUQkZ<u!4M$fSnamA{V0*BCg@|(*|HDM8m<6Gi;gFqXsq^lZ5o$x@oYV2 zQ+(4ra!rp(R^2Mqh?sfDq0<u1*Mla7i#oI&#Sz_Zv}642k4J%P@BHk=S1yE-;sv#g zOA33jeX1X7ML|yeK;eGKwR{kl>9%j2!*x6@p1YMK20AsAQELBq`L91x92aykc-G5D zmUI83Snl)z*RF5tcx5@~i?zmku294XGy8AJ^mM#t9l`Uji0rnltn>DlL{|Q%dsEz- zy!=XJO!*a&8EeRgi_@sGo+<c>S&Z_s>MJMfp~Gv7rVrN?lV2M&5%16bDrx$6o?b&< z`$4Wn1>f^D>}O5ZbJ&3iXqz@kD88%jfc4r*2K^dbc^<8#p9SXr#{0j&{d$~d{gmea zJ4yZ%c9Qn$(P*xx6PlDv{Q~|Dxk3Zxoi>;2c{9qHZTJr}^O@nA@NUVEU7yCp+2PFT z7WLMNW}}00fjasGR|nJ*z&ITAdkMDN^jH9#RgqpE<QqoZwqbFz^+>Zt$v+7latfKa zzs3D#Lre_v$TIbg@3wnjR7h?lRS9#rn__+*X-;8}RBP|^fp)?ROcAq5s>sIbF1;LX z78&PmlwA>*Q?|cSSu6tpZ}`Iu*bB+4iYM;wAv3sz6yR1&ilv$hSx+|7LKo1TE%o7A zu#1!Relefa+msI4jRzEP&#&qQJrVRAlO@^}arl@vP?HC}obJm7Vh;sG?44{f)40&6 zE27*{2^MO}F|hM}2G3xn;kQN!VKFA5_bF$LW8Z82KUey5+FRw}Ye4PH%<k-kXCb9Z z8{%ZlVagU3p}WjTwD#8gMZ+ZOKCXgy4dt+__}@3t!!;cEvk7EiwoUK*r$JTtZB&En zRtolzz5@_Zu4DteN(!Ie>E@i{%@1_*u^Mo+K8jSbKgi@!#P5_N=u@HYYLl(}%dkB| zjaxg)VTS8tfgaCT1WbbMHoVNRlaUWbvRTLU*vFM_=iY6k`vJZ^Mn&r}oXEK_$3s<A zjj)#s<ixf~liiCld%;QixSgfMx;KgAULpb|&NgaU(0$*xL$rZX4}&cq#gsfbFmz%} z!=qNFn@o8EPHd7@L0urzEV}!ayghR`d*U?Bzf1&DF2kb`sPlyHuS|u)$3@*k2o`c} znD04#7jutuQ|$pp?cC5&Op-jEACWcix9NZhZhhne#UYa@y$M@j;gOx~Fr>|G@gv7v zj#phnz3dUpzG1vz=bMyK>P8T1_{z^c2TxXfb8Z()gKn08AU3gfbZgwWPWW6Chhu$B zs~i50f4uzPKT?qr=9j4dNJRF36cOz|5K$}<-$L5f7eu6bRa<&H?UjU{y13BcS0vQ$ zMfu7uza$~^Kb?fW<xl_+X1*dJ6A^B{BSUY3|ENT*>2&lSAkF-mU`&6zCR6w#m(7^? zKS}8Ty7({~{5l&p_1c`_O}|&M5au3(Y4M!C<Z1bvD)YNH)tHR^)GT{Gq0NB#gtavp z4~h|N4i@^^jpI#?(EF(43C3YY;b=VvdaF5G%XdFy15os&e*qg<7F2e=8WMm?KcqPS zZi2IPxFzN$ZxCjG%W2&9Expbn^#$jq<*Kv~GN35jsOb1)oRm$6qS(R8kFc$mjVU7= z1qEAdLhvT>Pge*DH`W1)Xf+X)7yNt^;&jvmEGJ`@zq$8Y6K!eEXv|RDEQtPiT>Xf0 z52$j{M)Z+am~_JQvmYPX9eE*|7aUFJ{R!3cjBRh*q(|oP2=NcSegyZxt*=C?U1Q3b zx5p&pVx%SNTcpC<;ZYKo-IjMYFL-+%XpZ7y_!#P_N)5XyQ19j0gb$pR1vDpi+Z5LY zl1sZ35iAHnbo8ul4r0oY=p_wb@S@0%I&h7EE7nVmOq>pGsks#9`$WGPT76P0tjz8{ zK`v$(T%!|*4GH3A!R^7NwYAR0t4=l1EhN!>t{#iLUufxl@8+}W2WuAcfQy(gB8Hl~ zonN(seQ!+fewyQCRi0h%a3xmqF`wj<Nc->Vu)wQCfY(C)ysAX_=Uw!^hu%D$_H|!8 z1AIB2{6Ib*&oak^C9{=$G$P5P^JmZJc>?;~_ra5^)zLeh%foGSK#|sQs&i}mJPA7m z74w11j92+8(hIM{ZF_j+z@w8*7lKH4na@FyLN|;(G7Bm0tnqY1<goYCI+~W?f-~p6 z3CLzL)Up!{tG7b$9$M~t(j#SOyM#8!LRQr3Ugtf2F!q+{Vrn1UVtB@wAe~QWtjj^v z;bh`(HYa>T1LH9Z+1h%+_tuW3{1Ol^WR%UcuR~00I)=VgoyzPwH5|9rhSY6$i$hFN zR%%JGvm;Lm#bgimjK1I}Sm{I2>YDU?W37{bl*hU_1qy@bn-fLi?3rX!YsX*Xi->R0 z2YkK6tK^e-#&gu4^_PFM(v7v7E>?6>mN8-`!ocy12$@!jH0m9~xl{6wm;X)%)up>; zT^0OBra={6y8Ca<^;trE>7rLB=nJh_0NdY7Z_o6E6532q-YV`}0jR0qlxLpu(1M;3 z#HvvyQ_`$Z(ImbGfZw7&)#r2VGz}dAm_KssBl`z1!)pnk6#p$<x}*XYc*K=GuZoKQ z>}Ri~wEo%(z<5oMd$QE9=3yP2KdL+atP=}d0U8+B5^24=2p7#-yNxmHS5$xyd(R-u zwU3x#d5LBwv%F}FlqWmMkVgKk7I{r${@iO(Zou$l;4Vwx#1FB;W`6+=0Exd-udjW3 z`&GVRm5k89?h74zUL_=+nOxMj926A%Oma>yG9|xfpnSNdujcu`bK2Gnm+uqzcjoN{ z|JBTWHF3|_+^2bZMTA!q6nc%$;jhuTe(`NIuw#~@5~L}u;=1q?Ebc{^@{dPmMKSKG z_W+$AwaaYJEH9|daEuz>$V+Qtm_r}8$stcpNhpe^aPSL5+8Q`B#zDcHZ_qWms)9{H zYj4=9#nn!d(hC}~yPEXw#$Imru&dD$KkA$u+(M5&B!0dVh2$1#XAW}xY#RlS5@(a! z3x4$E19jl7KGw4Po*Y$t3M{AS9U@bYeswto$E>`Bh<?Z&3g3(llZPYX?dtZS+mr<O z9%{8HC~{%Go=fCrN&Gg-sdk)g8=Y!iV=TYvw-;YI=X$Wax44W%l1(ypj5TL4h3*U9 zHB?km{u!f=iVRk^wK48y-Y`e`@rc@SpJ6zv?2EHnUx9@R_RIl{x}~jM4}iP#)=%W! z1>z;#TU{V97vtLO{f%3!b!|=!xk*Itdu#SLW?9v|-`4<A)?tPYQ56jzR?qyDll~Yz zd+SuUM@{%{?b;U{Inb>GUqsXO*!aN#fNFk`R!;w*6Tfzq189Ihdz#g+Q9+sp>+Jwz zK%Bpl=u`gfxpvPweEiE5O-A24y8?&tKyd8$UHwdTr-}TFHyj=OEzO>xM{^;b$Eo*M z5gL!>#H3k|?>7=PesIlrg89(n&D*U?_}{;6jXS=f$m%w7(ifwcLZLJMGO`F}6L^f} zi4W2{o?V6Qu~4M|(v;~qsVHxs=vwZC)`RL$2)H6di2`TlViR4i_7=$^AB;l_;er}W zf^NBy*g#_-^1@>nDDgBONbIzYcRhPG8$;j^PQHu`S#6DyOyW>maIr~%!w1K?%IPY1 zqH`A>oq9RNT%F%fvXtQ_rdf~eC|m0FaNkU%d#K|u%Aq*Ij-$(0cZ~*?v`a47r8q!% zWHI)!uGMqHAYR@QkWVZRj7S31+wB$LP<3uRZEKvG<8Xg0q@;*VH(7h!P}->!?$y!4 zrYS8CgMnuT(`}3$MD?548}8n0*4ibIktSdsi~2ybYh2+6yoCXIK3@K{XPtoespQs^ z-aut=w9Mj09{=(3|9;?Cwrs+n{6@1`Qopk+^gokb@sWGac${MHO$;bmx#*uieE5}q z@V7wZ^ki4$kJuIanqB`SH-a6uf9FP5N2bB6m^*msW|ctu{^>Uvr5_bNOmC7YdjAY3 zF42$Ia$?n@_~Oa_3hM(q^9$bpD{uC#JJS#E_SKt#-I{zAD13Ph6>85=A^#RC%%ZcX z)zJCjOji!!b}GZGC-YRL{iF<8f4QiMb}pJev4<O2`%<>y&45Wr)!32NAu$Y0fP3{? z`*BdCV1p^+)`_d#!Ukf+@C~Vn*U8v`-FkE5<M#f7YXVe8w+E~)XK!-^J{5PSQPbfz zwtHx%lM>GRX6IB1V)5EEWUwxYnzq}9gJNVV+XAoQg15U{oZndUyc4%*q)x-*?&tUU z1|I^;&C3L{u}9d)m$p1mf>S_&a3h_wuZ54`h^<-_7&Y~WZ7<S@@i>u<dx-0+X3mV3 zj-4Pn;J*R4_fntS#m&?S6Aw{07mZFO+0i#AeZdzf)!NI}n2E;Rbj0@V@lgy6WfvR8 z(%TN&?pWzsQ37V6);xb}^^dLbHCy<Dsz)*cR0sSB=7qw)^~_Kx9FZs^Svx+b8Tvyi z#rqn0mftNOQT+!dsh?}%wO_`M=^Ru|1=qW<cET~EP;n2CcH3A~d%d*`^>rrduc{MP z%lEBE%(GtxOK`}KYx4J4?)=_`#vn+rCp`N4BjTV>UeegF_5=~W1}|~o$v!I~7@Q^N zH&4-v9wc~5iKa*OQ)cXk-YF{!J~0m1&Yw_jl{JfgF`}>iO@6jIzXv{5svnDM0sOm* zzQ64HZIy04dsk|Xtx5y!5x#!o<oz|0@0-iQld@<@;2F;B`FeJte7m)fGvvYsL_;R# zCCZs_ylgWnGF!~%rVyd3$?^UmM3ucbLM(V5mbzd)bhGB$2~Xa;MrD+a&8m<{0XbyH zgE6#oK}fNn3di-JSAe&NVT<H&t6#Js00Vk<0K(W~S?m4aFn57HU5wUJ9ToC<8-h=p zDIo-<LcM#>>B0z=GbMs`&B<4jUkt$Lro02}dWp2L-aM2C<G}DfjV7ll)KD}DP1dL6 zIxK`f#}xdC?tAadPv?famZZ@$I(@nKNW6XdH{5F&r&Tsh#BDc9r1%G&Gh9E8eLTR7 zKs~L2&;KT0da;R$-@ts7*)_Ywzx|I_VFL~S|K4bl=Y&tF%>&fd-$L_iyp7jtyYO71 zjCLP&s~VFl&@!e0$|Co~V41rZ?%FhFh4xbGt8!oJBSm5$J|yOJAQ8!X$W6CdW{t<U zG3TdYU3P)P6d`h-)^y@;DI1FQ{qCkyf)yvPBHB<)66+V;d?!XP71r`(`l_rj6aPPR zZ`R|uvaN}|+UeU5?e2yRbRYY`Z-#7(5=jlfJwS63NpYTsH_oKQQJh5ndIzP<tjw&e zy?32^ANrvHB}=3tf)Q(dYx<U|XxHSshyOqRuX=P^XyHS6@7ikXZ^avccbf+tDYR5U z_{F;}j(*{LoxXTpwUcoNx>$e4*dJ|4sq@CAPvg=}=L-N?f?~q*vn&NpG=1DTwwif% z4J~#mIy+Wavs71v1ahY@nDd4}C{qP9GiMLzILAU!Z=0tseY^<<J1W?F2e3K+@cd2f z7sus}JY*E`QjR;XFw4lU2T#MQSBi+lFsPb6J)i!dUHtI;!>{_^_HJ>+mZ~W*gQZ(? z6~5MVEPYk58T<n%L4gJR^sMQ7lv*3_*Zyb+z6K_LV-J4af<XTN8#dvx2Vby%>n;Ru z^X>V+u?;`w)VyGST8+42^R`Ql8&)+5FYpIzLQk^BVO_Uf(-n9^#0`14#(RVvFYd9= z-E)D$3bpcAhpxDc+2I7A%^hwk_Igq_sFUQv(nTdYWZn)tDG`vId*Z-Is&?V9>9!Bx z))Y%1=SxF&I=;}OTjrwhapk5>e~S^jt>hk?Wep+movvCEKol^a`o(JFOQ9M&H=?|h z_d+73rd86*X&WAAemfrmVLfj!bRSY~a<~2T&Vtp8y<KmGOQ*zJBN;1x4{IKJuob9p zXhWT^>q22UOhxf%;O;gh5v$d0*N+<I0ZpQxYe*Se`T~KsS?H@xEYw}g?c5@+SVs!n zao~<dC=1ZYwc}EnAnb1NSqr_LfEx47N|!1X!8|TiDlgc$pp@GWl=2m$;J?5JNpjvS zH5SX;{LuvtTrEx&OE_2etZwoXINl<w;isrDm)<L%PC83dk2nUvB#*O(^KPm1Ur#qM z=m(z;Qy<(Nf<5U0lpPf(V1eAhUOhpMwRA=CGrV=Jd;&W;_vtzfiqj1Ce*(Uh2H<b- z|2H7BcA<5Lcwi8*^ygmzC(F9$9;^cVfz}@Upl59z@I=QJ60bOx02e^Z=cQw!3q}u~ zw()6m2A^|p8k0wl8*m4BZmA^TX@$QHfuuPB$%m$3b_KWwxeEm=Z6v@}5WzVamRcNe z4Fbm6ZjqDsa*1RC$(aV=q=UJq3+e}wZIJ_D7GvVr6~J`hJJv$3aGaMr!I*ab_EDJw z?otc0T~765M%(j3zp!+Utb^ycz&e57E$e*wF8I4~>~jiEdIwHFv>?pK2*G<;>U)m2 z5SWyHscwAhcmu1JyGv&_ur|wjJXZL%gWsd)&s)^#)7j^@DnPNqz|!K<l@9C!H+A7_ z;%&{z1LJ%=1<dMAZal{DdiSHEIp?iMm%_x^=3tZvuzEt=xT0tM8UQo*5gmI}2!WMe zJcY}Cc<YSwm6n(OO3VAGFPcN^8LI$$bXdChfH7$J4~=s=foI>ByOyqn<kE}Mv0X5x zJ9t0$<tY&R$Wp}TH@j}>&Id+MKcp={kj4O3^Ab#l#E03$DZ$!nn@1+t^X{KC&tn~w z&(%0s-Z-hJwWnrpDtkWE*`3f^6B}Hs?^2*&-iWqTFDHU6oZ-d1;+0<5uQ{LGU*%+8 z%UMF2XVrEHq!$J}u<u+SV~Q@*T|Fp^)+@zP?9-by!r1lhDEctvC{JtLMMRwn%`65r z>bM?aAI%xP3Y6U;M0^r&?_NyRD_{kmu#q3SwNN*r91DrHPMfLnXTca-%fW~^MvzIz zHw+(|$bO3ZChbxpIfcH&lL4Uuj7l(No%B!<o2nAdVg&`+HUR27&;u1Z6uWfYZ4Z!G z$l9gk)mgYURuS(G@gd1AIdxvJ1MC)=sa@;Ko-$6;E>kliG?Em$9`qfJuW;D+Dc)4k zra?XKCLZgp5~_S7;Wti|GWg|hg%>p5*(`Fe@AgQ^JKU9~TF+2d*XreOZ`f3<I`1~U z0n;D2lpel81<Gsxpd~8LXpGsr+tU>N30~#@Dp>d{P=Wu56+Yz{f55B1!dG9B!N0se zY<&8F=y<`M)(;wdOcB2N8mWZ_Kk>dsAyQ!T3o7>X+CC!ZAsbp~31k&2s82fYXY4;E zLJLSsAc;frvwsHY6>yqfbqfp5@9@wUMZOE^V;^i!CD0`gog=8#NMmpQnUewJ$Yznu z51HOaR#!WgI(YNvtn&{~{}p-yRwAe2@6Y@Av>%xT>?AJDcyt94H*c8*?2^{@xCYM8 zruue;{_6TH47cB}&|h7j<z0NgLVtCAmZk>Zuh1{o=l41SGMYf{JK;zMQKMMIR`LO2 zAe)Fcdq__y(#wDqG3PLlxfXs0M;qPrMnL$!gl-z0=HVa~ubYC}%TYI1bL`HmMz~%E z@i@kTTWa=^GJw4e$7+D^$L_F4b77^J(rxV*XWWzoy{mKcLhNw_m-UO^QtQibS0j9- zm6c4ayJBpD;_!l53At{(%n9XT%ae#HDcdc6vMRDp66Ge{&)#NL6vxomFU^4F8zPIl zc9+d-peLOj4`i8Cr~9s%PU}6E>#%x3?*daRjf}@J26W7-o+2#o_6A=8$hmEAb}8;p zt4Ni%im;_xFbk3BePKAW47y5*O5GtX*X8q8+5`-0Hlnf%JZM#C*}k?5E|CQILr1`J ze&mJJ?Kcq>j9y|u-$#IyPadTg>=WY!3lJmr$=@X6;F_U&1a{rfni0CHaX;xAm8f>T zAR1`#@P61eK<htB-QH!E`@ne%YFtRQ{KX#pf+T@WsNwVvRA$!Bf>LRKn(Q;s$Y$#; zy|ry|@Rn|U!0H8mga7Y<EUq^`vz&_sB>`OVz*<i%2M}U$Y$NN`EhrA4m+!{)=mF?j zUGc=7K;zHB)+~AJDZZB{f!|F5Qp#QW`ijN!5}?)8f1sEj*!1y)KgOB>TY<law^{j1 z)i!(vs8Ot!zri{@<|!lqot1CMaap}6`+=rk*9G8G>h*ie815CA<*@Wtd}8^}Nd5<d z4nRkPU%X@S^pDZfa>pxbe8YSRc*`Yt%K*inD=;&FCQrMMvsuuR_z6EP+$KLT))RYz zS)FwgmyKM-^<4Rc9v?gEfgk@mdIZ-PfIFYbZ;K5;AD(x9c<0In*THDF%Q#*;LN5E_ z6Ka0L%!>{1hI@}dD~sSakJGaorO`dRl|SyjJi3>Eo^8Kiz!&Tj3dG)dOxQE!!@yD= zR&7!NO)sH9(|D76GvKukAJ=oBL&b<I(hGJk!aci7gy0C^_`&D=OMsf8&0J1SLS_j^ zc5GAg_}OS31Yhq~L{bQldy%KEG6e4=zhKa%Jdk&)RSt64pfY3XC==s}hl%qX9WPV7 zIk&8=@b)&>j?x}!vpm_Y#IvAL;^t-m+^IxooUrDMu4_w!1LD{aevw7Y?qt+Kz$qnU zB6LrgE(t4SmnC7n>hB@e?>a{|`}@*cantyxQ-YsiL^p)+#`SI`^T(jVDj;Ie-7ey= zaaGpGVbfCXUO}~_<TlErq#6-MF8A{P9*C|8ElH?`mXa$$DrSGAC8b~mNjMM*bJIuL zt+LJ9_t=;J^9=*yeVdgLfPb$j5dVP!-Tz^{lM8{s8`wK=AJCp@*DctIA9XbGD;@oy zSkdOC<+AqfT>QCKTX{Gai*KEa|CDP`XGh_OYjFDD8a$ahAP}HWFg5$$jq1NvYy*X= zW0P<(meAiQT+VMb+u$1Ze(jH5-tW8RKCV9Z$u0Lg%MZpBznOgx5{3`;0kpWRd8Tl9 zq7e68qPHxEUB>EwIKrAyZ?Z|R_j_P-Mz}ZzF>>c#u*ALewWxDh8lh~tB8k!ziUVG6 zX$7rTTdi7ee4A0ck%~E~$hXDqmZ^Ar&^g&$-!l6J%eWPbA=kK;SxRmbhFJM%I~{jU zs+`qS=Umz16W+`kF}-a{TIYRfO_-{)SNvPU3;{Uydph-$&v0V_8-LY3wItpyL?6En zVEq}`04w<o-1r@^v4CCSJIB>u1{>fB-@uLE0UJP>eFHas2W&iC=0CxW-vJv7Ech92 z{0`Vy99loajo$$q57*#NaN~Et#={-;6WsV6u<?R@2RD8PY=F1<4cz!0u<=Za{|YyL z2W-523paiS8#In#x__%i`?gM`D|wP;9XXFQ1Rz=~C-YKFfWQ_@oq>)d6Z0B4b>5CC z_{O2Ob$QEjxfF;w0-eWx;uHjiebM&QinqNNtc3CYdap)Fg~si)zr;MJwjBj?af5Es zFi@fOh0!m!@@VdJFR9AZ<iM*}xRJ0ciPK*Gi<otn8-NAgEYt?;kq70(zWg6=%A2~^ zw-36T_@TS~|6z2uNMh5Q_V`y0`tfwtPRAue9Kcn=y^@u)l<*z}`w|0tb}iN)`uhiW zyGQ;)f6xDZ`db-=<cI$5K7{s6Bw)^93B}z94M_fx6XgYaccbVOB#_BnFyGxMlR&;- z8xyzv#)<OI7yc*iq(5?&EOP44-AP}ZC170ao3rHh$ys%Z7<>W}W{$ECIA;14jv?@J z{ssREep%dW&Xq_U3frxw(WhySovgtj$jjDUAFkuz+s<~Z{N;p86)&xAJBdh^O!J1$ zU8XbEO{mCou@hm}f-7<`;aM7I(y$BvW?IQE#7K%eaDA#0FWA;(3rgb|C?kcW(Yz+! zA&KpU-YwR|(pa^Qb3PVm7hiX9)K7ij=v$l=*t5>xLT<EPFt8J@%2^2G9rViTE&&8^ z7?Xta|H!@Y=8kwvvw3q!e4pZpFSw$>KK&o5Xats1U<^re7S(IcSO(Z21_ZRKxqIk^ zPwRhOf*&9D?&AbJf-{RL`6E2@HLUX~Kw~`uG(QEemXq~I;j45B+_sBOefAP`2cOAP zAhV%`Q+yfMLvMW=*+V~C=FJ7azvyUi%5rzwJc9JkP%zNx8oqTcwdNnm-7F3C&w>xE z`_fNzK{xeNKZ7R}M=+~pMyKAf+Qj`Zt2b@A%;Ifc0C^P0DFWgNoRbfl{t;5jAG6WG z>OJwpTTrT9+#3t>0Qlr<@M-!Mg+0h{U#V~_Af4JpcjAo$-rm<x)vvYS=Rg%eLT{le z@S9JZ@?$m%{TWMq(47}=37%z0vS7-MqHoTIVxR^Kq}f7$;eit#TF>r}4+;wAfewJI zc<%<etor4ru8+&rpF&oj0#=l$7+|bG;?!WhvIUtf>!2i$bZbU<Px$<z(_e#B_z+}x z1`yDX(A$INcu6t-I$!?sjlNhwxq>M-du@{>hY?*yAMC<;!8#8%W?$Ie&n`QfvsiT3 z6JM`ZhBQFu6bQ^vEl%q!8nyU%h~R4k^@-10w;~V6y5E8^)DqMmWB4${s3?w!Fi4V> zO#DVuyR|m;kSWkue9k2%B!qoOw5ZGPLepQl9Hw9P8S?VCQ@u%XE5as`bZR9a#B)Mj zbb;C|X-5CqR=uKr&&0*v^*`}VUF8o#f1qHV)rRThSep?5>`Q2$mukLe)t#n{>?^M) z&<UMm6sImA@Q)4O^d+>s^y&;2O6Nkj^RLvqi=IE9Qf+xKONC&+o>Q>99^{Gt?Nh_g zC-=+nc;Fi&fUpGk@9+9uG0tkc$1lysy^9++9zti8+<wUjkbFaJ^Guz2rJ%MS=LiXw z`7qDce@g-BYu)H|Nf%__bSO9$VB|KI7(bS0`8<UH<1W^W1~9RpK~K8FCEa*ZorPTc zu~r`vkKd>*UuiXftXjx604`si_?qUcEEI^~UjR@FKupjx(RWGJS;&ixS)3_!;SKmJ zGkgTKe>um$GQ%a5^y@kPl^HG!Z9nGt8_WRC8Q&dGz6S6&e29cvi?$Nh)rY++HN@Dn z>LyUdLu%W|C6v)9zNuWd^Ezg?Pk_YFip}5xxqGu60_axG-O}{22uL^W!W644NzGg4 z$Q4WxBZ6>`E9}-eGtj=Rnsy{#RnFGobQ5U!ZforofVJ*mPoiP0Z%^Jq($T9ol#JdV zcETF+CJ2h9GwZ06bl9G#nO-GYQy47;d+6>8MJ0>8U=76x@>x;iXkhDVg41^3gn4jf zJ>pkzwMw(O42}V3?(Wy(Ufzz_fg;$Z#qNRY%1hbPS>r^Jn5JWqgj50RS&A6jR+H<V zy`-VJBmGT-NF_KY(OyZrvdQdDb%b;+Fy~k_vlmPrZqr?Z^>wJ`@5}%$Kq>CH(6^u8 z*~;z;0kCF-V>pRX)H`dy;-^>;3jK$?0q=o-!Ii5EL_3IbE(J70F%9Uoi#cs}zT*p| z^Tr!62n5(!JFKpVh(22%5?_n4kC<g|;`5u#dBGy>Nn>MCApk#5s!<C2IC=wrCj3y} z7KtoKY3Xi6nUW&l^fumW>5LKu?pI+g=35>;pM-LQoUeL~^&@okIrJ8+xfF3{`_9Ce z`hpF9$Rc-gONNIv)E3(&x2_sr5!)k>+^OkHC}hP(Y>TTp)@?@<Z*9EU(Kt>yNj2R& zupe9mk1vOMP6!=d!{=)$oij^|&}q0bXR3v6r4$F~Ah@ND-u4<(lOv(3s7**WJ><cD zVAZ2o*hPp0hqAmAKEozr%hp##rxCK4lryLKHbcw(ty?kp;CmG&v;JIfcOI<B8aUnA zuDGMp%A-5%=(%Oz5ZMLMr}@CMYAGBKisXkVBp$0S*>zqa9xfV?zlwq={*iDL<>hbE zYES91-k#w*F_!8fsjBnjE9BW9|MBvl=<4sJ7lzY&FR!K8pZ?NTVJSEN!AQbU_w<1? z)a@Zr>C@E(vPgx`Lug~|;xBO~WRa1>a{s?BqK>1&fu&Snq&-@<KJ7=6{WF34)H9EA z@IL#qf{)8LYBXIKR}OD7)Oj$jd>TqP`LH?i#6vIk3*{~U#=0`43_xYnqv!oKdTUHJ zVbhlm+z+C!C;8X$A-!L_hXE_hnIwH7t6x4ni~EK5tFPBa=No36A0%a!PM=<rh90AN z9Sai_$4x+t519|F`@2BAm+Ds^-^!bfw-j4^)5rUxT7NVTz<yPfzm!Km%JP$ESw1is zHmlmEsUo#jgKa|@y}Rwh^-3Sy`wXypHk&Y=*k!ceyWM$b6Tk|TO~-M@bl48O0FlQv zmMfWwlTKSP#>f~)4PG<hL0Cmx>tTC<W2a-o?U_@??5X~~1NQV7rl<3E)rgx5oKC*I zCE#tNMlb`jTOD9~d22b>u5J8$o2{!$LLAXG>OdwE4ZPGXiS1Ow>U@|<x6MJ5J&eVb zY-@97E@A4aP<I!H#-Y1G1EkOTPHV*e=HMwegfJ|tx@7`D4c>4^uw5?32uZG*&RcUQ ztQIQwH0-8HI!3_=_u+o)9m~QQc#3g%tVxF3#1dR88mkr9QCS-ISBfz<7n!mVBou69 zy*KMCyP`s=vFY7nc}kZg0Q%O4K1*zo2Ya#=IdwygYT%3MG;fzQrONB{3xJd2vzS4k z62}1Wn!}bmzJC7&d%L5uqN}Tae|rAt>?|SbKR-Qxbao!T$Dd9Q*j?CnEf@YkOOR*& zu;)3sW)4*Ar8qK4;SQVeR1K~;;vC1Ukd?|^IT1!#XpNp7TaXXX)mpe~Bxx@&K*ut! zQq?50jat>%to>Xt6S8V&$?1ASwWZAf+2=EFn@PfTES%RDKilfT9_U+LsAhJR1R2+a z8FcDT?KMH2BAWHV3*>NBXijoEDS5L+=QQ$s)`d8qWu->G5TslI<iZq_wMPQ+jvdo% z2lvU;n=5vwr3{PiCbCIZTXJ^8p5W)3o_X5<=pSh93hT_hBL$@{QJ&k{yf+L9n=IcR zRZoo)EIp?=kfsXHD!R0%Za7ZXjTI4{Wjb{w&PYbwbyh%$K7RR|8aZM)WkKs-8!L}G zaa;|Iz4iz90Bjen^KNk#<>6;Fk$j^jjFEj;6ZfUj*R*6e`m~amw0ZGpbO2zq^el7! zv2oau6yE$=Q~v3uHwpHq^6SkkAr}YC5dE3DsJ_|o`C?SOcRUG}MrupZqifx->`Bd? zZQA?W``~VKU`o9U-=%BGoA9lHQStGvzpm@vjEjXu;=-c9yqJNS*glPmrQzg<YnSgw z|2Fm)?4KR|+dYrmkKY>o*E+#3eZxQ33Bp2qS?z4Pz*(y!@zWJPpyK|JkTe^tD{t7# zoEN`f2SG}=o$mNqr(fmvT9Z5KaNbw;YR=D)zTr4$9Ub=xH<0XYBMfC*oiJC3E?Xy% zyb!3Y6*zRksN`<n!SO0|@7KmXtE#b^eC7Zj+>MTpm)&Yqv)lR>we>!X;wI6U>Zqfc zX8<5;A3O5uuv?&44y~V|$*>Ki3!+TqFyMt*M0M-ZB)kqx>L3tkJX|(#us?@*F_Y}Z z0}zlyjN+P3C3cgq^0*xcrXYIKIqvFN7k%mA1-H$7hO4!ui6;b`{T$_Nw>&dd1eNLy zsLhRq=%OY@Fxtg(w6>10PwXzceS|@QQiL~Lydayd9<ueBsxbQYtP^BT0?QCT^$lO! z`&gYjC{72}H@!L7+)09L3KpFS{F=P-`Li?!R0)&3e*Xpg_Kw}Dt?6p?$e$t1S;sE# zaf*|;I4+lJR?BQ%0ylu6Q2E8N^|#^DtnCMAd=Fm(%(yiE`*9EX2wnaq0QM^tvh2xU zFkNC+C?i=21y|4X=+=wn*$NvUa{^D_M0I6f&s;Yw*+hFbnuRd(x{OSBw7rL}8@UW< z#Z0pSL7g6}OL;ZTuy!h#LvE}}UXYDDwq@xdM0a2o(XG$!YXdqVdzUguXibcbuA1w5 zdk*=`)-kM2=yX%9)Cf89TOL?GE^I(nc8|!3KLZ(c2i95bH3tK6JJ^h_(~u;J(p3jp z--kSE*>;T{<7=2kLYMpdP2es~l%TjsFb-@)aJo(H>|li4AonxLnH@ujTS2kv9B(c; z>(zL7HqzYBkC3k2sH4{n;+)BPDGoZi2{_7m*;tBvW^bBYie|)hQQmb5=f@eW;3v9B zk0@DiYZ8rHggC?}RQbote^^vVM^we%`7Zqb7Qff{MTVVql9JzB_TmDq+2Z7Yw=XEj zNMdh+6!WxX$A=_k3HUQ7ln3HYe2A`6C4tqM=!|;(8uMCeNIq^7fMfE2K<=FWBxG28 zVJziNylWYWu@Qwoh51i|=)-=x;wPYT-K7HLiqDG@`<IYjIP@MRs?VU<=|T`Vv-$(= z^bCr%U(OUzt9P(}U)u^VY-RX0pM&C*`y}<&UU7*Qp425kYb*CB@9XmflFzdQEo{!m zj-~F9`!%FyEJghv)TxG3u8sAAb{)^}4kesk=p)UXJ&C$b{Fy=V1dv~<xU`2s)p-_* z1<zNuI0~M808hhdBap&foPmin2axnMn;zE$@_7w_Hefg<KK3Q(mJ1(xO|#VKo&y-X z4{N-W7a#&&D-ChOtRJ!j5>#hAy)j3Rk7lS-NG&f67ee}{a2u&;KXl#__69~T4dCyE zs$bbTmVjHLmggLpYq|mbu|x6kgjZAVbG0B<oUXo=_X_iTYY!f*N2T&JAop$N@~42@ z-cb{bkfMpIRZfc|siDx$DsF_&4x+qCVG`C`VFh;7=D<_qjkyA_MGZ}5kbNvjImt=| z7mhorwd`Kn=Z42n(W*iNwB6eSJB{p4%!XiE0W)B7<Z7>A^2As<KdS-_ly*jl=dA0u zd+Xwjdo$yVDcAOq(_vSG;z(p&oVRRh@U`f$K&H;dkAU1|1W18Je&xLSLXKH{)o)6W z1(G%M@Ji?m0Gg<KS7+fxORtxu<B{^F-xw2UM=g2%KG1L9-Qm%A*JyY<e}5i7p2ipK zpE-{oPvZ;r&z#4Pr}0t6|J&#B<7ot^`@?yB=+W1De@an+yaj{!<=%X9<^W?xWgnc= ziTYU<eh`JfPN5=}q_@wZQzbiPpXd3<vs`)?eeTf&CaOA@4}3sCt}Fdl=>x8(y!FNv z^yrQBIhl*V@z$b0xIUM0@a30vm=2@#=nnYdP8KIdi!1)@&SSkaHI4{$^cZaY2rrJZ zPA#sqUj)MNHqh*}yl5&Lt$m*d^5f*mnV8~IE^ojrx<hF~-8PlOs+RXRdCXTFpW3^0 zjW<2N&g<j3h`cGcC4l!Yj9o!Yko!o2tm1HY05S@K45^lOqgX24LD)Jh+``)Nwldrc z#axs_CU0G7LzJV_&iBJvJEfdyY>jNcZ}_?3{g?k35Yjyn4<1Wi$~gJQKVJU+js%G4 z)@5-P05DaRf5-YRt!`O2eZxuvMEjo!Ypor*H!76r{2Ko<5?+1FZ~wA$78$*8K!Ckp zemPYiAO7i{|NJYflbIfs@bnM8uKy)gng#P%i<44$vpT;6GB<&^z5$u+H2{<Y0P`9` zUur}@YZ`!H7M6)3@7?*ccKZ1AkM}GY=D)H@AFaJ%LBB%cMR>JHzg+u{*XA*|-<cV( zoxhpe@67BWzkZtA_x38A{gT&)cm}%ICC~-r2Z?85dmiXJ9Wz;++x<pB$tfs!Yt$X^ z95tHz96nqHu1ehN3swu0Bn}CJX7cD4+q*1orV25FDinY<DcqoGHLUw)RLs2WT7{04 zCJI^Gii`;p;Ty1gCB@XNIEHA*C2Ns8r6ErSvAA(A+Ky-IHB+toy{;=%5LvUy9{Y=I z!3G==bH{1uns~v^*?QNhP7qv7c)Hv}w7sS0s`bRu4Y06-PK3SM`&N=+>o5|kju|6c zz^E=L%bnUe`ofNyp>@-fnhNKKejTD1EzwvbI~&1DEv_ch>u%=FqHfQ(5R0$e>a>+_ zYPXeL3b94-Hko+<m;%?$%xn#%gqvpI2G~FP?l6~Ozm_fUK%TtjhNf(;*F3xn9Yee# zp_lu+s~~`PF|6F(Ds6Qe_c_Z7V6P5m?tlx?DC~}$xRaV7TDkpk3JVgi+07Ouka-=k zI=V-c4Y2rDTd8U2(KC59INLX{1O<O;ygkn#ww;)wMI#!cFg=xG;{9@7)AIUoly9P6 zGnIHPRzNFX(>gnFmabM>TU_l?X_U=SqwF<X_hnbF7?N3)6}%M_5h`8TT(6XFC8<yc z4`V?9o6((b;ks#pgQ)Q_%w{vNFY~>R+>j=rxQn~GU+C^;2F7JNcj>hO`jI0?X{HKt zin0~H@Xm?-dUD2mZSk;^+g$@4+uh-I_ronV5cwwSy);#tVDK*jat{fk)!}_Fgt(|_ zBTGKo6+P0PjfFw^`f5+9kp6+s^)v6QhkSB7zhEZ{;rI9Es+{`#v@IAxs34D$6~)_6 z0{uoXu7<*?9eECGDk7uLA9UUmoqVJ9X15eCj<$J(5+58xM4xaTR;HQxQ{$(<a5u~1 zw(`n(2R@ri*uq@S^GW!`eYX+8ys;(hp|^k3_41RpUecf6fBUVqd>_ziA&|yr2E8o* zG5SRs5$FR4Et2*f2@T$}UvjskMlGqhe2HJ(3xPw}$MLXZ5(wk@?z~Gm<pV!w4s;t< zeUPs8E2ez!w3$Qq8lX04>2#^nIe3Co<n1HXfWRl$I=!U`Eg{?|rXp&=wZcVf3ZAuD zQn()1gQ<F?5G}J<+82Dpv43nYvnPD;>HM-Jxi9HM4;w6drgp!k5B(83gWv8(N0)vk z;eWzdKQJVjd`Z4jZs1*f)ambN@&)?~c=CHvdVq~j#pD7TjCKT3ww2UGk3>Iv6onwn zX&={_anev~8lz-?(BV+Uavz5Zjtja1yG8e62=W5E9$v5ufT^(VF&xzIJyAg2%QiW; z@g%uaXm~vl!FzMgZ&xG{?SL+9Le3+Lb2u0o>Edbt{BXJ?oSny8rww;lViyCx+Z^gW z)My2{;#mXPB2^0M<Q2a~5_dh^C{c7NTY|N$w&jJ<F&cH`w90O6Ec7<5_Q6c;s9Sbu z@60uJR=D9-`-@XTx+@mZJd*oR@)-9vyC%7*L>yqv5@@x9)DQ}KK|FUlM}bOSuI_|v zl`Yk*;j@Y$nTx}5E8e_YNluW;-<V9vRx!il=)zlT=&X2R%UP+z60z|N%s!GilR60# z>=2Y&+HPkh)oM%iL#0)cK)6)p<xCM?Z;I0c`LN5!6zD_}p3ZJ;Ny<i%n&UQ4$ii@d zx}RJZ$R<HMsz)~3LqS~haOsi|d+Ga?JT}0Z9U8)D$l(Tc=5;mTU5sVfZiMvA$#9Nl zc*f+qm3gF&5kk^nb9DTfM;z@!2<K7lf##K9@my_dJ4N+;Q(jI5Fe(uUPQnRJT{oXR zATiakEXeU>o#i$jMigz#r0)aejM+P2LG=ZM;g{XCyH<)6UKDO^t@n(z&PV=4s4WYh z3>c}Hy@;r!laU;{ad|hot$cyHUMDSm`M*V}J+09yyVc!MUY~M9X$paE<|CT~2?ig6 z>Q*Hi<R35pi|%EkSN@$=mv|2Or4~F_ZD4zh{A+$0Nra3KyGs&e_vi@r<Ouv0YyHdD zF#ai6G|%QoCo~2O+v<X0OkApVFxHF2=y>_IgZBC-Y-yM?H44Nl%Rw@h>h`!@`pz%u zl_Bxu^!@W-<EzJMS|U)QPR+tan1x5JyzuoJz!LJ;Q2i;m3nbRRTJ^#i3Wu;*N<}u0 zj$jYk$`A5o(H*}Cyl1~+!)Kpt_{qYC|7bgSuJT@*g{riwXdO$|rr^Nh(^Qfg`|~`Z zkiX4j@v!skSf_$*jkh?%93m5G#PMLOFBlui-kChI+`5nXhBnY?46a9dO^-BVprur= z4UV;4c)cs&dnp+@<iiwog;!1LYUT!<kwgitLv=&(j)Vi^430pE_d18PF>xQ!aUIAT z9d_tuON+>{OLf1YTdz{CL9WsFjsqHSN=GSj7^?$^oWQ7FzaFHGN)L^<&WqbRnAxy9 z_xjn~@%wxz1w=pU7y2&5b*B?p2av#N-IbHTb#1W4x^o<aKxdz#yxHvZG@@cD?d-h} z%R9kGpeDQQ18NMlAR74drgIx_3#6{4t~w2GO1;Os>WE=<$Y+j+%(T5anR1N_jAh?9 z)Ml)PGg{NJM~R`gG1AlyG<~?gV0nwk#g1$%xDJQTXhMU}qvI~`7NE;b?Ufsm?r2t` z=I&rQ*dNxLQ*osC3Y_--`UGT!NjJ^`+jjXX*X+JDy^@c-A@Chshw4b%23FKCt6QcA zCqj|Y66i#Phk|wBPCcqI9N1CXro(p`6%%l8X!hMfiD(_2?2I0UEEBsn@{aNjR%O@W zQwJ8kDcX0V<?s1kZIp^x_=GuI&j{rpA4H}<<I+*Yq&^buur5%H8}SY2*w{`I(3z-f zdvB_Fud};e8&}6sPsJC^u2vLd`QqIotnPI7aHo)RyKzRBI^>!(k%NkMLVg&HDt$|7 z^OhLqmtJbRFiUHJvxuv-r?u`f?}UPrZ-ik=^V5IJsKSp<Y~+;&n!Lfsg|~4@HxdX4 zSZmlKCr)7LNi3}v>A~QMS#pVhEyRi#I*e>8uX8QgLdPi@Nq#Tr4OmITGJe5V4CEC> zVA@a>Q-DT-xRFyJZ!FpBTSS;6PTht0(jr#xbFK5sgecXlDK1>Z;X+X%Ej#T^FJ-U8 zQLP~4rfkns+~EfSzN9O2z8o&mS+<i2*nQ1BXlvNAr=5Or$IBWGFsm~XJdR3~7!E#a z)%5@;X(MnYyDR7hcC8(T2**?oxxxdpa4B$>(~P-dTCC(8tRIGz3Z1rMv|eM3vsHD@ zzhAwR9kJII-(R$|=a^7rn06&A{t2w)S(fp2THglbavVM1yKeC)F&dXW-&r>;HW-r| z<UVP6s6RC3#&?*NknU_C?Sy9NlKl&o8ko6GcUxCqpR{AW>g;O6c|sr6X~H%#6l<Zx zM-3E`b4~7)nY?4_fD8DhHza7k+AK*re5z_PHe+e;_|dsR<jWb~7q$|Y%!IAxZOo#( zarCv2_h^*o@{u4&-Cr^O)djl-pb>JF3GJD?JCq}+xYbnpz$>?Pj~`*^w#qFI&rmVs zueK#-4&lTF%o`9I-PYceBbE)ca9Er8?RwpJF)iUJ5I^ibk50}_=HpZ}<3c-#ra2Un z*!D8a*BWc{{dCnYcLXv05@4#fRmMUSB|AQ|mC6b*&MAb88&^d*s4Lr?EIa9-?zpF7 zV1cx&o4Z#+04{g?jd}~n=w(sKq&Msb80*W`)sG#q59&NuLQSj#RGL=tNI+0r1OcL8 zJyKp+eSM=0k=jh8&|~?_|6Dsx8_SCJ^1q)JBIx>7#ZjA8rT3|mYWS6s*!a*M0AJbz zOov3bm*$qI-nH38xF|rqp3|TZm%7j1(NBaxVKo2hjX@VJyQqJc2DSyQ=AWzsOMGbJ zrJsjyoyMRz7E8g2zeOKjx+l-EGyQk*^2nD%1kD-G*w;{7cySE;Nsw&glfQMB+SR2M zV*ALSe#-|jmwb?U^(US=0x!Rmk*lTvWc;(E$TM<88^XX+n~e|Tun+V+OR|ti;DodE z5wPyyr>QTzh^NhBCjqQssi{i<%kHy3@$$q1Pg?DOIS=kq!~(88{9aH_vlIn1m2h!p zk;JwVNfiR2eD&bl;7OSK?OHyPZ~=08`tc}$R#-^5up>Y%<FnnJ@cG*1SL^WC)56IT z;HJT!fC?xtQm)NijxU^JM2+D0RW(A|6vBF36=;f@oqPz6YbEPY^Ri)3tG`8^O;USL z@76m(^poS&An5_qLgxj$4QN|K)o_|%JvbUV!yesQS6aqYC^gtrJx5~L&X!Z-%;Z|? z&#M*YON5dm6)xj2IFacc8_(DDxLMD2>7MRui1r$T*of!|Z0193^imS5VrTLJ2lsiS zCsBLD^B88(ER?+^vY-&V)FY3Kp7ztG*{8S3Z~;C{rZHv@FfyG__xT+0`yC>~#FeBE z7`lT=pmGsuO9#Np$-LqqM*7fQG^k$4!8~gP36gQ9E<TL<I>ws?U3CSntV4NT^YX5a zit7#OsN0b1fe$d?m$Y#1nYE!lnU9_LW3Q%gLVr3UOTq2yA<;4IW0zB6$pr$s6<ZjO ziDySt3Y<29;4pGt_Q-P&Eqn5H$5fQO&>08|9=|9HwE?(}d#?;+A^D8oJ$kC*m#;m8 zgt1TDj%l{-K5}Y-;!fP=#2gTh4}RU1lnUo{1(g6j$>e6kM8O%(ZF3)(W<N9AQwC!S z4+*QSZm?MtQ{H{h4~D;{XBEo$%Q>Jot9n&!7HhAEpg~>nh@|-Ea*2p10I~8Ta?dzk zn3)lbk&5Gdi&BF<N*=jCm4bZSuk~#YyYLR^nY5xNc9g<N4&%|NliKC#*3%M45Rr5* zWQXZ?vvy^Ndo4`vnG8bBt+R2V%PF1cInIH`=IE8KOnQM$_OS}Pc3zhgLKJ)F!q^); z!}`n(abH1i3VP6DexgVVK^&#g1nt`6z^YIzsRY}Q9|cRB?mMU2G`qVl$kjQ(VSAlK zM~60rRf=38WCwLh7qrDuxOo9pH&t{-U;bm$)1|QXk6OCkZY}H|FaODk^1S`~$-5=h zM@8{nt&;ItJh}Mzd2?&FJ|h6xewy6v;m*IdUteOH>?T|a+-4y0Qa&h_T+%;Fu{eyp z@)nkH?S*7Am~a{h+|r+Z7<vEg6L-!x;TaNLjI8#(X#gcf8jw!kD}t|eJ7A*$5qKh> zM`a1$5+5eA%tO##58)&BBz_&|p9f|a==v7OD84M=!;A!K<md3!H=&xP0%-{tIuI~l zpDhS3;9%<1a%A7G{-QSN&t~|7{m~r%$_(Gz4tzVuKbzsAg1{{Pni4V_QrMrDG}*(} zk9&hs4+w^o8~6o7XNKUjDA%nF75qk2HidnMr6!&?fpSJpsK;bkm<v6*@>j|PO*HHB zl+|BVbPfy#h%W6e9|M8SZv?*yl^!-$TFxC|X;8;q)XFf0918P1THdxFR>#b0EyB|z zEN3$n6$L~BAUnZ$y%~<#VMlDH0~^p6yuog0NFA7Fr=v(EuFkLs$+rtCQT(CyQePva zdAAbswjzK!%{zrFg)>sLZbfql>|Dagu&v6XWz2|BEMj)m?cOAak0Yf?2+=B<*DP{r z#V*GfVrgiuB-n{YjkIodvwVdq*-zj~=}J#KiARY+?zS?jkrJoZ)Jr*F<3bwN=Q3*| z_M(E8CNA{F(ee0K$r}1w>lw-zl^MtJq<am^cl&e<z!QiWdnBNKcATr98(Eakz34vo z%Cjxp!%e(2(SCKo|5{y$Yzey~7f$`29ay3>YVI1BD(~;F{k^VUu>H8ZVp7?ot95@I z$~@$BVtYC}cShgOMZb}z9HmdUp;$q&H~YOt82O2Jttus}eG{%<Fo;}TAVCbzJ;Ka_ zjGqabVY)*@<>H{Ie!2H{#;Uqct5jqT<b8E3AyrD{b|2T^e5k-=D(%hsJZyHW$?A3& zq>iEaf}Mh?<>h*B3ij6NrxS*ZJIOYwgL}Ol6l@cw&RL!}m(>ga+8y5J+wo9Bfj=qi zrC`=>+Q=#~@mCny6)XPaB4f$<mI-YX_EzbI;C1~q@p2}c8%=pR!eE&S^FAuj7VetB zb*Y`!9V}I5j_j#0_oo5o*Ye<QLRV%jW3QD*rdqfEc==zoF>BkPD&923-#5HKE1!Ld zg;;&ykL0K^)x&K*2ir%f<9qHhaTkKG5&M7r7_xt_Wt}Nsz^`voQ#0UjVLw>_@4GZn zH0=KHJ#H8Ae|V41D>l}jY-}hkgNe<<xQ@|nC4Q?8@as>$$BRe);O783kNxn|AM##X zoq3I7FBxy&#j)SZQ}6VQxo=hj*fj7`z`bnqHxKK}g92N4WsDbfVDc|bt1sAAxZn@I z_}6RG+qJLj4-V_U;SK!oUj8$$U?E0Y7&pHqk|40;!MI607&j9NgZN@>u-WbBL%PBT zQ$Qu`R2WI<v&R>#*y5eqW`bZH>~Y<A{>krT!BWkGEH<cnz7mzI-FFw?jKhQ0WgKIk z?B-@!>xw^zSqgCHa1HLnzR0z@oJFkmFVjgR(^T^*tUNlqz&wh6fX2O7UNigH7{iS# z)_l;!MOYXCIPQ9u#~tU1whchEN|D%lUv7{6Wqk+B>0%M49yTp=z_utp+)H#1Md&VS zqJ}($Lrue{rRzXH)?wKR=>*wD#GB%MPFJ>mg71aFoWl)XJ7ZYNTlPx$yYSqRZU}LD zgiq5p^aCKYuAyfS*=1AU`^K7luuTX%I5z!N^KYKOppt4^F4S_qvF7JJc~weUFyOM> z_l(bY3l}!yj9xgK!6`*<#{B?g$5XqO&m|7uHn#}2il$FnV@PK*jMw&B)V&?Tpm%bm z)sAV1U>3vK7Xxg6m?E+0uRBpf82)6b``*<nTNky|rg|OOWq5j0r%2{23%L`tB8(k- zIO;x_#pZ}f5g{bk;Ckf>qqZg<W`iARH~I`;2i;?jbZAZ_VIj45P{)gGg_{W*){w7p z^94XN9yS8m-g|X)h#c0e6|fstw7g=j0-Ac>)`2Lcg@tGk<=s?5mb&7~O-Nj_q+;{p zYykyw(fzH$p#o!HaFH)B2X3ScjJt{E9V>tx;P1nGCOU#~CV5*&@Aa<8l6Aeltw{7L z+CZ7}w?VyV&F0SHlJrh4f~O16n!jPWcbvD_dWz!?P-0K$6(*^z;KqpIA3y(^nk0A@ zWUA2RWw~WMRhWTI#sC*90@Le&7sp#=6&v@c3DEjW85t(z{1F|1irm@68~|{)JJp>P z8y8VNe#iN}<i%h>>rleb)`=pL%6j4NzBMw(=HncLJvkl4n8Gc=zTIl<n9TNV9$KNi zB*Ll|A#D#MQHVJQd1fYt=~Y3ap@WFG`BAU)JqskQnWEM>TgMDDWb2N3uEa3Og+zsB z8yUo0MwqP#{>dxYdZX8P;H64~oX$*AkOk8<08q4X3+(yZy*zSef7vVa(J>iMTBV)s zlL!3l@@;S6IS*wz0;>pPb{JBf&5>n1VRM9{7YweC_d6jE$z2SpNilUT$7K3OHDLzV zS1l)FJt^%VQW&gQi#J#7RTSlo^>FU;;nu24Twv|rvjmlri==Kk9pFEAoo{P6Itm-X z%Wn!_T(Cf6iUSE*tE5^B^`P>M!Es}ZWI)mucI34IkxLpH#B`bQJlTlb=8BlN^k|ZK zvRdW+)UYTr`)g|x<@chB3wPteOf&2m2JGq`30)e!6;CJsLO5i=o#2f^Y37J+wv!x@ zRaF)7_D<19emy6hLm_P`b!F#m&iiW%-sX*4?@1T;+yWnM?<Pi~a^1HnUY&u=ukTGc zOz2s#=96<?4b9HvR{ebxZx_tMc)KWCU>l#@bSNo%y@a0Edpd>edVS#%I)U{1teSym zSlRx@Wj-<8*OR<GX`Abo3+MLeu4c%2!RGA7i>gJ~@zG%O?rsb{JjG~Ij5zMfM8oyd zR=LVjW_kF;_OWCt<T(^9(b-YZX}x>-F9Lqs6vYgYxr|ErpfU>;$A2ZWBwLaX|Bkh7 ze~ZGuv*Zsemo|Q`YEzA+`2JOI-YL%t>xVvnR<PY^9kbsvcbSLR<+^<5zrQAaJfQ)J zWp7HJh91!1p<IEbGzYLbEy)?0@gr=gycw_EgR1b|gQ<{GoS<s#sKZW1E%HFGv8Ai> zcjzVm3B4>9yD8xBRc^vV*Bg(lizkJIdL2)1Wt?|>p{Rg^{=pJ#J##a@@Qwh3v-`Lr z_(DP#6y}wsa%w>^3vHp200i@vPk3KSMHlbUyB~6<K3}`w`NwesjrmWE{NuPEIWxaK z^0#sSj_FBsWNJ$&C1EyCGlzYctZYiFy*-ez=UQW%L6?pihH|GgA>|AM$J)&Gb1L)J zontjF>!z4UCh`O~@|Bx76wY;<q$zH2WFQ$xb{n>H1nW~(^~Yv4F_Td+aXFDTv*PaC z7;#*Y)vV1LweVy!P_bC)KI*}1qa~Bo3pT0)c_C$YOB|eamdNccA~!9NVgjCuu4Aq9 zBFJF3+pOtK8=>2J?51svCJ?(*#K1*>ly-uo3f8%=+*r*X?*%EgY+o=CdG;z`StKdb zNoo`pdOcN<zpKU4Jk6)gtul{ng8&4D86JJiQgq@O4?P9;ok0TfMnKobwc7o5%2(T_ zUK8-$OV+vSE9)I??Zz~wnNa><dd_(Iou4tiJo`$$c{Sf7h97#@EgDlj?RMO7Jo9{V zp*q9q%X*^2!xm=A`B{L!lu^?pW+mTz_XYd#?eBAhz;LQ$cYs%XcaX#>0Qw{6*Kkny zj?k+Q{(P=aSHrXk%gDS*@hl()$6v*KiwD&8?x3P@OrZQ^or8_1HkG=nt$SJAs4guN zM+Z_m9<PU7@{n^jCWF46IbiMD>qCar5yh<Xe2>Hld^nYwFNV84d*=GK*xT7E)(^MC zJ}6!=(b~kBLT@#5Y0qK#%?#yZFm2-lfA<leUKMJ!L#Qs7I-*-Q0@H1>gO4iiK5U0r z2Bs&oZ3Qt2tauI&joEe4Kxt}0keex>hU*~Zd!R_Bx}w4rhK}Rx-VQpcs{H{z#LgXy zUjDJFApdM_+RM&@9fiI_jZjo7elI;Z8`mXSm^93d0Xt{=!r-}bmDDGG$B&04pZtG& zm*%hBN<Sny_3tLh4<9A@(zy3clAk_F@^=oUH{T@wkl?q61b?qdRtR!P%o-=u8=Z#M z?QFRWqkV6GroWQEpL(Z$oX0!a&zm9v*5jMyczIM4yvo^!<v0ua=WE}s+R~@tpIf)L zWm_CIzqfF|*QWuw3@K&vQN!tawTT*lrDOt*l*!}reacID-Y)JYjTA6fD)kEi8|#p~ z(^t$y;q0REVLG}P#!~0GMf_yHj%U5Qt37YIZE$K~r#w~1#Zi;)%sAS?8Yi&Z&Ksg+ zMDfC0YdI7Tc#Jgreb0#@6vH)An8nGXQLt^#`~dB-1z^8m2u!TQJF$f0w`sU+bKDV{ zd_#-2THR0v$-?G%uI3eWWgT`f?7gx}`BmCnYa!Mjgaeyn8|J%2n<KUC#tbcB>4Z0u z*AA=M7YrAV8EYyc;kKUk!%Zh3-e~qAE_9W!k=8(hC!w{!n8~_Rhf343ED)<ZF2Zn| z`jd{;627~1+GP`U_UdvKq~ZM5r(r9MU`I$V*mpJ!Iew&4e$OlZ)Jq#XqKatx043{u zR~%e@V~{36v+mfoZQIzfZQI=Oj&1DNw%@UB+qP}r{q8yUoQNA;*%eX!tFxcXr?M(L zi;KJP1{8Ys2bm?t-RpQO<ImT#`_5O8wSGS0;8p_Igj1!xNb;hRjTwJ8`8!2@E#0rD z=?rTJpnMq3l`xJ50+*~c-PfFL2&M8=aA4Ca2Lq5;NedHWSPhgKj&$||Vl3-&eFrnt zq5ebgAh-E;wv7kkMlw@UkyPRBv5wUnOF=i7oj^LYo<TaKNcO5F+FSZ(b8xQVn!H$( zncm2hiOp8?98mv%;pm+slHOw9*C_@0yzP}Xx`C6gG$L~}x_gIx%z%*vkm%40=6LXZ zNT_0AR!o5_GIz6b$j-T%V3umvc0Kv_g;msIDF<%s$~J<NGX=BfJcA_H3KJRuk3n0v zZHimHm$4Vw^T+~#c$Ol?0bJawg~;T_6=V%+a4gWkL3iR&<UgzYD;<+&^<+N%mbGvN zJu1hLcf8*n5eCPFuSe!pzKm;KzDXq1uUTZ{!hV*ZR?6YrGU>~ZWG`TgaTxPcUTl(m z8D5SIH2T~xzbum$uFenlTB_+Vw?}V=?!2CmrQ}ZH9*p}=&fyOjHzyG}aud^ikX&cj z2CqcWdW3g1y7N9#Z3-2XKS9KL2JKyB{TLaF1Q?{Yd0CHOJZ_ISRkqiS`MRHE?fvej zx-bt6-HAWIYEoxzZePb}-vY}^9tPC^03g3;BH}$Mm}!<0c_nYb7gK;AHSwGHD}=IV zUzyJpFR1;Urrd9IX*0RYQ_%g>ug)}AH1>T@3eTL#vN9%q%k-SxYC#goWAIAutJ;_3 z@nH+saDdAtJhS%pyi~^Mx0f(p2D!)`sk%9NHlbX)@mk=pymS993C6sXFKWd`>{Qd6 zYBi;q6O0sc;zvk@VV1|IE9+V%2&_EkIEo1cER+|t`xM`BplJZ5KLplku(@YJX~-6Y zPoBY*a$69@5KQIwrd?MG`}6TV)ew-<8Y;PD@^0nJ`7jMII2DHw(q#XJO@(VB?ajxP zFFQ2F2IL@~(Dw>@hvThB+KgU`qwz4?ZZWyLbk0XpZT-p#Xxi0Nyu!a}fpR#-F2G;c zz)bQ09{<dA;}LWopyxfub>qs>j@h7X+mD08u%70w)~piL*EJ?GXbTW_Q^f;*0bpJt z_I!1TK*K@8o(?t`F@qc{aVu11a#e9N3)l=oX^2iaDwNztQXdB#Biv3E)5)M5P-v?x zms~mtF?k;16qC(dr?9yh-H_rU!wU;q=z;GXW2l&(KWQ!v(OrYw0jfp6RWTDMQ_K>6 zGs%<SCL+^~Z#gg+;wxWKLD#23RXC>?aXG75<UB*hZ5xdwD@v@%J|LuI|1m4NLWSH3 zh*u9!vMPkMqZ*+{8gX`0<#vrhnU1k!-%#0oC%MCNjr<$&yRp(r0Y{GqR0(<6qsafU z%VA}mfoStq8g&kqL$O-UlGd`lu!vGO{UKn^mfK!au(r&TFX@v;yTL=z*6g=}*pwFR zL}au@G(VvnbA`e8fOPxwqI|IXYrNN6ZB$;(L$?p*O}v9vU1OpbnU7y}lS>n$QD7Ju z^y$ZO<-iXpXB-OJpBF)Tz5Z>X8&|1Uh8@&ywdkhUb#1riE=@w4Er=5q4d}feiz|Mh zN}A^8J$|#9OZ<dHVc0RTKnPecXd)2LE`4n(5TQszU=>M8Fc1ph!Hv_d7x~jJ_rC~% z{(pthMbW^}kigJ{0)Did<%NHB-F*1$5c=et*_ba^X|XyhzZTwX&LQduzL`j7<M=+F z62e{2*T=qCKzfp~d0ZS&OtE?{&b)LJva|Tv!65p+s}M6cc@4MJWF@36*X@FaucqL8 zv3<3wFHz8=p6O6`69%*VQOof5$5ozCU()tdym;xE)$Cb<jPpAf@py%3VqF(}qtA-n z7R&<OQEV{^Q-{ZKm9xZONc=`v{)x}EeV@yG^x4*?r&+XHD3A|IN@e9`KUXSuEw(4r zxLNJAI2u<okbZBqaSFqbqqXo}<Y#WT%52#=z5AUVOV4VpRujLPFCTThw6AhT$SwP) zP<{%gY2Jq8YWeC4gORgh1>oc<e1IreldS(#U4revZoA%~WgU~opr+$9TO`+IACuXm zS1TPdJol8*qY6FoX0DLMfsblK?qa`imEFRldZy3+5Q8&COX>G_u|nmk1fI8q-FlUU z8uY+Yuhugp<)7Q{aIeZu*S`o#dvB_6kGo#1(h*+ZxjGArzG(W)^O@0ha98lW7*MCj zX_kzN@Zl|SRZh;4+m`aYxPoxKSpLkDKg!|Pm#$SOrf9RMq)1k=9^tUmx+!j<Wvf|d zB`CEII<LkZQFkzCalNqT$LYG0Hy;Vzx*&JESnrl<TCCu=QJ0afP|R10ImSl!G<T6b zi(h@e(kApx({4~PUz$$d?q^N=PErVZzv_6l#85dYJ}rutcI6@KUMSz4L0J6u|CUQk z0Vl*o`xYwW%vc;B{b4>5d=;W-U+h+Gk}_Yy!LD;yeCH~+7PcM9vC1}hC+hM_rGV%C zJ`<Ezuk}rAk9hObOY}|Ni}SUb_6~k}CZ)YN{&;FQEuq(5E`JMn3*sW=eEeSD$o15G zUs0!Fv(@LV{=JkjbvBS%!mh(velLEMk$kaqjkVEb-;nWy%Mo|AS|_Jm+?X~zhtE>! zTCP;7)il|dv9(>b&c^+IHTgOm!K}7W(Y4mO7gnue{kn9Xj;B&8o*YNxvshvi1L3gb zuu9)R%y4qGp&?hj*=c5;m~ykIaIxB|(~z=MVa9fjmphxkz`JE~VbNT>_!SgqqEpQI zFUlrcw!u&#ZMSc+=I0qA^G;jumeGz`-@53+-zu{@Xt(0OFzw!?Xg4pFMz3h8Sm3_0 z!;^rvihyr#wALeUG7*-xM$O{jM{Q@9zW#KoUR-B%jkEeR-x7ClIZdA><>>x1L%`p@ zU;rFvuSr?w_JX)$oAK^+#hLkB_T?0{oJ^x;zgi^WDZRw0eXx1GO4VSs#F3`)G%UBl zCiMnTak4JW(5qOsStt=1xVFN6qIH+GQSUk8b>O&sQy#F^Gl;ZWqq(~ppF(XnZ;Y!| zuC=;2_q8uQM^_e<bl4uRHO_u*WZU#S*l3B9q<bxXE{YDHWI#@*mIB|woEg1L$ayp_ zZkLKU<L@vRCo3*>mx?RHnnS-*Uf>ZWbC;1}>w(G5Nm`@lr!V&r)ozi`9*=yC_Sk-8 z*S}%ce_`LgWdA<4aoeJpqCxF}wtqE8HSJFA<8_C!O<rcMmWgW&A6%@VmO4W&aQ?7n zAtmpw2a0$cOm+DvijItcj*wR~5FMm6Lz{3&o~>3H`7B{!rfy1{zD!c8v+;ELbr4Fg z!%_3%+g0L<yKH@Kz}xL3n)o^x-hP*U*Dj)LKCw70<vYfutE@jM`%a}Y>>@RxO7CfI zO5w_outv_>t&&(YtvB!WZb4S;t|09YpB}r$)9rSaQ`Be=mB(nEvPTw_IqWX)L_JT^ zU{OkzxKwYkLKY(-{=tw<E6yQ%L9j#J<TEI-RA7Vg&I%%>XvULaPEp*0qQpnq1nQz$ z_V^XN$i_)MMI~6?g}YM@`qdSXG%W^W48H;SQ=?WFZ4XUIMsD7p>MAYgO<Ha-(0Iro zq+mYI_BfGI?<wX@zL;)6Lm)17hDNVl7FWcUkezg$CRJqq>|(^%l$c<IhhW^4!0da4 zs#54?9TsYpE`<*ui_n{AVOi+-V^;)eDpoYoqVQFAX_IUaD>)TT1Uq@U++>sLGI=eg z9xrymA&0{$KQ(H~uf=n-p6)(%n9h_47oQT}?`3|QGuLTB@dN0x+-#IKJYa~p%RYCJ zl96G-4?kqkvxNibB=~(49lqLUv+NWEFBIF9yuJO736YOem`q~hEmez(Y|>t|T2*w7 zoRO8Qq)S+*l*+4+l$KS*F3;|8lU4>aj;mRx<(Z?yRH%BTQ<CwvE<H-hPe~#(NlCd; z2UOe_P8NB@r)3cZ;1tL>1xilJVw%=xZ)hk1PH5EhS%jKW$*)j5Y(@s7t=*(gUI!x+ z%G9GUVlszX{Em|CtyYa{MHjEr(^YBt`MXb|vJ>WcetDH+{|gQm#9f9Dm6`j)8OF>z zjDJPo>lFEBsaBdDVEj0s(7chof*o$yeaCXX`!JK771k*7Jb!ff+7*%{bhyZfG<th$ ziw?99@sP5T5L1(oMu61ELsh9zT5oM%NaXklb*J9VXn2H8vw#@bM-?G<^o=KOAKC4o z+wDBj3J*Nnx4VM^eojw*a9z$81*o$*Os&03OSX6@$%;En-|igwq{eNUeKq%r*ebI3 z#$-pS8p%z~y~8ZyTGT5WRb5s9_IWdg4-3TL`_@M~-K9hmX$jYx#Xg+6GW>c|6csN@ zD-2evID9<qhJFjOV-Y3fPvPP0`-E3H7ACmMF_Py=i`dx6(B=4+VWuU&toRd<+|HJX z$sdNoHF8$A3@^*a(ovRnT(z63MC4EO)<~!y^hR3hwv!K`g_xQn^}-FUnlieSN{3jN zz73YQDfj$3p4U4RXx7>O;w)V(mE!}$lOOS#8nnDU-+GLcEWo+o5Yrk@RPT~&LaX)H ziq4^sO9!Vw(cY;>IFaWtDs_tvp(M))80;URuxDxKGdH8ZyG69l2G5cWiZ`H`Xr6Lm z@Z=<?4G5nwZ#b#f&Ei9eJcLgNNKfwNKX{ya$37|%c!q}gO3gj+J%WUEiCQ`!uN!cI zik>PW^9U0;R6*fO?5P%*5m{72=_y3mk+_sn)yV{3Bj<ZjA3)716*!2UitXu^oSI|5 zN5E}Sh`5iU@S2_4Lqq3|&wfFPAS#mR{QGg~wiS+FD!sJmp$V=_a*mee5eHo_HNVC5 z6o0&(R>Uv)U;Wti5jTj{`~f?hB7B~lek)Pd%h@%zQ{Bkv77@&id>K27bo};y1U-pA zxR<llX!b24?#dp)RAHyl`0vU1@9bhd#O+W{!&Dpf)lBmW-+o39N{Ys%D#mdt`_3cH z(ljS&s2Hejo6x*_!PzeT^4vxSTt;Q8EDUtOY9DRn?;Ye2twx$<fH}>u1ZT}vJ`A@7 zu?&pTLlvWwG}hIb^m7UPq^yB~<NN^tYhHqpQKKZeNI>d<r%<U;R|vMaA8Jw+7Dp0` zRa3uZxpWF*;Sh|@G5t1q|Ffb~E!FQ5IAQHPK$Pf4-VgvkFM-z#i(VG1T6%2UsFA>^ z5erIQ>9c^-cL;Yg!Kfx;v$)<M!cwT;651O_+qS?+206HzNc&`J_1AA@Kddu>Xcxki z1N!&K{hd`;EDMCh$y{&%3Ekg6d}7$SGZ=snI#Xnw$rM1bEw;|II+Htzi1}TzEjn!- zj+J_+M;Tpg6?-(J;j=cyF^fBa&d4#~sp=5IA#<!`O1zP}DPlbsoqB+yVP5C&$0DU| zW%;0VN4zaAZ8uE%jIBiIPQ&C{pBssmeX7Q!{;+`A%$3OW!$)~P8VB^3-*B1KB5=iB zfxzf0F&V9|JV}#IdU-kBI}>jppDC3)j!D289po5I+oCa#iG0pk@wt*Tp~)qeGu78T zv~;fNCECLe$XTj_N-p~<wnP1UYBbBH#+sJHmqvHNq0J>8?LB*f-&bftZNtO%xnIYx zF$e69(Q`dvo@17n(wvPvsE=5sulF65km2yN%Cvsmm9iG|OUUCBtx@Al{nB&aYdAPR z;*J35#@G1s8Pm%W4Z{hY#oY*(zZ;&!CM{Z(xvM&YiNWgEyx*4yg~(lBu+Sd4TV}v7 ze{ElA$x&AwwoN~Mqy-EkfG>#)qxJF5OqVk=*T|ppO$}l#1!KQ?9wpWtfNn-XAf4u~ zi3HND;r8ep%f=*e#n0%5L!Ar%GLTYz5gU{KF+{lFi3IaTyBHC7mTeuul8!xUSoK@w zasVy-zT-0ld5H~^J7lBik>c~0J5E~4lw;Pqwp~uGE8mng#aTK-?8vb{e>7q3oHD-$ zjyC&qF}Yu|OthF`XAN)a+?f!{!m*-b0hK9%J%F8gGleybp*Sqr{MSp3BaohiI*K`m zTzuTJd6CwPZ_Tfl#WdDrBAz)f++3G6KYej>S=Z*+^4L1S+i!|r8_Wv1N-R8Oh>fx4 zDnUDWo=3N#$5Kz5*Q_ymw!X?OXZaHQO4RyA%TBQLOMh03#*$L{qo?eP){0UaQgyDX z!kp1cfgN8{4|0j_i%*4T7ajk$0^zmt*-sAgTMn|b2K2io$mU{~Cu3#FjeljpW$wM* z5>&4-pJ81R(?_IXID>soEYljJQp;}Y{Ip<-%}b@>IHPH{s+eJ28q-InVL78|?h=1$ zd+CyYYTbHSzTw67&SMI-$+A`?(bW<XKchfI#q|`Dr?C*>sz`+QVwMemT`bIPDWA}q zQZLN>bXFFBT`tURC7<8rRD!3m6yZxV>{|r$r_}ss{&FYdOCs#qn5U6*7V;GK5%e7t z)IXq?$dHK%3=0MhG!&H7KVBA-aL8p<vt$j-kc0_34Nexg1Z2VgtQXFZ;xG6lh&ijC zC&Pl-F=%=FBnTMcDa&fRnWtql6gmcJJ6NybI30Kj@I-u1pp^yr0x}Xx9gZew75Fmn zMF4!SxFIS4dlkeR#1_0c|H|#SCisl6EAgz<9T@9%p(B95SASQ<kPtxl3daS62ZjKg z3mO(c`itXl(A^;j7G!6OITqw+bLh@8+<_q$<N()hIr{(?)-bR^^<EJ>FAN@NEGVUa zVlQ<GX%CP*sD(dcKm*o;2GvK#nMDuqG$^LOLqHT&NANAnE&eUPBX}!}E~qb14JhaA zw?r^!fKIPnkspY!_$R_GtRrSC<R{E0#3m#G5MqG7uHXjlDd;2kJMdQkaPKceBql5@ z5IFEq;DG?-UP(hVCLAo#Xz*d+gE}8dqAVy`5DHKg|MFh8BsoZPA_3G<5Od&$HeY)N zchX=T_0xOUR)!v=UfBtgk|RvwZY=-MaYmtw;$9X*ZUD#|ZGc*@wIRH}{zT!*{b_Rb zf2nSCBi!+SbqBl=fZr=`hzY=51-%3Zu&<Uvy#Rj%%=m-$3hg4@ayw#iftZ4F|6=$H z`9Dh8g{_IoJ$@f*2=@ce0__fM`GDAK=@r{Wam0Q_cqM2B(LLgM$GHXFUkSkNjq8=$ z#c<5L7#9Q<1a<c}`0M^3g+#jb0KWu4>Ou;L>JxgbcLTXaFDRT^g9Zoz-B)zgR>EVf z`-M9i1hEo?YEzj}nT`7X;fg$V(!)23iSMN{Y@D6O$~VAAlm(&yNd&s=(4d6XGmV_B zl>;pSW%W=0g~0@y20saW4D#Urc4`3(K)Ms_H8zBP*QI&9m;io!dxtS32f%2;S0TUO z%nt74BU@xUGWHQJh9Z{16DoQI%{Fm@r2W2<1N!`RH3iiIlnbOYx8i0PHv8lTyS1wl z?M~SL)oalUyi4Q=<;n#4%zf;sc`AO(8Z#?F`Zx#WTY~&4TWEV|G9BZDRFErv%Neul zfYZ0b7T9A2=C?qFY4!)`Q@)UNG4yPBB-q{?j|(_Ul(5vF5lIj}OXyQ(m+ltb5#bf* z6@n|SPWKno&IkiE{>MM0&1?8q_OkCn?(*C_5Pc%ILT?JcbKIidvN}S(g15qMg8G8{ zf?k6n`iK2uEI082_3`iiC0L&M9OeTD1OXECmmt~^sz1M%X)x?R*4ayS7?aYzhBY`s zf*0n<*AERvNs<-p$if7k1|uu7?Gnt2G%cD2{x8pC4eA8QIpB}vj6^GtCKMeA2B?gG zG1kF9nC;Ud)qtrJcjIUVx!EAsfG(4E1Mi8KU)Uw^IKl$Z@2-?v*2!Hj_?16ylH1`g zd!__}SO3^;yT3@6@AR4fIbmfJd-e+N348U@+<pOc-r$RV{$JF&#!7B+K#qdn7a?%b zY-!kj3AjOuaQwU6@Sff9s<;rA@xpTUw^x8Ei{yVbiP|m=G8W16!Dj(?gO$B$1sedL z6xsj418J;$^9i###2sT6=?pE4bthj7<Zr^xFOQ`b$#=tfL*fys6r2rE=c#qw6uZ?( zXTvPWhgg*we-!;s>#tI&^HKKug>RdyYMYB_FZtE?57KAqEA6D6zeYBYBIrU%+)!Vk zwZF8^r>QQY>w69Imb{?+65mA*eZo5t3IAjD%?Ouv7cPgTq=H?w)mnmiJ=LuLK8Jr_ zFOp$dHUEHZOGaTw48XPJKiFogD8vy#52b;d!peA!5qkRF{0-$Qm;odUMgg1{Kuwit zeG#XG!L_yg^Xug&&uUV1gYIw&<aqUcXY_~j{_AAzT@V(CtN(|t(Y2Rk!w0<&MdyVW zb1UrAnb2w0XH@5<FPqV@?dp4J*=OwH*^HLD`Cs|>Ih;m4n~QI_dywS~PoNFS#t2K+ zISkd;!Q~*?5If0Bz>{KglqKgJK|z;jZUzX%x<uWVXf8575s#!x_^C4#x9!CqoH^QG zyNmcmOR|Y#O#)=^JxRAD;}Ki_KzvFbA(w<xv^mVbzt@)wS5rj+3H}YE+!hygWVUoc zL-h~M1Tze)>pwbW_bC6LL-i*N$n2%x{a=tnewtrITnfyA77-{HrOsEi2p7fmA7P#% z=Jm`1LqYzxR;ifaun?kw#6b%E=cE;kO^B`dn1Iv%%bh0WOqM9eSV_v2j>3tWE0e*- zBit%MnS(m}xApQF3ILEai(C%>d5e9S05fU94m?fZDlm26$^iCWZ9{wj!77j~7zc1> z0DUiIueu>F0KeW1h&Mp5*WQrvmKuP#is%i22ZR6;=06iK(2GojI|}R+z_&}~2-mAM z5lhMkoCPKiY%r@kLv&B>sD4I$Nn7A7YCw8{yUXXF&6ljF=!x@@a*8tNE~*h;gRCdf z^nVpF-WoHi&^yzMzl*uc;E4E&{tDlU$Vc?{i;)lYzs)>PkO>G23JwGURK)+Ei5qe; zfnY&LgNTEg_}6JGSR91!!jv}Ljs7=~fOUm#pF_}z1klqUWFgIgSOe$`F_>`EKqtYE zfgb|EdxZ^=0a%*C_x@(uJ?(hEW@-x#G|G3-d!-H00sq6pF})OqtN`#=uvHja&}L9x z|Lk4{LrwtXDy%omCFnC~E-<8jP%rVW^DdJk*eHaPzb+9X*Z;EXF6=JbE%`0_ExRMZ zD@H4rH>56b4e&|;;Xejd`1h_1x3rFg9#Fk^GlpGwiVl3=|0;J-T*xdUmVfsG84@u; zVZp)ucDcxh3i}Uz0@bkElJzU~S{lMJEzfDeg>BI}2{){9L9*pog3O5)NQov!lpx8G zXNa?9TSCmq7SI)FA!MD{7I2F?M;s$hQELBc{A-CkXAF7m3_Yh@U?w^dp^j8drY+W( zVF^E{UEnHu5y63EN4757m~II>M=7ck(Tr?I**`b!#^s5yab(%E6`M2hEDW3r@`2AE zhcA8z^%oQmd>9t<r=t{q<?5{60;%(${iglet7c;v-u+3~Z5cS*x$$#&?)lFyNQMur zZc(6(s36f3shB<T7Dc<vqi<{OM>in}BK}MMLD-Y))+>qR%kP1&x=03fr2m>_6$UWZ z==Tnz>}hxV#}ux594h3Gdi~PLLuHC#DM9Umoy?n?7Vk=~Nzp;J2W?h4V<^}X_0nyL zyST~O|Gw55A;h+z#&oC$FKPw9q?OJQ>3HY`-_==>GyUr|2alMSu1vTRVc&X9r4IJs zwpBNIkg~TIa81uS+jRxe-(5beSKw9ifz;OZ39{8jH@Y?L;Q{!VA+h)CLHft_;oA`s z1|CpV8H;SAGg}P8kM?{n%%YLDdzu81yKC*4+oiB;j)LwaI0`@|0Hl{d+=7?c3l~Iw zk(XIqHF;8_hOf=qSsf+O)K@Ea@Wq~a#IItIN~-z{ss*`|y`jw}-=WPX-=STtPeU>t zCHKr+uMA9R#_P~j$DTd%ewp@S;Q^U84KeDMFXd~umZT^RQ;#&<p@D%d-@ve+A$$^3 z4=Z<u)N8DwT&KdbGaZV~XkM|hTPTlcmTP6=E!V(~_)VXTuvAy~sM3MlJ}8CMIgQm+ zv)Pw57Dw{lHWqE)nA?9=>j?bX9IYeN7B=_olrH(gyNed*(j(yk?jbmTMN2Y3y7e{M z85wQUi`WalE3sP<?&3hw3BC#83*rl5z8-&#AKU{}EYpg~^y<WAJ>L2<k>t!6;j|uv zn-3ZHdnSlw{Q>C|J)i#t`B{AR|IM?3_$>H6raf=-UV3ZzT5Y^;`5xecF(AZRTg13u zLM)nix%#)ra^3EB9mB-=P7pwAQvWb~a=UXGlLGOH^yj+Ubnn`yDukuFyN|)_DP2d+ zSCb9Uwo4S_e;0Ux=#KepA;h`$B-3=(fB2fcR2=T9ZT47UV%ooCsVrp=RkXJ{hV>k7 zC$e<npmj6$4^3?h(Qn|J^Il_ptlg$={SKD{cYJgGz4ouc*IrLIn0dt9W?Z9eAovaG zA^5U?)?RxC*M|H|Z3W!AzA(Ms>^1e<bojM2G=?w^x5njs-Y19myTZgb(#tRpv1@EP z1@{Powknqd3VT5-24>=Ak_n(N&`*ajU47n~ac_@<`#c1uFoZj!qJs~@vIyDxh7vV1 z8TOHfITG|nqQ!@k8c9Vb2^%GB*Y_{D(r3U@41E1n7ErWEL%}q(e}fj)Br=)I=f<WH zJ~YV70E+q;KD(Pvi})o8Ejl90r;Jc4<cU_W>AFR!o;UjlQxc`*tj1)ucTb5)J6h`W z@>^~`wOO~CzmK@)i0UVhF`S|g;o)VbHavo+Y*<5l3;TJBtyjVvA2{=_KzNSi{f+tP zL_;!po9CL8={cM)BqAlsOIJZ4lN;?FCFKH|!=#_-=LgL;#b4+HX&S&`4h|L@{s~to zcD!ItywDUGU$svz{q8c7M~?PEJgr$aW>N(<=CtLFd+XuL*}bC#O{fG~(^yjUnBr;X zdH>M>6Vy%}Pw2zuIepK|3F=XlvUBuG^@!+B__JkCYglMlEhB_md<u&mVTl?b7n+*R zA?#x0=^fEVqrWRW*$=a`%-XR(2NH(<_n{R9i#ErhYP-lRVaVzrl=PP7FIdCvKEP=~ z-s9!f#3ptRC#_l?Ft&KWapG!YAK>mS3}wP*#rWq1=>>kQ$j6yFO~0-`@qYSJp&RRG z!eo5r1?v1o&1h{~U&@pD`y>RsgE*}|%kcW69`7T*a<^|7HdS3AKT=IQnZqFNPohMJ zO8@#+f%wsniE3Q|nubf#;YiU)p=7Eo)E@O!6uN1BpzIDpafBgVNZBvFKE7PO#uk44 zDgF|rPlM2u0iWny=CQjpOJ6yavT%~{@pgsq@fV54rK#R;Lp2yU$u}C~AHK8Ntxw&X zJ|evpAGR9Gu5QO4Lt%v<Utxv69>Y(qY%nX7Uvuh@jyM>!>3%@(czN#Hm(U8YCo1;{ z`fYFcmM?yU^n5aVzhT>Mg$>Lv30zL88cu>&pnd=X9GxdGC!2-Fdv|>0Lz1}@iuxj? zsgryPr$qpm)3p0TLX{ssJSDo^5GZKs6Z{w|2^j*}rLl@?!yT@BrRlJK4d~X?epZX( z<TG{X+<~DwIgSCD{0y{hksv%nE5<aI!_2ldeMiuw5otjuAF`M~<i8j8<I>h?ypz?B z*AKR8$;6nPD17y|sB^WYD=p5j=rl{`Lm151P>vqv55w@yy>4jH>Lfe%nZx`MBIl80 zN4B{?6_i+C=4o<a&=ir7$KmQvO_rnuGgfmG81x(&%04-;@@VvsZs+5yG2upCPOp}D z!zE}Z>0f4M;J)!5jjAO6c(*8T^5!<WuRjh;3Gm!jV|r70JMs<iA9c;2{z5|cNMh8A z&k#dO{#QfzK_Q9K2mA%i1LgbBr>bD4`p2p%kY-!vF?X4^b7qsW#jQJ7u;w3K)eru} zfjLf;CVU>u(v~shTvqIAPy9^GIsAbs60m+WfV5dobdGa(I5yb#=6(8^PsC_tpGmZ_ zuosl}(5TIWS?hQHbbopzT%>*Vyi=H#dpZREI1Skq@T~F7!6NgU58NE#b2fU>vXl>b zz92<B+E|Zclp!X3C>t+KiG%(mJpa@`E#&D0`2VQiA%?wMG(3OWn`M25DG(oFG;=u1 zGu<7&3;wZrV^v{T#SfWG`KgVPV{Em|Bt{ec_DJ;1DZA$F_tB7~Km9ggy<*19|1pC5 zbmurpp80m<`WdPJ&TM$EUAWyo>RqP<;<RDS{rrntqO#@V^!c+Cfi8Q~WBCc4)=&9f z_Bh(|Wl;TM(*4-O)o%GMQSn|2albl3cJr>)OzvT3UPr?vU)g;a3b3wNolq_0c|1AM z7;u#H#Of4&q-l6GNI&X?5c`hN@~vg99NkGjGKkUwZemX2nS`mDsLJ!4go!m$m-I15 zxAX7N6fs9{!EZ@JufU0a<g^T9r`TBzPLP|>E;gRfHku;Kc|@<~%<LeqKE0<e>m*NZ zA@PhVW1q@{AKIqy41;)L7}_Saw$a*C=biA}iEOCaSLY)~EhCCA<G1@H^ZZw}d;p(r zVn({IO7#NNT<v<aiY7Zv4c*xT+PQ2wU5l}lxzZEUeCc$&Dua;aBH6{fE2FQf$DHM& z$$rwai?`u>J4mZZz*zk%XR_jfx#9dfNbBi)tSZe<swHV@^1a1nfcbKN{wwN9zSqz4 zvkNPxh_f)$mQY;f)TWh?_5At<9>OCiVA~KW5XlCxM;u)QH?;7+Wkk2>Gj4r?Enjka zBiN4{ZEEgY*x6_4nWb$~Hi{D^eiLV6R^56RKBNFdE(=C357LwiC!G;Ee^?GzGB>~q z&%o-+<0O~{e9ncsNDaIA2ZPFvqih0CaiuK}@g!jWq)==aWRL|ju$>;n)nKwnCxzaK zFjddmynAxeyDfbPW`GvHmZfw37H7Z)zLvFf`xb)KsBlj%lru3f5^ZgNX@PRvu#$7t zd>yVY?Yj=2^z8&;w#O~-y9@R)cPmfd7O|Cc1NM4A0Q|-5qxWmom+fLkK*Sq}7l>dK zArP`o^-KWt3KH+$?US1&tZra;?-I%bYyu+AlzUexx*dPY82d3d>QpnuBIUgddzE=$ zL~a^-d6V8k&a_D(V^Srw$Ix`7RPGZFYvP_fuwn9j95?F$!oVxYAzFAEw;PDT$Oo3e zN{`LlIbtr1A(goj`jXcKekU`FmckdkFmtdbfRZ)s2kkisd)d`g){HY6`YZgE&23@O z0lVAo_1{quz8vt^dGjvMupST{usLJb0S&9QyIPl=x3+GCd|~`RGiHc=51NeARc}?C z9>5o%`lIiGn03CJ9pryCCn5Y(0Rg+kwn!e>xN!Q@q5c|x{M}?*EDv;C1pO&-{}n)d zRwF0OU6U=F2R<&S{>*S7C*b#Pj4huBY9`S3Bt>8gKx$WE)$~@KBShxq4=pAy<)%-7 z!qLJj^Ygh&yM{Jv^lf?;TaKAW%2@i;*vsTzqqlP(C6m-QTZ*T^E0_+D0JJZ}MJLi7 zWABV%HfDYgt1WY>nLdcXL`nikm6`adll(<@;6nWheUzBNnAMaOZ>~ipQctgX5Mw?2 zu8b}HD{q>SR@DQKKE_2Sfp@YUkbwzqpCCYP_rjL-6=DPAJKhJRd#pLoq0Vl1!<P4z zvKjP~bpyJ4z%9VfsH4w)jo_BQ8S|6&1ML%c1G0POIfxK2(~GzU?8xYgw3$f=^0Jp3 z^l?A1>-z~cyq0Sb1nH<98<3S^f`pi44{k%H!M&NL!N9pq7YCw&$389~?rF5&x*{!a zm|alydlM$!Ud)Chm6ALCgjzClum?!txU^5@6`NpRy}rcR7X~yfprBr}-|$Ra=i$$5 z>SBOv%7V@-?Z63~pWQ3&zzMto(`U6-epD!9Jn+MWY40a5i{UrF7DJD4%0z9#(Y=jn z+b3kbu5UJ7>JHJwk>J0fOw%^JZ)5l#F^UO?#IgI_QPZ~>)A~<@60M>2;cAxoYU`_m zeM}R4claIFPuhx3Q~co$WcyF97C(^zJYT}B2}1veSCLz#Z5gIhK+E6jiK;lRAy<R< z=s|@?k#EdS!B;jyAc29K1iAZw@Z91#pCQPI?f&Xm_0+a=#&KchpuUqgB-OUwu)v;w z#kDcH<)3A|x!%jH^p2Jn=6#0VLn@I2A$aC*sRjz+<CyuDZ@JjsX(>=y^k=OVnE2*y z$p#YP6`1*!ZrKJF;Aem&G+E&-jHr6<w01wteF$dh2=3QLPsxw{y`tK71&j&aKXFTz z48X9=s0yQj1Cy8)|FS?pD-Rno6W$RU1ACNdLJ?441cXHm?IIgPdlYHH5RlI<S^iFm zU4#x~6j|n~!{gJh`eo6yD$T3~R>i`qZ|4z%{jEK4(L1Ag(U)8WU*b|Ywqsh-w03CA zEL*}M_IFJV-6&QE^1&iEtvz2SL}w*~yvpvBaX5prs<cRFN1N)irO-PJ;0DtW;FiAL zVYR+YFyPe%o@?9~H7IavER>;3BAF-yZsmk6=g_u_I0yfGv=&3b&pgz*_f}{i49?H~ z6>xSK9$|qy&75V!HFK+^a+0=p5UPMBVD*~Bgs=yDvnn4&8T+mlh_J;Ti15)ADF5La z82j-QIQ;?tPh$Rw+&`K6C-nbB0WAAP2R!>_<6pla#(dUobcGUNQAT<p^Zp#+n$q21 z?E_AJiE+~FHMp|dwU2vSy3gm4+X}SJ*bVMS!4QZTF0&i7&+_{B72&h(mB3q57ddtV z%-T=`9DlU9cmplb;m;IZA7W@NERU|#&WO|w<sf_`AGI%7E()hrN2E@p-jLmT%|7LA z>b}Ll9^sYt4)!&x74}<u6V#8?7d#iUXU-S>#i}O~ve95j<d%F+hUKFE6#iTO5!jFM zJ+L<1cek=Y@HGPq=vxR5%m6c}mvhkYHqIF8wZw9~O2?S0pk;{ZR>7F-)q)wsTOu0l zheSN!CUS1q%NXuegBjy96AQANaX5%D9C|m*n4~o|3+$SnGU#KJdbit&rZqPU;+nBE z2yzsAH^7O)<L@L0VoF1=$f516A~z(T>@+xH>O(K-q4%w18xWr~16W*2RIkaQ@vUYX z7@s@?cwFj4FY2Mwt!NtvpHvN)LP}+?%AxhGY8wQfTn)HF>P0W>q35k^8*mmmXQ0KH z-0qqat4Hi6NDi$a@Jymk-@_3H^%iF}`TTmUq_a(1qZ`qDzcyucc6eiBvy=@ciGx<I zRI`ruXw$RgYl<!Pi*P^j8}+Mm8=|-7H6&p?^1M%`55+?fk3R%@bx;BaY+GaDJ(4wE z;&45gRtg>Mmb=m$EcR_%x%aB70EyLw+5f<5V1($rk*v-ldX+I|wSnxJR*OL7Q$6xv z^hZ1gC^wa|5A%>(wgmq}SU=^y@$4K9JNq*sT0V!oFmWdDir&gk?3rRFzH6*@Z2OSY z)Zw0QEwdAd`|j^UL(&e$Xq(C5r}5!ta&l3o<rl>lV*%fII~aq59)w`Xw<X99Znv@a zf|l=&>~26OO~@0{WVQY?d%e&HWW5k>%|dhN>a%OT;0MH7?icA=u216SOt;vx#m?|K zAotL5qkndj%va)@L}KmRy|`_5^8unKqsA|B+$k#j!!+y}v|i+ccr9P95ZfZrnb0Az zFxPNllww`+imW4*ehXn)7WW{*9%edm&FvRqCdJ%}qhO}#3nyQ1$%AR0+{qY`3eV~x zB5-O`==!lR`y{TSgtKg^^VJE{!ca4o{XwWn91vrpL?GrGqjf1Mt{G#)lO?Hr^j;m6 zj(r?xEnp-_V_s5Y4HZexPN9+Cp0ZKPqEf2l`7n^<LM{{j-@Z7mMwr|)nk>z~ZAKjx z<g>uSKVj7{_*1-gJW1|rrfwZ}JQ**F23}WIW!vaomn~l<xNod*B67DyT7Q4i`C+|# zOMDSCd=b-q1xug9?ArbC>nud1d5R9#ukjXqYxe#$|B)|r%dz^in1d)_&Im+U`b-&J zmiXK-yTQGoA3kX1h5Q=bh4oq71?Ii15AzK(o@h^@W1riyh0dWzqk*Gu=FzmrUgpuK z5BrVb8I;?+62!QM+nah7gEWf|V!z6v)@e+m&yHT39x0d>xpE~}whD^wup!2Q+op4R zl3t|%=fK`Mb?Y{;_((~5Z$Y{ni~JUa?6<NFY{XA|$*FV2)?fDSqw*C{{ynRDSzM;% z&;+~m1FZI}GHqWt$RGN${&>Z!Z009cftM!$j;N}*Pb4OdSM|Kc$nzsQVHDX%7Fzk` zX7yqq8K->CN_y}s062z&WteIa*G4-|1=3De?FV!qz3fuN{OPQpN6N=OwTYr~po}%7 zLO~6EQXo4YaGAWRA8oeLkkqC)rEX3;`}ep45AC_6AAGw%q{I;!ok3km^LhzpIWQ`I zyihJLcAi3U#1ggaU{~!N3~es8F7cV?$bI~>n{g5#k6TjRy*;{_XPM}rx;R1M3Sq}t z$=Ih;R;#e61y@7}Z`zTdY8GbIsNxes(@{2V?>a4D)B);9sWiPJ{>m9TgWGNBb%SPB z?Q>dC)kJtTp`9DgfKcW@>`esyvq}32K=u{Y3%<2Q$th$o!U@9WyM`toogh#<{2fPq z-!!shmV}Tm<(n_u&C2iN@EW;S8Cr||9iX~<qwpYL=gDny{8gO(qUD<19NrErQ0fcj zS8X5Iqtw!SvShc8c}}^G$yO0{Zt=()E&i09;C}h(yz3=K+G#xOruG%cLuzTeqBg77 z0Q4d|WdQ$U?sSdo3)IJn27e{Nnw4qp-!cj0P#M<9ugVy5mG2pJQzo!$plWcdhcnH; zq#)&vcChxWaNj-uh5oh$9O=yj?PCbW&sN}Ng|ekns71)HsOBV*`2JyIVhSp|_lpLv z2vX=6t9CM98&TaQ4PMD5Eq>A{0|$;1UfeQ4Fd-vmer_ILB9Wl<NioEE*Z@xD4T5u< zEyEB;jxB~lsc_7bt2vGrI2C8gYDIPh5R1KD(33_iK{fPhmKmcO`G5h}p>*rMYFSKH zJMgV+8x4a|JW^r!w#bEUn~Gf<s5S_OUNs@F+BdzBs0#R)G)2F}<Q4<Rq~=ru{}AMr z1Bc|})C2d*$rUQNS+}K#_+B#CqT#v?`FAV(v_;3$HHw8nh{%ZgKBl%2co$Kmb_8xZ zD_s?C?2BlJ8lw3{CtinQ9HHENA71=e?Lm=3U98lsOATp8o8+XHr&*qq3xiUbL{nf} zr%mlAYZ#*p92wOl{YN!ivs5*Yh6ac0qP+HZq}{LNP+aB_@l2)>P7agUm=?oWIe|XB zO#2}`lT1uiU!owfD7*3f*3ELHHK(xp;3#-p<CgVey0Nb>_e!Lv<qdTm+M#o+8zI)L zey=&8nQR3m?3*kfRb(BtHG5{};I-WNOYlhiU-Z%ML=;yTm|;w3%H)G5WhK>jRsXX0 z%1CFUaf#~7m8If?r0vT_$+e0_3D6EgBMN8XtO<h56(6|nb&2JFvDQdr1)D3n!DI-n zVa~#h%Zsquw{n3_wfZ3&(4ZftU|67=e<!_4mm{@AbCs$pX++j0(}c!0H{3dO4shS# zPlMOv4tJAxtQD?`;V9&_iXK3tr6!9Vyv6$dO02s%>u3&GMeg(1)(cp7udkf_U~q$v z+?n$AKYjsRD1Pa?fs(!^eL;b4jM(@0KR4(w)y=Hht*!z;qefp6ePFA*hHnLE`Aqu; z=;52Y8)-_c@}ujrWqwrzMjZhPqrb#?1gck&>|=I${Mc_q4sdw6G@}m@nUj<{QxTda z9=3TjWA=Pp00a}1jy5<g=D*i8Afwhga}iRUJ^WNNCm{3I`MM)M;1~b8E7b|%l=y1Z z_2Hbq5_DOe9#j~MG}XT$M#3*BuD}n4iY%H7fD|;}#v?sJ6M`%0ydivFTG~DGh~sv} zh7i%sa*h6O!a`)W4^9K3w%1}-GD!~_B#SX?9x{zF`*#*cLEU8WkZOz>18IpBT?5)e zoziCM5L}FzulVGm=CNXYaor^O5FI?ZMC2OIgs%MLoaV8`knX?d@{<b{t(Ak`)zYo8 znt5tcFMsM3D-~bB>m)0c?f=wiRx-5&Y8I%q;*zIWKVh~MXqKp1y@=K6R+er`8;7Wy zZCchT>maXdhos*eRx&^6c|*cByFaj}y%BU{v~N$WoEe^|d}CvbR))aq)7w5;=l4Ki zK)wPY(;?o=&8`!7W7`RytZnj)Ro8NF1gck{U3bRTeo<~e>vUbU#AEOq^I9oI;TH`- z)g?A+YF%3N?Zf;8q;4MBwQX!T@i&<oBR0Vyd6gF<<tod>txC{0tt!07A?3z@(?)?7 z{nYOhoe3)9k$gc$%;fvo;V-91=t$)GU-03=wDygx^*zV&K_<42Ip_Q9@FBvqPQ{%Y zzEJu`Kz@8p#+~hwxWDq-?-ke{Qx0hg<z~xa?sKJYx%J8aUAw}(Bun3dn5q|zP}#Qf z7FqxdS!IY*{}#t}xFeS<EaH+`C?n}EuzHPfW{auVx5*fm*~iTgXaAj3a#!E_YoUyx zo1E2CUYCqjc^@S`o|GXGo1d|pl+{xx%ao}QXWxsZFk>GkeU5gYCVj4QA18gz;4HDd z&auFG2*juwZS#cqC6Vxt+$EZNb2?<<pF`*$Ld$WrL036J1y|4o#pSrP5E@2%V~pY| zoiwmWdlM9@59mZHyRT^Oq~_~ia}q<0Y2nS+#suTqRB!yI3<*8wNNt)7iNrlh5*DW| z19`m-8T>@xIfXlD1KnTZ4f+VrSKw2gyr+jGU)^c*pY`TG_(!)l>=!3gsRqS6cq+c1 zkndN_YQZ~sJ47nqpA=sh=`g!3XPje6VL!4xawQ;dj2ZB*sz}5Aq1-fSKjJ-FCBm<u zQ|i)Q!6wWeWAX9;O>znDKBp$UE3gflLo7<o>SWv^)@7wjaGR{cSJ-6_)eS1jymg8{ zSQ}OiS>e_?hh&tRek7E<ebee~B0M9OD`V7U%`);KjayDQ6<6_@{|2TSFAiNOSGOoF zxcYoQj)bHL;j<E!R}ykoSBjnz8*e*@Xwh!Q<C{Yux8N^_%b#UOv&=^zHWsev!m|Qi z(%5&JQ`VQYaS>6uVS|>IZQ;~)oE;TyY-$RNhBtygs{Z6qZ&>JXb8O}R?ieC}NRh*A z>mJ9csEQxY$XoR$wQR4iKW#UpS6kLT(lTVh#DR{AejE~iM+rliD%NDTqjx@~oKH0w zYHR|zD6URMSR16NN^Vv+DQ;{U&WCTus4Sw#yXLAvq^4ESDoyH`cB$KTsq1s8n{%lf zbE(7D>+Q1_B3!-B%|qq;YAL|fpuzJJEJN9tCD1qSKn4szty+OyvVgc{GG8;BY}yRJ zmSZ0XunvF4hA{gcYkm`4_?v)xR0YKz-D!s5(axIqTx$vuTmVeKy{brK(f8}>@hi(l z#(!5C6VR^IMS4~7jnD*K0P5eZY{Hv|XKxO{4>Z#&s+$wu_Gt71)smw_San#wNo*+S zm2$hN>E9?mQX4NhE_ddYZ|e5v=kZsWDCkc}InaUmaWP0A2YlGnw8fg4-9V&%_{4W? zO@M>Ay$rN-jIzL`F}SiCOc3lV64O|78c<vL!R3zVM78_0va*F(Oi3;J=4slq8+au# zHfFT2t<VMg*J>qGxcaojtdjXgv>b@56?I)8iRoNp8up+DuAQD68~Z<CI1t8qqLFP0 z`sY!>d83&5+2sJ}lBwy`nrV$B=CBwv)%6eH8=;$}8c@$kjX7(?tG;BUaZu{A+DA^{ z9f5}puw=CK)h8*^&2rKgZ|Dhj60iN9V{SxrJ$m)oP_%e|Z87`H@W!5rsyvkIp{%+* z<m>C_D_oT7@8=`CY1XLG)Vt}|Sm!I86zX9m)vs(Cq~|@Y%BI|)#wua}Yj&nRd~4={ zq*Xa)4f>i{I1jeV8!75j<Q!h|+O@k8-vy2OTeb}mqhA*snMNwwwR?N~=zBZ$b{z0~ z7p1lwxO+Q2&1((w6;IhyDa2a1wSX9?i(P!TEDVa}Hj4(i(ekL18HC`cP^!?CZgf`R zIv=QuI1O9DeuxD$-f+O4>=2D)<s9Eotf7kooe~}7TFeR+26;7w4*CG<nswyFt`^z| zf$AmR{FD>=2gVtv0!?TOrH!u?b0<qR#tPa%<&5=PrLo6aox0DSZnCKZd?>5fswZ0( zbw7s+TU5&O2J0Scy!T1f+njRW`GSKK^YB-FVPaJ{hkB%e&pR>oaVrULyTI2hlk?Yn zePP2+y|Oh*SHjk8B=4Rm`?S0-`*YC%^)?yrTkYE&XG5aR$w;OJ<;qxG%cy)!CSlx^ z{I`y8r<t2m*XOGM<#UpV$b43ZFHYBSQD=0K$XEe|7C$8zBH*%YCTn%2oWdTx#=z9g zWf7RNMoWHDbEv)mfk$hkp%8()O{)y`B_t8oYhCgQ6$urFnQG!L`AAXgxq+HpQGZbU z57>BOWmpnmlCB}U*&?e<S2d3NfGIk*Fh_8SHW^<I{r<a57$~*I;N<c&>yR<Kl@s#W z^TC6*GL<hrHPL%H;%9tpBId0(Qc%K3g7(`6hF9xuhl_hGhD6%Pt!#zrn=ip3*h9r( z(BM}^9pe($w@y*Up@tob-`tpu|4V!B!TZqMsSVh{TTV9a2ae5$xxBr;n{0G45$fgT zX4*TQkQt}v!qt=cn<&2P+O)hq5^u;InA}7nVf6#mdn{u{wk|8B(1?K(Q`@f$U8pA& zR?4@8g^MXvEI<5EWU${=VvhL<2W@q(^lZ>2Pq0OS8lGb&!;wykg2A>I)Teb?Zub++ ziH1K0JX+g>SXCyIW+TGY1&!89suOgKN1hk2C_|ARspqtm5W@>Zk>bkH;>rnd6eSoJ z<Z*=uo2xf~**bVDzM=917<0a{5sxCZXf1W0guAAAWIRJHyB_!YMBJ1PdgC5W8Eufv zL28LiGI{Y4_PAKs$k>j3(Gfjx{r(@jv=>ZM<%$9)sp%s4LR(H@p)qPw)sA?Y3bAp? zCHtgEPfD{SYff#kB`NG`MNZXqkIV(d2NYA0`0UJisJ3ET`^w0*IlDSh3+4pzwXqeX z&t0~9GsX{1c@Eosc*ld+;a!S5Imuk3tTmVovhxIy>iPl(UM<UvkOD_I1B@v`f#J)> zJ*=vjns1WyzqQ5yR@Fs4Ub9eZP8G2)>(WWptK*912h~M)`NbE6O>~9y%JnHuEe)9Z z;{`lvSn9(Gz)M-B{G@A`*nL&>LDHyLq(#<fly@r`*hEqyuJeG=R}fz0G4b?=iLHXt zMQGXuR<a1}&X0%vqATf0V^MDQWK8?sA-0xK4X0UTIi6cwmgEHOwFd)OO1$)F&Dz`y z<@ygN3u*PFs8nEX7<SV5R8%mv@hWq%CMVSAoD{&sH#k0TqZ#J+kRrcO!46c$C|hW( z!l5`_x}pG>lR>ym3T}|jYVcqE#WA^B-zwupGcwL|F8P!g8se)-QDb`^xC&Zz9BE-J zDS8i199QPoOFS&w7E~eR?fx_!q~ZFqMd;g?V>A4R<$P?#6b1G+E&ZdUBi&I<fVz3` z>0wA#S*~}Xwz9zLj_GGsZUBUWO3>^lL=U51sQK3W-2TG&&Yz-l^rPQeb^4<ddgAA! zgmOVyWd`3PJm$0#FvMh8(()Tpn$lbmn`s+V4OLFcuz&E^V;fP_(0Jn$dwP#h$z8;b zQ+i4w5(=Ob)Dti|y$j%R!<(!0IAU<3B-<ij_(|o*Bj3jA>YY%~$`e@X-*chPkCq$) zAIB8`-U-~Jpp89MYyYiH=qI?965G5_VttTSB!e><dmkVG=`?vp{}zIHd$X|G{$Rcd z|BgTbjOqVN@JMv83@0ak{6hr`k{SK|6?ZZ?rMNwGuPVe)h0G&5QRXFI5YyHhB>r`d z_VkbvQ_R9kY0sd=@woY;DZeS3-1C1Z`^uoWnyp>jU4sU9cY?b+!3l%A6ExW1?(XjH z5@1MhcXxsZoj~x*aeL2q>Q>F{s;>FfYp;G}O?NNV)Ut@SRlavZ^nKUUU-k}6V>egD z)MO81xL4bEaFDNR4|FmCXv_>_6cINKpM;Oor(K)-o$uj7i%wxzg~;h59<`On;p);Z z{F%;;I$$IA_={T`giGceF}#^Yc<&<43`T`>lN3T91jC9LfkEG^DiMdhL9HR*br;=J zM@M3YqcTd&{L|6pM;ZOe<D84kNoEviK7_ZcG0pg;9^@RQ&Cxc<s`6WOAWC69<%*ZU zNFCm|Mlc|V0b`!wq-c)z^GIxR@PRGkQ4bR%^3%G{K`@m^FY)PQ)(Sgl3EQ)<K$mR3 z9-3mP5m~3A+j1sN#9J+~5z)$pp=OT<Gqv|(x;Sx+Ei}apQYPnoc%Q?y#NygGI_^10 z9M^73Yjm)$6$@UnG5C(kAC>dS9<;84p8RamJH3@;`4;R2^wn$t4p0XeoceMaW$E;p zAry`;Opbmj6n-F?_kwPbe13>L&H7@vm@Y)t_(3iOx*~ZN*1DWn+&E`2A@MG+sui)? z6}8%VXdKU^$1_t^GQBW&38c@YOGS_Aq#NSiZa2S<9>7P!vqg`xaSnlsZ$ECxc!N`( zJ4iQ%z(<5a56=mthK@Zdab5M9h@HO&;L&3UizLJ26<{jVd{C3Y$1<8Q3}ARDx*ah! zG7QNGKxb4x<-y<+u*2%Nc#$iI4at1An*&kR0FKGIw=G`+9tWB56x}^3!KqMQgEYs5 z$?qQoLIvY-V7IBG5%kRVi)rIKrV?PLiJspy%nw0JmlqplHIeMXuV(j>=+;7@x_8}b zz@g;VkV!ytns>Fqk2G4@OvbZYURwVTu^F)$KGX1?WTK{!7U00;iokFwCVCa=U*h7T zz}IDSa_*68Y^d#TN;PCCw8OmFfH^}|b~{6x$5h<cCW$my`R2l46^a;R?SKUBFTcNq z4gb2$*be*9<p%Xd;_6<9(}fAw8M6uZ+~<Jif#Tep0s-wvJB-DmF@`Q@7Ue$W2yO*g zxn~0kcR{6uN%be5Nr*aLrF)dqdNGswPrSWwIo;}2X0-$g^9Y-^<k7TB_p$OX88&U% zqiI#{jGDzsjp8l(6%-rEDU}-xxW{RY@-6x`qlSz0RUf;!E7!Sm8l_wGt45#mH5Mz~ zPc=Klrb?=dstlV?8x{ymO}^<A`n&6^P{yUq3U}Gt6lpS2!!`{oJG~tqF<*qzUP|q7 zZYQ~Qw{1$_>aN<sKbv)}sf!v~7SVMFzCZiXlQbJ?l5OH>PTLkqc*IgbP2VO3<4k-% z!GRTJ*5(WGEG0i5;kCWpj#$blE}k%+yg0uo?U#2Ap7w%d=|Oi%<_)uvY7o|?w{OHY z8(mxRqQ5YsJxcay!p=^<!?JT7OkW7=bQ%RjZbIU4PK|<Cq*vVE*>XkeraRqB`k%q! z3gFd|zqSYLhqddJWx=bt1O4y62PFRvhM*-Yu~)_Cav<8ITbH*!#a;?<i5$uSjL6_T zCy`(}N9kVsEtIz_M~o@Q`tbCKaEghfZrnagU+B~SY)o1c+LUtXO8%X{%hlJag43)N zQKm@iRXnP!eA6>cw=y=Ne4d@hl(<eb!g4S7x-Ts2{)@UPso?iw!`?d~2L?_-;$fO3 zeZcZZB=r&5kIb^vWWN&QD~GL@%}=j~ijs6BUnhyz8S9%AP55(kv4WswO>)wm(Q~{o z3=G`e9KZrv_t-y*7~L$KNeZRg8$LP_S*xyMu8h2mI?Jb;Oc*sxIch)UPD1Izkou%c zD2zBqX*OsxBjZ95!Rl>pei&&tXAq9lh^`!Z|6>Iw?s!mqdx7#jPrku#Ae<O$B2v;G z9^<;>PJ&VXcdVA+ZdbpYvvRfO>vwp_zNCw*Qz_4eQO>pjw$2WyZp=Fka2=^A&6V^e zkp@`BKuLn1IbU<W=bl!FyI}>pxqn$5rv9~`abMrQ5NU^A`x7?F;KtL1B+-pRl-Bt* zGIRSuvriV4Ju4-6Hc`iK;<~x2JSRN!h~;=w*E^+oU(V|)BqdbLy>U<Lcd`YMxrD8> z{B|O(G~RWxT5OITL;Z44wvfE{Qpjw>2A%$F3#R}-Y29;vWAiRzVyfWGq|uF>1J=d& zOPm2Fd4X1Ij>U0qn?~EzT$*~-A0nC8meXIGMhDePs3;{uws}_E>(>@zE9ZtS8iEY* zz;LLunx7th?A^--hE--viqxg!?GHjcK3RrY1Tf?fbC!@&&`k>zS8x^EEi1^o`0@ai z_BHOcW~M%ewu8e0zpHW}4u*ngJjsaAiZILw-SfHoON<)dAM5Gq6dkc%=&%Z`YVGmc z=b@>M@s0fI3{&5%waBbZT>Eh`+`i0h@Khipc)rolK|_QVOFGkTLdjT#{t0e{U5I)8 zWUtgId+lmV%wEG27_Fb*Xj03Nnkai$b++#@K17nM+G!|h4kGEs<{pb7_}N%p&cB(Z z=zi_f426hOz-8xLOT;}j5t)h31c3(?6)@0;1*otkw4`ktQ3xRE$;o+Q*+k1qInOH} zuXc_Y<7kQwqva<0Aj}gsbF6LXqW?Tll|AyQWX{A=BrGM;L<e)e0u)knGA;Dfz9cRC zS#6B{HdR-RalJZwpZ#4Q1Mewc$7uP2ziRxO{wDW*(N2<FU}dTVQ>xis>%SeY5+Zk) zcpu>tau}Dp9&wL?h&>V*Vfk&><WxOJJEbNvdMV_ogzhp;c6m^r4tq&BFCk|=*MTi( z=dFF%8qwN%pV#zhe?K6RqU4p=OhE3Sqc%oBA##7YmlT^9)d@!$jtQ}#spu(UznCNf z%N9FtUseWY;i}alL@#Qq?e;Ng5j;Y1P9SGNHJ~?Z(eE`coXCS)=5aX$<ys7D=xC6x z>lOLktR7HAVKlHD$F?*amQF*Azev3H(3sO(N92-7H21Y{DNOS))%ikINlYPWGDHl2 zV7qC|x2}k%z2UQ$DyB_L;hx}IN9!W`_K|K_FTq(|-CW@WZ*mcm_Vp^LB;ir(yeFTM zw)>AHEhAh;->UPG+Q}@_x(bVUvZ|S=l5|?@J{JEf?@$X?n)8N_gTGDzyIYyLa)M~5 z23kLl87`^m%hF5^<cHpW{bJ(nH2{X{%@%9L8W`3>3LSxt!RI<|Z*aKg<Mo4NeW&{5 zxx^<XKXA64nxM+|>66YR6Q`MyZQHkB%vlup5u7xwwFr7qV{U$9%Zhkh_{aGf$#R67 z#}AqrQk0}-x?<1OU+nY3^((LYouno8#3pva((cun%30LMeCbKS9O{f;=rO^9>Wseh z1URAd3`V19{hYzV`{9~{0t_<2uX{TZ3s<0Taqk7ky)G};gTmhK4RiPZjnQG|D3GWF zYs$Lb)fHKH|I^slT{}8)kBa2h8_K{ZfP1Dy1dB6*xa*>R=*rO*Io60C+0V%Jv~IRx zL31lh;c(j5gLT7W;&jiG<&*p!$>>uu2W=M<U-)^-qk*Fwp23hS>bM7V_f);??;rVY zbnLu=wap_M>#|kv&g;c4bW?ewf`8s277?}F%0G=S^)s@wie*K89@ktFPi}R3@}(7f z5zZ>4TQ%R~R?0jIyNiteuw6)ZlCRH|ZuY)^?7_87ly4M0>l(V}nv!nHuUarn9kkE> zb`N5?pyy~&ANvyT2S=~hYc~oiJZ9;I*trIb&p@<TNyi%C-Z5_Sp;S&W4L#!Uz{1=k zVyZt(iy$m-dOYqgSZ-f8znt4&znB~<>Jo~=EN?fy#2)s$IGv766eyUmIE0tq@J=?P zMNlpo;>cL9b;1fiekSr*&!X6}Bq~$XhPx0Rp%P$lQww=b1<P}9qSZF6OW@t0lXeZQ zrxmjc?tv)GuULoQ-DG91rDJA&J9bg8O}pC*m#vB$2{B5#!OO5BHQG-aqI|_w9rt0E z@Ev8N7JF^%ez`MZ98F@nHtF8oO8sH9tIB)5b<bhvTDUEYuer5Wxx1w$Z1ZvC6W;Yx zk=84{6|VIFDZ@PJ71@o+2Z=iiqt@_^F=WHNl&QeqT=@RckMkcFs#sF|J?>5{Sr*r{ z*M7%5PVRapX~;&!72|GQUy>`!x*~4avfxt9;FmnY`r_R0ukDSA-%Yn}3HZMfS^WC? zEw5;3xCwQcxIy9!(``v(`GsjZ6E199Z*JnJKO8Zz(W0N+vzmzyChnKV!{UUNa-OIq zjeK4MiNr3S6GfsW>793$fj?hGI2`{$_V?!e<q+`i=&2o8uLMJU>CyYEju00p{C*3i zL|g~A>_bbb7fa)Vi0)y3{3>zM>xj*GIzH5L$RW9a_{o5AY_SO@M%HVQ0m8z9t=GmK zcjcz>xE$GdK97mD7u!aNx!B-+^LvY^cUpq9uNi^626e^6u53Sc)G3elw)BYgm}It2 zB5h{EdradjKPo>FA#(8cI0MI{9DxG<z+w&TTfHpz+oqKn{vOBGd&L-w_`Q@|<PIhQ z-V!03r3N(txWcb3u|Fi?29m<~Nn!9GUfJ(RFP*9GaxUD`&ojgbsRm&o>oUi17jWqH z6My)4sLZYs)&O+l%`Uug*`VM%J6e5vx(=yJnl4tL#DEzvpltr|w+E*zkPPtv5vETO z_%`YzzVdsm%oT&qZkfD0zY)H;$+&RSK6=ppsIC3V=$68XGq_jzjHbf45LQl7u4!1N zZodB$GoOl}pgTWc*Ua<UitKvIjLx+eXeSR3w&s80KRK^cm#wCePHrsVA7cx7a?Spc z3#T?+$JUa0mVdMTLF+;qDkH37wL~z|3pHJ6YO6@rQbJLz16xETBuTnJ@Hs@4A6R~= zS=buHi*B8VsOUkHlGH=_B#e4(PS){J*){&UEN?_tcTgoB_r1oUhlx|LM^xdIRy6Z` z+YPjC>VBTakWZ+jePkTH<q^?$IClRoEF=w)PRlNe;?3g|ljom#ZR#qeoQ-uU>y+$w zYlemiPDn)CrbXrp%C46*OSQ6%pv{z;OjJh!{Q{w4<QYG|lPi@+QX+h_x~WBk(=O9Z z*=Kj@FIEBDQ$~BmJ+BGeOs{XP+dhs8cQdHkOZ9`Hwj~h#S>zL+ra+huTrfHBznl?Y zH$3tp^houdqhvk6bD=$lT4TxXO*>}NB)kn$trPz=$fDsdbzaUGH1-IGX6))b3#?^1 zMc3k4L%05Y<zXNeZ|bGMs_(4_+f<_TAc}elcRm*SoNf&Db=&+PiR&j@NFRqOTM7rE z*VjGp3iN|vx4}IG6Y819dHoE|XtwMD{W|mhfKj-++zSRb*(i0KczH4INvI;Tj3rgD zv}sUu8Pg5<IqB1@tU7f^ytairUl?9IiZAgZ?9Hx<eY*(x*t1t2{+B72vH;|34@P0? zvz3}~yHqGWZ(b6hSCd|6Cz7Op4JYlc?oM$*mms@ZA}fMHs;jJVfBYhO$Tfzmow;U# ze8w;SQx9e3!=OScbE^W48})AC{QiBHDS%B<)KCEMYNTSTc=EITyI%*s0kVD(1YhU3 z&NL427|yvYt`yE$G^}6q*#aGy(87aQ$zwVXe_CJ6K`2?p=qme8a&w6vYxo6)H$w8J zRAMY!rY{Q9o)H=dr%feTU!pua$K`J3N&0x7W_On@<(fZnp83n8TXg;afqCfoDT$8m zpA_Vh_Z?wmaJc9yj2c$9Ri_z3pBQs;+dT0IaU8#j)4Y<Jn|<Bg7(;zbPVhP2?FI4? z%p#xQe*ckFZq1q@9Fs{n3|<o2z1QW+ts3`a&>igUd$Dvw&BF$}&XBLY1{z|Z5cEoz z>odVn4(bGY8$?|?@(ykrvC|;U|N5XSYAv3Cj%Vh>Q^n&MyKhF7*r9yM`?iI?eYR|W zI^3S^wL9JCQ}d2|c@>Wz?kTSnUHlWW;3iiC#dvNd#_AR9)c5!@O<Y-)$#1#VeD5Xv z{0WqXZ8*toQWFp#zno0(BVNfT7NSXrU;i*zu}DS)ePPF+wM6>Dq`osn&p>Z;8`>|D zKRIhe-ZXDC6C9H;cUefF8_6q~(f7gX**07CEF<B;Bm*<%FdNJz4}Ty2d+7t^%je1W zH6HT9{9zUS<8>;b$S>{K-*HseUm{QLti_e#tQWm*uGB~M$y62nxb!jPV#KLRHK;5Q z#F`#GVlOFAcDMzi4_&ZfvA}6aBD>FDDXG=e!lot5%i7;=1~`Q0(lCQl&J~L(6(*d` zPyc=~$B4QqB3|>rZF2_u%o&nTQ?I=!o82#~lzccZxw0+!EqgFAsG!>mD7xS@ZKT9$ zhvxQ~yjUDZ;4@8(AwG3;V*mt$W$7j|q|&vW+bk6t(&a~{zG%noTI%|jvvn9qZE5h5 zvb?&ezeG%cdUkh7QU(sCC0l>ya^}Q%%@_EwOau5+{q3uQ&qA0o9EW6>;j4AHRM!H- zN#|b-E^vG{#|~7L9|Dd@kat2uoO;DylmJcG1WI$(FQ!b{%Ov5U*o+hnPQwSt{a0N$ zQqRH_kW}Mm9cT@VTBT|N2;`w)naTQ>cQp+x$s}Kge>C}1#2M@rr{N;W1%E=baC${l z3HmCM8_KM<0U-0s<F^`})s*kW%t8CTW&MGSE95Cfe9}_c6m#X7@}v4iQKC(;SgG`Y zkLW#MA9BD)m4M?qf7f0eE>10)rqcaIF4_Dsz-pr&4mLuwBD^8ft??ty;wxKK7Lca$ z%Znr7QDKD+?8ReS`Ji5)@@>`NhwN^w5gY7DLN)eXWdeQr0RK`5E0=tK=Hqv-Se&(# zdlx2yN@h%0RBElSB#6UkUbje4_+m@m7HhCHVOjm<e2YVh<mo)$70UptboFFBJI%Ry z4&RSy@Kmr4=o4T}Ii;MU$;3;8CbZYZ@dkj=2KQ2mnD&V9&uh~-h5jegAIRkES0?RR zW$dpl707*~8M6iS6C*7YY7VB?w8*i*j=0U3UXX|`9?0eTN8}{7n4K}fu}wO5v{6`r z*B=P@MU;>pPGMD&MESWs#jE9LBR4F!9B3<aEF{*H>rwdTDTwMK`FsiwFI171w`U5) zE?JMb<W*y6mXM3;Tq`=Cu}#2=bO}cfovG4hb$@%jKaETu34#Na%t<26KSwO?&vZd_ zHmOLt(IPaCZVF$SJoaLZAs`Dd7$QDsBdZ!wl+#>0L}8&~Ct5L7VZ(BZwTb}d@Dj6J zF;4Nan}OmPFK6Jg={$M=*zCd<EYy*`Eh&Qdq?cSkREyMR$4-F<Qb>mL1E2QrcxkFt z<u^LmEZEUvtMMOfpL_3`o5Ct6{Uy)^sLwRK*D8$j2*HkL#)k-x#Oz%ySVHjNa5c5K zX`I0Imc)Ln<uS~Z*j-mi!-Pw2px0iK(~yFde4l69)IDM*0vq4RHV~9EYJmBUr3JJo z_!x{cC<itVOODLyS@@FC)foMOVLmD3qR>W9g7QZgu5X74u~xSX&II>$_^wbT%J~Ua zbW!A8df_wtUiz+3t<~0o@a&7`Bj-<_Oqs`?+lE_cX6;AUpJA&;RwA?4&F^hRX8bCj zOK!DSX;;fujcoI*Mc#Jo#ms-vE4a1&DPrUIQ^Y#-mUnf_#=q>@^5;d%YvHr@&tB`- zv|IGm!&N%_=3AnlZL9uEFKUmp_QAK@Khax6W_xOVB_2y}hgRE~MP_;mQD4CVgU*$- zWe6OkgNlMVnf6E1D%3?%`3y-m3hm0~xPvO5-{kOh@}M-@gI>ZhZXNfOLKfqtYS5Co z!p5Z4Ca?;2lL(|GZZ)znF^6|Gh${Bx8PW%H-RB1>RnLpCkz`szXPRY9aTr>2m=ZtE zK{9|HGjt@U{N)jL8i1`rE?Jy>&8mS?1pWpxxmd)N66CD=xZ0T#$;GclE1ii4J1{r> zP(*^;x$zE2VUtdzLbxjxDlDc+48FJH?269mETXurmby067!~x!w@z^1=9R<h)WzIK zZyz=zifC@>GOwU|Wy^DDHIFk?L-Zn3gI5#A1alY^%D$C75GN~#NyH>64_Lk$$(4~D z7fUB5>PBm2sYs(F#1d4WOqQlC(`U9@#=)KNvn<PJ>e3fv>C(v3Np2l9-PCxjCda-W zY!Sf^XVFh9Bz?jqE%!z6Obuu)`WhXhf9+Ip|DM^y4rmQhqoyWMLgYfu8j%Q3sR!;M zrNQ?Gbp`7wRfARplruG}MivBx_iqA4zZx+r)-f%JUpr;$S$&f1X~uyWV#PWx{Ms%X zVfdW}7oS+e1TuUCni+B$ceTxS&s&(LIHB<!m=`&d_}wZ{5m`X;XBffA{%JVY#Eb=l zX;X+*mr0i{F0~nfKXe%X*)Kgg?is|{L}j|oBre>;;$2h*z4w&mo)VF%*1<TAIx`&6 z^}8{2dPbCmo+=5i8j)=oPrQtqc$uAZg`WD>HY!!SLQ3alxEBO2B1N84iI2JiKFTv# z(XNqq-G$VIhqxE8QVJX_bemC;sdfgM-Dx<?1MS70tc;m!?bV)ZvJONaMO<NRVA;8k zKhQ(o1S<~IDkgZUb%yQVK0AuNeo;FO`sOM&_T9L)+<co&<w=r<R5i5k5~>X@j6w~& zgB~(Ww=i{BN$x3r9G(7YdvK?wuiu;b%Pz-<iTcgU`t6|plM~EEMpCVa;r>i|y>H&e zh^GmnSN@|Xv+=JUg9MPPQ@XQ4YZVdRG2Cj7;+6s>?+&f@ucK9+)O9lk2^r#oDOM&d zINWx}J);YqwJufgkTR*7MLB0Hd{?PvxI2`le@!BvGc{IX4@cfJ?}o99JLHgoB&Jb7 ze5heJKucKWeN8JF<dxoU)&}{djs9hG?NsTb8@SoUKT21k(HLxQ3KAF*w~7P94=^n> zIdz&2G5~uL;p8Wq4m%Rz$dV`Pllo*xx@cFA;x>7Z&n)4su=DdOzhlk0D33(KcpLcv zB;Yn=ue%UF%EZO(;tOP7S1uUZGnZ8ueUn{r$%)#0kYGQdUb73@kMtaa@rIDnr0$_k zXD;J?2TL7nq=);9az0eN;AE#wH#I6=OuT~ZXq;;~;mLS(-mDGQ;HO{8Rj*`{yKu8^ z81Pwe=B?*FDyPAfXk9K5w$|OWxgTne<5W^g<>D(JeF;r|ZDIe8pWB)<?4|X*<+JCy zQu%f9S^dM)WA;2Q?q$UboF&qPy#;4<MuPI_&8<KQS#*rDL)sn2=Wpa$G19^9Mo2;T z;+MiHABD$u4*0hv$HjAllp~2nc7F)8m&?90*hiz9Q(W+SFl#=78jeJ^aS%Bw<iMqP zH6j1X>YB5AJ(9V0^PN0CZw~4{ViO2-p_L2G6wohi)2CBxnxdxwS5kark{xA>ax}{I z^o_-tnF<%~>XAY6r?~1&*f2OjHl9!kE+4VgAljN0@M;j<d*53^;Ut!S!LKDDrDWmT zo{VzRAwgBvIb0}!DfS9YzR$mJ_PMqT-RDpkqkL+sm+0bo*aJ39v0&e<+2eD9K!5r< z(&w@pCS<tlGz9QhWM3`K>G9lyZgvWK6$MyWSh8Vng2a2<Pe~JEh^4H-vi_Izb98Vb z)A-QmCu}h>)%jfkZ4er0%iIY-9_pM>gJ*4++dv2duhWAD3}eLm0AgcND5P25(h^rn zdCZmH^OqhWtOA-I918Lk$a3uyj+UCryYH6^C!o|7Om5GLv&{<b6N2=-2?(3qVdW{I zGIt6I$uW>8ERfl>d0m-Zb&20P>T%Yf;i_`}98EtjeLJVvH%=J0=?^xu;nI>&q2Q!W zj1aV`-eoL{zGA|cTyFt-BmMxSGR@wQeot}~E`mma`>nsQ4*A6$ekwv2C&_gtOP7Ay zQ#w^QubJ+++C6e1QgaWaog!+=mgF_n&~hb}!XWikTK17z$gs;<+tj`Uqgjj)FVRGa zx+%5vz+{s-_g!ALHvRXi5>^@8<5sJ2OOHeHUCoJIO(5S@S-PgvOY$vy-s~m5U@?=4 z9-2^cw=i9@DJm~drx+yz9gNlZHQ|1DpX7eZO?YF}HA6djqV|N_HX$g%LffJV5sBlY zf5-?pP2AsMv__M3Vo~<OO1g(Elgpd>F0x!`F|+vhRX79KDgON|BJ#&32iJOGzY~w4 zqQ0WI$brM)-Ba+)lXF9&wCgfA3nv(6jC;Lyh#N73#L&=N;Ir3+^F5Al?O53fGAtLR zxqM^!4;U`;fJ_bS<5P$aDhSaGie?;%Um{>V_x;35f~D9p20=Nc<O718MaK%L_v#5O zyjA>3+$)D@$$NxGST+hqNFUQ2G}C%CKN<Ch)m%K(*4DFHn_;&o4619Ncw*L_MN~WJ zO(H2Y0SJ<~9HC=|579?S%oqvHtoJGBbVU$0wiw>}X`p<@+nx@GQ>8nBhV9e=P%390 z6_FPsAtl7j7uP2c64I^yq*4%Ol8E_^M)>o4is^mIcp}jEu5s{F+Epf(j#KDDT|qbf znQl3*726%rL3q1`Ep<yjL7}%3&S^e?<g4KbcqQb?IS9wtwDz#8vzbNHkr76>g|SYq zf^81_`n|h%q}0_-YW-pgD6i)p3^wH0qCN~^^C2A941SVn_b0UCd}cj57Qylbh`$sm zkIv~#F!uV;dMCT$TrO|)RpG=Def})zh(L<zE<+dBE|RA>BjXcx_<<lUUHY0fi{Der zIOA=H#Gx@s6=DVZZ6JHB_o;Rx2WODBw?PJbijwB?hL8~d@m5gfcl{giXcE+XaOrZp zZ6PMn?N2>CTpub6f{2w6>uVTP+ZoD<8YjDb8N3D8Vc6SG97Ub-@ddDcHC=2Pi%#b6 z>VoyWshiW4z(5700S3EI9eVi{dCyGRcm`)`iHr4(Y?O;jJV!Zi-2xs?JX8D!EgqLh zX$7f_N{Fx;Z^13n0F(?8`)>4j{J2q_g#0f(ES{^>C(l&OG#4~oQ;RR+UG$Dl3F0mJ z_raIzly&Q#0bhT5NgK3U6#nong7K9H`xb#cx0ueZdUVGc&&R-}&1O74uRJn&qphS~ z9kKK_BH`vd^!hUt!*RxuY^eCdFZ`+|D`N+d)08hDAQ=+F@g{<^&>UTlU5+m3^CLh^ z2az3C=*SVf+&C%7Z95XRq0NlRl~Qq`(Ol;t^CjKpOD>2@05~suv$A^}nXU$42~=Yi zE@ew=62|#(+7&j1%vN`|viB+G$j*%V&UEy<!nC1~EmeZG$WpNCCOaWjCygZXtg}P$ zERn#{a!{%V)O|8<(8seZ7+t^|5Aa1jGhuWA{~^2^MS6-k%qlr?djbgKPZ^I&o3qb- zQJnOPIXSYlR*CaW)?D61La0M-#$p0bi}_0wj3DN0XhJN%ve{nZ(=!L6f4A1KoMkKa zusg%Ek_G!Z!*bHuC^8+K#1V9t+Pp}P;>H|7$lD#~{lVFVHYIHA`zzv=*qW&}rBXHT z7Dhj1H9l^=P2F)xTn6m7`?f)R;*d-VnL6TElOz`+>$Xeg9iYfZqW=v1wbqADd~7No z3FnuwL#Sv2!h^si)|7z4lrq}m;F#145zhNM3eLF9x+*O4n3%09E{YMmL1ggffT>&& zK9Bl|IU2;?KvTH9p-m9c4{X>ebGhKJDoRylV-86C78fM0?`fYxKSTdM8~h9s%B%gg zo@h6m@nkcQCMuDrVDP{^BvaTkFLlE_Y>{D&4dC(ah&nJMqYT5NNrX_rm<T!-eVvc~ z2}2$e`WpI9#bovBINe<^T<9i+g+(}Dy8{Xbr@}l<m!xmd*WZz?`=s1`fJFjxc~DPr z<ZR&@CeC70F(Go%U5m!+F~9T?D$71B;Ew;4diRv?%q;FJni6kaZseLd`OudU7LNfv z3$ezso)}_H^L8uzs=A)lnw1+yo36TW=^iQ6A7~g8*F6HageX_m8NcSQXf*a{=Y_t~ zvonraQ7;KWp{l*G@%8(Jd>E^x+pL<`b^mzP%Ge2LX56%m+$?{$gyRVBi?LMTSvPmp zx%*X2LXa2dLdlcZOXF%2_&lsXuh6QU@3@6ccHN3~y{=VWW|s#D+j!r1UXr*~u`d!f z<|+j+ENn|gQYoYG{=|`o%AQx(GkwT-;Oakq7{v;{2j1IBejO-{iU-qj$(+h5ZU&~W zF-8%kIk6qw_U{gU6a3!Dl2W2>v{UGI@|oqqq*KnRSpFI^B30eVS10|_*myTAMD9Hv zjlWL%wQ&JajhGMW<4sr&mwvO6&R7LQ-6wXAv*2GIP?=5ANmVV$qVIU_D{uD<QY^es zA19B*y_P*>@K?ipxjIz|Zp(_07mWRRGBc5i3U24%6okFp$a4c`LJ)<`-HZ!WNKgkh z*fUo-ijXzdW5BtxGwTt@TpSs|%%L<aQ7W>~Qa2F8YX;C?i6M+RhwrF3b~4e_(!sqH zG1oFu7V}Hx^aTL}(S)-5eH@Mv(y_1b!i{W#kc#oZ({JK-;w6r(q04=*Y6LSfY5Y$5 zPPE8&`GS=eF&sj5`HIHJb=j}1B`C+D#D3ZLziL^K)Nea}FK}d>^}skZCdpk<yI^D2 zJ|wownX6q<yCy*|)CsU<>d7irZ7RRdzFvML&YL~#4}%?H8F>w~P_dv=@6{{j5uBo3 zs{PGZ?3-SmD3zm98+5!yOXF(k62rz?nl<4bEQCYpN!M<*d?(%2bS&VH_nmFZig2We zY#97J!!;>zwl~v$Sq|`!y{1uz9EWn4{5`i`U01FVp`##3tY)@eJxr!yQs&M873qVZ zf%~O>4C(xP{rmYHG<6##R~Al2^P-gp<nwV1mk#U>lo?~JpAmcoO(Vt{fB*%_RCo1* zs?wY7fIb6^M^mO7Vadh<wlFb(n=OJK*JcPeq<R^2kryu({XC-+$oY1ddgjZFP4jI3 zkFk;4;QZAUuC6cK+5>O32yC)m@5Li6`P6Z)d^X%Z!k4(_d{j8r6s}9WiUk*^Ps&Ot zH)5u-kPPZBTz6JBT+F|m)m6z(%Rrc&)B4fMCP?2^HAA_Ve<bLs)My|iH-nM%$0~*e zzXzc;nmw61NrF&sLwb55(U0ofQ<{%}^!xB!Q*4SHk~BeF>>ZT`u9|N}CuxO>62xNX zjiT5<QMoD=mebfkg9P0n%cT$)sg!+2DE)&U-^8Vi_D)i}lXwj$oJeNk8Eux+agy;{ z@nq30-*x%&RchCd`FLY8f6|JM#ry8)nzNu5=n@Jvv%ukoqk!eb)f*^cRZ^GY&L_ix zU9#Oeh>%z<gw>o}pXAb}pQfzY+O<YS7AifGDwI(D!u|ffshG)>oIb5=`7y`hzDEn} z8hMrZ$z*~HA|LHQ!-Yb+WZB0dYm^2#BM+)gG`X<z$o{J$SX*Pn@n>r##xu9s=S<y( zog!*8;h+y<YL9RG1QaAYxxQP(!M~mA$^F>_pf_}ybOEW~0QKHJo!i~{vXKue7J}vW zt<e_GmSD;a%M7d5rDeG$StPd(tB2y_voQc2z7i|>E6c61_%s;cT{5%nAmuZRaSey~ zMl~G=_&UT%>8;K3C<ishHee}>EFipRVv-8kUgr!o7X(fUH-y%m9Yl2u4}_LRKg%8| z{mIfeT;GWfG=Q6;Mc(ZF#(kBJ^1f1$BLS3PM0;b5p&V*2InMjvK(m|N;szCMMoAv~ zxL77-Z5@MqoP2qtU4|1XsRgS+O5bWya1qzOeW8U_$Z!+{@-5n_oqd>Id3o>tUCx9! zWBUHMY0?Uybj4GP)Ay4D_gsGT!nozPj}b3jP)%d(40O^<`6iW>MFBD-cqBlIs>u}! zRGpUCdOsPG$D`1ajEUW`{fbgYS*|SW3GXtmA@)c}5k$!-SrC0$?}~MP`IRFv%56n< zdQ!q@Pcl#M($oY_B*!Z$ejCzLRs+%u4&{C;Qlr{HV}y0J*eg<kzxouDp0V1JJxFBQ zXvJQv<1)t<iuZ0b<y=*nGMTKG&b?|yC_!&o=D0>VE8SXPLRg`LW!SU(I0*qKNOS(f zeHXQF5u-;Nl`Ke=YTVKBtaKA_pQEn&?ZDc@@=>XqQS;D-{Z`i}?qcbc`Ccv$0ZBS! zxoBQ;j)<9C&9!-6a$-gu`lk1|N4EE{N5=)c<y7SG<tnD|5%}l~cW0XKC8D?N5!9xm zbXdwWr^kM)ND=1983yyI$crb6Yid^+y^DGLoTaN^WIZ-?K{eOdw=QhSe*E;A)O{^u zBZ$;pAbmrEP}YK*0C~1aqxeI6Q`nT@_+b&LdsoKB9;y4U@pC!!E}`*rYxJ(K<LA2Q zUB2Vz{8cGf6U)<rCOSQURJ$wDZs_Y<jb8pbC!<?8>oa$+0T+f1H$)j7-jw6p&cl!J zU3K1bb3atu#@2FKlr~rlnV{zi6R**t8t%jrmiG!NH}GSQk6oJAO4`fD8XVFZgbZUn zx}r~yT?*GqB%{CPsR=vQ^XC+PH>v{zyO=Z3I@(IC9A{zFFeV;>mBu{CmL}+3{NoN- zHEw{ifXUl*-fYF2P=BeLi|^+bP^IBQ^T`!%HOaFzrNw3h^z}ows3{n|W*_XeL}Mb( zr`~VUJ<z1}tdux9<3_8AV2(7WWJDT2XcFItHfO4<xy!rIpW-;0GZx^M%&oUP%gZT< zRUP>kUFsS>>7$xOB&|JYzVes7b~jP+5VJQ=UwKtc?KoK(a?!7y`~)`9`C1h`FYOF_ zNLhWeSt%FqNuT=AOcSZ8l3-IbfOXCK;ahH$@{Z|jv}Jg4+N7ls>@}--KXpyP7K##) zGE!Ox1qrj)Q0gkOjJ9HQ?N|K8wl2k}4r^L#!zL0IQiUI`R-us+VqDn2O<Xwiuj~1X zFP?lqS@A%?J`IygrlEqz69%R*JI<*Qlipey&2}nQ$FWq)9Z-Y_BINnv)#BYZdYy6M zO|x+B9xJJu{|pbx8Vc<BQsnh)><Zb%fmug$GECpT4>oeQ;h_<qzpEDx%b;L}PwNxD zLT)mLuy56+@U6HNu3`5C8F0s^rUbQ)<E0|oM%aZ&KQKACPjT?cL@N{9b6VPVG(~Hv z3m)hpoeL(EI#9dd-eYGuX9|wvTcgqVY%53YJVnbogtA>DDW&O)8fC1^S@+##niUOD zDbKYHjN2E;YTRM{{z(3cOS{`F8?8*xWg|_&J(iymhSxNF&BuiKb9zJN(US`NmF2CH z@8|>zerEZK83w&g#s`zxXY2$+3_H~4>;#n!gPz<MH@S^C7sMj8Kl@EE83C<Kl+y~b zW}Ax-dJ^cmE!Yk~-6>S)-JfOsRv8oK0vlG9PhtQ~vi+eQ(gCQy&C?Ej4)j@^KJNm< zvpkbme*jUE?wS(k29#|Rc%pp@NZZEsMBNN%%SXnch5@tZzl(<5!(zgc#{yp=D)z(Z zs3-!kXsF#3(xY$a0SxJIr9tX!(8+-r0K9b6gMbb|?#KQo7+=V=dW^-W_TT|LL<OfF z942T;RWt@s>T;-sd^jff765EHG-Hjr7WGjTHki9!4|&m<{?vIH;%(C!fO8qD6=)9- zScY^58UlFB;eGl()lV@Lkc(|iQF8&I4f@5Rf+g}1iQ#1W)SOU=;c5HiFkr|r;4qy= z84CEt9Ob6<y@t!2{}v7i7NEj5hZVGT-^XdR+CVfXlJ)w~FPvh%DfwHW3nZiuhQ#NA zkNe1iFge?po{D#Hg0NG4u9r~VFm-*lmvG&%hWT_e6xND`pR&v2l+4rzcT}MUgAf4} z=`b`w{~iK+R4+7SFBWvKT!QN_ocpi<avc~veeq9FsX%iwh*2@v^5#&3lyYs_lebx; z*>nWs^34aP0C06s7Xw-VBs%Ck0WkpVWt7%{8UWEUntMPF0Dsv&MQ4iYJaA#tA>xq< zi)a{$D-#Pn9Sa@!pHZm#?o>!BH?QI>H^J8&pyfoE4QUDDcOo@_zz0z~5j5+*wM7>1 z9F?#c+95oGSOTFsVXs5Afk>S2*P-uf3IqWKg8Kz-nFWFz5?*$p*D!yJMRVvigPMO% zd2ir?{`7zZqrA8;d?ZMR4J|p~3ILUkKod9uKuSj{4Lr`r8JBI8rBk3l<V^CGO^4mH zV}l=uz3KZ>55WWz)n`%<!vs4f3XvRyq-vd_IzXj)m#k`9YV1|VHGeDr4M`PV3-}Sh ztpmNNM_p}-wRo3Y4@$0YNDEf_N@WM9-=|fNY6maSr(6|mDEDwq^;I|`q#Hi$E8x8q z8RIkpT0qb?h80v82-69N7<veV=!69$Hq1btT`ZFb)-Vv~L}Sj!&4#n=({)12hR5%d zazZqKv)@K)M_L7OPeaxQ5oyu$CV}<k9_)2CAU!&oefZXCjkd{;VB-2rFaI8mKWE0^ zTQhPzZ|$e)s6?+LQzs_ngMv7vx9_(Gm}q$HK1nu2Mdj9DVd@O=yi8(Vu(id}oHG-* zM4oZzr_en`Hso<Q*FFQbDZ+;*GheaB9t^FhzFjlC2Q1>hrwYu+e8TyFANhFI5T60m zI<Q(n+yG`B_{AVptqLMjb>7bk8`0nJ>T&I$*Fb3X*k@2(VnWN%w?N<gKo;1TZ3sRD zv_KlCcf<|yLWD`))%qt_pR9#2X=`Z3FoH$jN%qlCqv(k;@}W)!G;L>pJh@MwN8V6p zy-oC{kDVXOM>T-d787WP`_T8<33?r-t<Ro;RI9=x>6g#QgKuLYMSqF=?q#?<qUYOL z0zv305b`BfH&h*n=n~H@un;HNNEKxe=$!xeSiothGc}}V7OE)BlzppwOA_M-x^Kob z>97_-`~a48_=BLoES4Z@0KE<zR6en0fV?Vfr5LIXnhoerX`yG@$RK5uQ&<<cpjRI| zWF~}0)F)k!_|HjEk9-FA3xK|id>aUdg!G0Le}>QMS@g0a8Fg@x!3jHYY!LP~6#@_g zAqasFHd74Q3bqVH;DnbAeF%benqk;ckM!f0a@@nJ-FCul4zkUMHh|~tQ*uIHhx6z& zbV6Tm3%w<i@=q`HxSl56xqK%GsRH7>B<_Y_1u<R{c0)Rf;nTV3f0k+cbhbFu*>N5& zf%p!cCLjWUm5x#xPyrxJN0SGk@Gkj}Ib|bS=t|`NTs&<5)gid=#WxO*+$Ua-zyznz zr(TZ&Bg$V6k&=(38AMo5JP*XxLj3IDTcU3?KwUj(jH-jY82AeSy^O#UH~~OjMrsZG z34mWlbPwFfCq0Ao0SQlwiUmnKz1{pDCYr>J7q;<$+2)r#wEGV2uWJG!SPUi`Y9b%D z8929%!iPK=c(V=Fj=&N)mOsJpGg80zWO1#d*_O{O;B*_V9nm3hZ5yo}`3)y=pga-Y zDC1veUiE#+;W<X%e~j8a<Csxiss{SMVgrOZfF?b~hMBwKAzRbefq|8~!eI3aF@8V@ z08<AgHJ}7QfDquK3dtabTMow!;4g<|6eZC?)Y7G{RbE&sKCv}!iB&L+`vzB!X$O@B z`s3^eLd9$cd|U--KC4!4!c=`TSj8lc1)3&n{fR)uFrbVvApc65&D`^x6%Q+#&&1V} zLCAvpQG5naX<|g}P_;mJC+3u1<_}KYXbu5c+j#A$=K)>Yr0pt+eG=1%q=7@*5!`P_ zpMF_ZJh<w7g<$nU`LuJnit-cH6S-4VjZhw)4$c(B_ur_(I`!SS{G4Uku`7U^Hztjg z_<yH=Nlm1j{zOSlgRDOCV#e$n3IHgVVR(X=0gTIVtwAIJ+GSYxAn$tkGuTH_=m>~^ z27tjct4u#8C6P(f&ro4WWC-G9C=}Z`R&f6d4Yz69&0|LF@sb1tX*?#<-#B6-T@V6Y zjMfu|G>CSa-V-i22!ET}6ZTUO?>6%hl+-bu%%2s5=)a`>aq6;?!ctk;X~{8@JEs#J zG$)9Ojc6Rw6(q>^o(TdOL{a~a2~q*XfdTO&A4(I*po(hK=c0<N74V@RtGaQ}>~m)i zjiJ$)X(L+L|DE=LlzI_E$7ne|fc)Rk4^jri?nImoK`S;*s7_~yUUrH}I!jOd?`t3P zpI-a~p|?z@pO9fMNTN658?zJ9I;00k$SJS(52kLOa{0l~KdH9&2Ue#O-KZ!5)Bu3# z9sJvd1U5)Tn4~^4Hdsa2xjr{lB#QtJHtcbzCJ-?j{y6jv2&x`~X|(>knR93lm4s0y z$fOY^41x|GHy=wg&_)$HHAt@>>VJrX#q_h$X%EYzU-hfsOq=k--jF{!ACnjY9z;G3 zgBC=&O~(iKABfi?|8`+c-pKTnOU0oRA394w)HYT-%4#3(A0gfp!w=!4>i`8WEBRxX zI7Rw65cVa`f55$BGQoeqeV-;9^Bh*uafY{bsW4?yTZdCRqD9~e04*K)An+alse^zQ zH~>J@K}rq$>#TK4G%ZiKGag)Y7J#@0dL(F!AclIvYDinqhk8;w2qF+oJ%JshK8UBD z<P1WfM%{g&`0PAY-7%q&Ha7Xs65t|#hT_Mccu%*4%lssa^B1KcxQOpf2~6z$Sql6c z?AfB;8<hG9YxF`?MG1lx`2I}`yk$SELy*8WOFR6z#?jwYz;}jU)}=2@8vbZi_y#8m z8Yh1T{*$0<xc>rt&2dsE(Zb%k?|o^iEq)oRTi)%cA8zygJrAIwKv?zvDU(A!+8Dm~ zHGl7mTm2XEA2O{c4f{VAJ}fLD2+|3I7)oiIjSuP#^|tx=AeDh+2!U+j0UG%P|4@at zk&|~btd}^3FlltyUN);t6&-(uJ=FgIeJa?$h+~vLatXhrtRep;<sbCaIXmjOxiudA zjBQH$t^dS%kM3m_aFb&X*Yo4wo&Vw#e0@qSNXKs;2URA3JsTCe%V}j6n6ZC~!@T0e zUy(phLt{a^?tDyL1N!twek<#H!;`g~%n#XhFjajv^>Ed&r+wbn@c-l*e29DB#Nj{d zfJi7S82COhCj_p*Z`%lbNU?!?i}Tq>37JVo*_nTiijIjV5?5A*^<O>It-R8_WA}V^ zQ}ID}Mkew$MY~z)!QcIZuEyMqcJHQkFJx7gAZ}ave86AwV-8o-M)WVRsA<#3dAUw_ z64mpdkSkMH4iWf<jcL>7BV~nto*bg=?~n%rvDiozA+SMIYy^ss8Xzt<l4Kx3KFT;u zQ{R7=%ZQbt+~tsCV)h~mm0IAr(}!b5<Ce<z7d!p`e}Vv;4<$CBXgi(TV(9Uh_TiY; zs~X7nAA*qhe<A~6(Ib=eTYqxWO!gBlQ!s)coJya@C5j+CYoEd;Qa7AqpZ+v#<U>($ zfC(F3G&D6pFdc#hC<ow3hbjeH13u8fa*7h9uWYSK%oycu80F374Y;tN{h?F%>(pxs zmTs4{!Aqx}M~u$@tcU-N3Cp<m4WEB)G{nDo%D?F0YB5Sa2(*7SOx5IhXLaz8r{ttA z@525-4gL!wFq9Pa`hD&73sUI#UySfqGHjG*BFv8?5S@*L&wmQ|7bW}#ji`$7B>=cT zW<ml$1ZWoWp%JoL3IAQ+tYNIg0K{f#)%S+KtHFG<>VH}R^^kTjS$!7uuy!r-n>?ah zlhhXhzm!>g@zJ`Jj(_o;|49%wQnLPcVhHDkD}y_l5QJkk@Mc6SQ#!VE68vXL_@7q5 z34XmrzK2(|cZynMJMsPQw6(ln@ZP^$q(7TyE3y;ve~<&59+e7lxe8&qO6WpzpyIVA z@n1aSpLOB?@B%gL<pkxd@5`6^ZD8ZXatMO{mvKft0g=9S&W1)^e0TP~oEL2#z3+sK z@4M+t@V9?1sr-u~{CgSY59$UGyr4gcfri1~=c3P>qR+y*fnkD!Iedape@u&as#^bR zxu4xtbAmSK4tvU`f)<|cbeQnFhi*QT$G(}tJI`Dekn<m#p}%@$;Aeo)Th&C*wCPyL z_r~V|Psjd8!-~bAg+T1T!C)uM-|P?_%3rHV^0<76Kr)zqGLx8ziaYSZ9T-oi<$5`L zaOYnQ!5_YT*t=@=i{3I3PZj@R>Ng|!Usivks>MGdFx<Gvs{2sP<m-Z$!~+}lH#ejT zX93g&@T5Z@0KEXhIuLk3DFD09pDQMS6s_D6ZL{N)`bww;0~=JdF)?Nx$df+#dZcPN z+djQ|v}*YO!v*S^&c&BpOwAG*e-0ShCqoMp{VVpp<3m#p`2W+ve^&zf8kb}W{^=zi z*UKXPJM5lNxj@Tpeox3xK<#aAPv}h`iz*kw4;Y-F1^_V~<Q@oy4O0;+353Ijs|Y<O zMxV|;{Grz|Uf4BW7+I?czTw>j$oMbz_v`q77s5K!OQ^HjXtUZN8}77jxUm0PEBbrN zYgs?SAIAiNp$%rmi2qUt_=ghyPyhd)aKEVY8D+K(eb(_(XXE?MKm3n&K-k-5MgN}h zHR_uvvs-AhUG2$*b}Z&7vHv0o{!ImQuB{f1E?Qcix;A=6jV5!T5B;GY{F@5yXQ*J& zH_E7~+fCHk`z!1J1M^_l`rPZG&tSUx98}S71HNvkTN%nea7f4@#pbh8-I+JBQrWkf zQ`B(IuJOG6kxFE3nP*qLbEm7PmFk=;j7s13ML?Z=m0IpxC2!s$gzFBzLs$JpAD;RC zZjszi(x^@|GD2DI<$W`wgEHLDb&MfXKdoSWUn<|Hy3Rw(8Q`ab3?cW@@00V(8Q)N5 z0{Ju}T!2)(4Id$!th^qW^j>JAQeQ_H+%k~OTSB=CLbdS6X6*Ri%Sly8AphJ@d*=n& zfN;a+oUr=>4?1vrjqmYmJB-to5bhA%C8&#}WQ`K;dTrE+|KVThtu+Qzxw1=wb@;;6 zpprA2xV*Z3S<)dWV7|lK+=cd&`NyZFg&&hKp~p#H;r2KtYp<H&_7}nJfj!dKMh9X} z7jn>6pWiR5?fSYO^cVhgb=FN?)^Puotn4LYYvZ{%eLpRJC@?ef+qM-;r%m%Wv6q@z zlrx`@0HB?U!-H6Jic{{L7qx{u+s((BXguU#!&#QbeMP^h{q0z-A~uC<8E_8HXxfBU zycBXqdH>Kx6nrRGMd+dsrw5-XX5&?s403?A_@;xdnf~ljc%nw@V_}xN0Zx$d9`IfC zyEI8XmitE8G5nT@=AtRw1b1);!>q%}&*tGJDR$aUYu9OivEr%4Xf&524F7NVF{l(D zFHH@KXf634XCr+zKdXTSQ|vMcr-@U-C7E4EOptez+}aN}Fzi9Z-^=npP5M;K-Wf1T zE=&r41qKgMtnEr--<(H+<p4PYKjp@MlDg^>3@NeI>EqHWg1Q}AWc7ugQbl;4j0;*S zh`}58z6rwoD07k4FBa@l#*D;snb5hX6kFdTwaAgbd^&RS&(P}Xzj-*VkBf}GHe4yo zoJ!yX+veSLRg-m4c%f4T8XgP_PA+~mqFlu`e<TJGLh7|LlH*0ezuq+b4j-U+s<o1z zuk&TW<BV7gQwXm;osT!1p7IZ>nZ>!Ho@PL@0P{2))!_b=YxE`7FLA`E+0t14%0_DR z*@&LbqcP{#7kR3D>SP~_vDriBFZ3_EdH9vhV}zu)!_@^vdI`|vc^}-23Ql?th`lC` z0qlB1ciWLxh_)J(U?!Poii{<zAE@kmDwx`?AZa}KR=&3r-0>v2E+e=6KZz|AdL27H zfQG23B)J+K-+i7W6Vy9iET=*_$1)NA;B7#kv|i4hAQMD7^0}~h^p@F<dO3qbf3J4P zyH=J{|1sLW0pD=mj(aOCy(ZWl^_)>-&59Z$)%3@Xu4lVC^Lavw>)dkXcT}Xf+D4C~ z!w|t@oC6GGx;>7>w>@rVs|_tLO!#J>jNjWdE?m>SoJv8W1EY_=)r%Dm1d>?j0UFpJ z%u9Mihz6AkqX~~Nu&Q`1=H?>XBy%B{L5LrP1^O7Edvcj{x#6FezBhM(g-$_sm6)Br z@bcT98QPTHJyw(wt6TAh5-a<Q-&|!KM`=X9GNFIFmUkSZtnXf3yQX7W^U<Xc%aU59 zAC7_1Sl4|98x-E%;i-wYGFjN>Xb6UtRy0`6qRo)Yqu=y?#IsIQ(OfS7M*rcAxK&D* znY=S#t>~fH)ne;Jxv<VIW9TDT*cwshw?c4hVX;(;d0?SJUHp0cJtK90-S(Qa11|M? zo!3T#;`6Hp6DG*Mp$;H0UI&<Kiq_QgQ!f5B^g_kgFCexcfMW_g$tgeK#W5{B$tgZ* z!!b=f$*Df^z%gw+$tgU!!!eCK$*B}?<1DR2Fb+HF@47S^XU4y=EQiIO2S(Tob4sLG zA*#1#7*7tt`uwObn<vL<leXF#FwiRqyKU;DuFyPutZO=ffuBMxi_zNK;o%Tjz8Bkp zPkB$ww_o~U`FdSoh0!Yl5HUp60=T}28rqWF6FZez!61FSS$A7Sx*dL5vgy1xXhN{* zAJ$if0T42Sx|bOFc}T?qrBo$hHkgRB?9*hCH4^?G0GmK$zix{NPEiY{c1IAdyG2=* z<X;3wL_+LKYl>7?h8YE_$nh$XBVp6c9(7}Tjj@l-hoa7}R`g@p`tj_KL|tH8Q^IZ^ zDk0BY2Ni|-o*!xNiG8Gf+bkIwS;jL`7xuEEQH=MCw@igzP^Nlks+wTTRK2rQO*Cey z-r1@q8?#mK996R$b5!qKRa1?*s&}5MIgNR$cfP8*jQOf}fvP1M3smnyRZB7!s@_Ga zmSQYYy^EnR8;e!%5>-nxmZ;vPs+MjnRlUnpEz?-0dIPG~#|Wt2a#ibRl&jtfRU2Sb zsNPCd8)#Ij-YQiaZd9q>YE>IyRIA<^Rr{Jzqk5OC+8-LrRd21T{gF|tdRM60*Nqjb zw@%f*VbrPKm8$klW2Nd{rD}g{tWv$JRqb2GYSp_&)&9g-qk7k>+P^c_s`=~Hy!A}= z*~gYjpcQew>(ozeBiE?~_?4-A=QO@qTER34{~t*s?>NbErp0k9bKKSKILqR=t+(TB z=6E3fm2)hP?_4>LIUb7dIG;H-#doY?jz{A=)-lIp?>Zu98d!lI5*z`KA^#m`Q8qnl z>3k`i6WP(d16b3Jiuit!R<KJ(dE0=Zzd;rJ	hoe8>6)Ss!@E`XyN(WVfR)gw|2I z5PRec@(mgADQy;ZNTi+cGNVfMb5g2bkW&4U3U*VhGQ4XR`OGXM8xDtAi62M{Vo-tK zqI$Pdf#0fnw^4!LM%CRBkv0ZRp%o@Hx0dm#6!PDrbq0=F>_=!H#_W&bRn3=ADA=N! zRZxSh3;M}yJ($hCdA6(cWFBW;`_a~*y*=z;9!~UpbWg?}-TP)b`eYfeD1|{Wx}jhz z`4MRq&0;C%v%bbK*qyetpwILS@+>pjk&5roLSZoz=^*FD*zSH~X;GXDqU4h8`$*%W z<vv<M{^epxYbdsVby#y$ph4?Sp!ML`qH4C051f2;wJNAcH=+xfvo&t{_8_!uEcBre zt!l&8bXPPvFp5@b3z<4okTfdK1c@&g7~L){NE^y&a^)z}HDlp0b>IKMNsGMrlrk2M z&zac{!C~4SJAzpHsD*x!?mH=8R7GEk=~^MuwJyhU`JtK1{j!WP)ThzLT207X?@m?A zFm_V$xI#;|D73@kPTDZ<s;H*gmH851l-;F8*Vj*^zocFp1Xz~z?CiOuXXiUhdUmp0 zYaR9-<nVB1RH1#iI*L~FM5AN4Bew9r%2x9nMNN!Rw3_EATFrA5t>zgO>|PaJ%`?lo znrEl8cxWnGykFLA#gOIxI&%z~t9o`uSM}_St?JnsTh+6Zt?D^h5^-ISt>%gIU~DbV zDUwY40?-<soz4L4JyRtCx7bY)b5Io3AX~K)SJ4fVX?<z;D7I?nD09`$ACi*pbD3+b z)Aj-sb|Nb%sxK)(E5$f`$$?Skx}2k8>vG<IXI;*Akrt!wy;9^gsGWA@|Ltm>9jtrH zx?1O`9;<bZvaHrQ+&PNcCc|B$aN+f6DMAeZ<Bh11KcqKV{}^h&{70-rj**DZ`e$Vs z6RBL@V%6CHVX4M;Q8l(p_3ox>Y`5yIS2f9~SG{}a{k=!^?xpwlUe&u#)f8hNmE+s2 z;-wZy!7i1ShPl(TqPQD-Dju6p#eghh8cDw=CP4Z$+KQ`z#5btk{Um<B8jDUNnHyE_ z0gBE6)q9Ylb5QjjqUan_y-gIICe?eGqH|dF9wD{ji0VD6Y7XP5>ODs4#WB@;Ttzh* z&ZeKBN3!)(+4L{y&Dr>U>N1Y2`6t!9lk5$CUvwf`&)E)P^aekn7T~u9Z#IhM(4pWX zW#khl**0=pJ1Y1Ot@a1Fy?ZD<oEH0ou-_l`@94Ha#O)nJwQpIio49qqp@VA$tNr0< zFpi$V9O3p{ti}GQ)!yB0e~jDbz+Q~nBL^PD-qa`Xrheiqdf)P<Zh7}j{V?|K4dUG! z#JhJoRr{UqSeKIZqj#)lkoDtttjoyy$vf6F$@=L#*0ad^8C!w~1v<j_qt57Sv*8_C zaGa$hoZl|zw#zn8vnqXTwLT-~o-wU$w&$X#%Jv~qm0-FAm7r9DN-#sBezr1+`q^em z)Xz4Hit&q>lJL|l-~+Ra_i0Hq%iHsnNE<qi7g?xm*C)K(#7fUIr|`L@|G~141zQ)2 z?<&OJi9WfUn`7Gps9{=P%%?`!vY9^Bn8Ttrmql%!6wJ>tH(kxm5sanG%r{vj^D<fi ze5ph$ug{S@$7Io%Pd3XW+IG~mSrA=OaAEXhQS@YS^kj(?^e>hQswMwUm969n7hKPx z<X<8cOpG}!i8;{1Z*==;2V(xx4^saRE$JT~y00bmF6*^=!(N(fDgA{K-=3PSm|rQ` zg8oHTn<}zdD0$dw=Zn~pD%H0l?Yp>uZB)Y2R3QnJM=;FghPlizj~nJO!+dU-&kPH= zVF5EN<c5XJu!tKLF~eeRSj-GdxPel;R5JTUk@?73)38kH?kMewX%MHW`YxhHwW#Q^ zokSBUXQ-R-xs&Y~%HB)2xh~W!k^(XY#Y?vTjIwC4WG=Q!`9I&xf8^U7=jY876G}6S zDQ+z7q0ntNKX&`!KODU<#P_%??v}75DuI%=z?85kDgosQ8Of3#lMuCA5GzjbAC6vV zN*F6e-w!X$$LGB)V>Ufl<0N`JnOc*ZYfU9mkC)4M$*P4;h&pnWYNFIr4V@%O4!-Xw zd-|Yrnwn@2#q;GCDxNaaSYMIG%C@QFs-xVkg4&r1YWLZ@RZuzXsbLzZ(*4-R3x)P) zwfkRk1=QSbRFk;^syQlSIw+-UvgAmV?agUiRpbg>x6)|Q4PTOONSYxO!<SLTa5*W4 z$xsYmI-wJ`l1eC(N@ypQ&|HYG-9Za8tN0-di+=Z#Y&9Xk|Kqud0sbG)O$zY;cy4ll z|HpH)2l#(HHzmOT<GHB;{vXfH5#ayv+?)aaAJ5Gd;Q#U55(4}`o?Bvo|HpGn3h@7U zZpi`uAI~i%!2f%@MM#^KNtLyeLXR7q5R7nTmM*Z-1)4fM!LveAN3{lNF?Kk{?x~hI zJdrukUN>y1b~$!WwbWsM&fV-m9%3YRPqjY7len8~6(^hGlHV0)=i-L_FpfBdiyNL2 zN1SSkbH@?q;Nph;#k=C1X6jPoh;y0Z(&C6qFvX?E5tnF+`_a4Nl1y<Kal|E?;xgli zOX1>%<;4-lZ;*X>A5UFxaXiDE!}~I)p5k~4l8616yY&#qi$line%!5xIJ+sX|GVON zhB=4*B#t<qg5==?;)s(?aRcLs<2T4Y?5FRFOEy#YUL0|fDK0CHI9?nwhW%w6aY<(C z2E8jzG{t4d5vQ7&`B!noC7P+r`NGVUhyQ_RWp8mjmI=e(XHN0%oqO0P+^vT=o-2Ka ze~G)rDJt@C?Ok#F9wZF=G>$l)I`{Cwam2YyaUaAH$1_tN_Sf%<vze(I5=We5iu*8* zI9{Ur4*M*QIH#GqFTX1;(G)i{j<^&vGk+FGoM@)*D{;i}Bq+mm&#K<yc!s%#{hT?) zd(2aY594k<#PQ;gIsB{KEl#bX4Ex2q;&_I+h7XS;&Si=l5l5V0iu+m|aSl`5FW(iH zVv762IN}sj+#kge$BRSeu>3gUWHU3r{;s$LQ`|S=h~xJsWtcaPIH#GqZ<^vFK1X0w zEA4M(cKx`_uAc<n^~1V)RA@M)sHHvYGGq5C4be?uDsS(8o~aUz8dFDcI27>70iP1^ z**(i8F+%IvPLouqmNdO6*8fBMnz4SYT8TQEr!yY|$Ol{@Z{9mzN|~2FED-7!>g7Cx zIroKg1VPMO(c8T&s{QIGsVy-xrdPi<_3ErFqnh;USyHsmcOLl>6X9o_l=Imj<09M7 zTIz1LE|Bc1WzNZZY71|mw(wTg7TzWW3tov4>XkOOv&QE}*7)26`}adW``|GOHL8ux z(EK+`1;aO{Ww0IG>3o8*1-4tz-W+*HrX_iy>dW?Ld@Dh7`#^Ie=wzA-MSZva4N}4T z{suK;5Y4GrFR_u444QA_B#X^bB(F^tXb-gRJ@g{9cTLd0iFS=BH;;T{VADG#WD}2y zjHm?tTO~eBlfE$)PNAKLvR$I?<8mrPXgKebxVj$n?=a8!hJAM4`Cx7@$dAyh)7j30 z5w^oKivY6BzYR@r-eQV%QV%V3M5q<cg^|~SdWlVCqAhzVwS3mc+3eWXyzq}Eg1JB9 z&_X97R0h@0>2r<TAvs(QCq)qr8rq40oOi~YcQR*M@OhC$T@-72+yL6m(UKwm)ta%f z^vkjIM>j0Aq@V5GLuYKaOIvyp?I{}Fna`RFGS7upwjDRkJ7L`#<}8`td*j1-m-&s4 z8DbH&vk>Ez0F+nr#@p$}4=q$!zEN9DrF>HzN{@U~SiW)Roy?hfzZS<Hv^VC1mYrp+ zWm{iM)Ppey4cHb{-y<0<s<&0un<S%E^`2Jst*UXF+O$h0>R6_&1j5EHDHs|u<Og*1 zG74ADK)7#NxDNb2%fUlD0FD6`^hWBWJVSk@XH@T58bUp*de6}i!#UM^o`y!xtKJJV zG<rexUQ{(a;i~tNs(ol&QoWZ|ZK!cs^|q<nSBy5*dqvfB<BICNs%pcGtE#tM)xK)9 ztKMrgbb3wocF@pihw8mfL#NkO?+qF{y`g$<($MKmq)5;_#%aNOOVz$_+)}-_Rqe;d zZPj~6)qZZ=QN4Fn?bpU#N^rSE+tRZQPr^T-QK$-*RMdR=T`Z})CE~L`Kw|sRuG5wp zsY;>^p8h>=!_$qWarYq~lj-wTTFc#?$#)g<7rDahpL=MKeR71_(;v{Ryq>)XHL*K5 z#>Ma8`&mXk-Gk*8$+eP<x^SJjt2M3+vE2^~p2|g9b1?LgtzfS-!PrB+XnQ2jec5L0 zk9LwZNHkrpQF8wbaU1r37L+?Z6;4|P?STU)JWKvYY3N>>euR?PC^_s7xix}z|MHQ= zF4K#hdtn>gk{88c$8}-a<OrS|vI7OV5$u;T7j<=UalyQU5}S@EAwYYFz*cL|pv_0v z{)=N>HtM;d;9Gg<cyG!SI6|ZSu^XhYzt|Nb8Fk$;i5ppRi;!o{QPazR{Fsv?ZVz4I z?tO*Auiy__BYV(>(iX0`!p^Sf*L_7a)m4<<4~zqBZ(d1MWP1&p;W5tD8r%J}pv2`L z@1mM2M2p%_aM8{Uq5O$1&qSAvS;sPlOa<dyA9yCY1ls-!1uI-!Xm<}bXKH<k0uDv| zlX(~s|CH#-RCe-*b~*J_R~Kat??GaHOhMjPbWjpP)O{)UyUlt!S4wd@LuuRS?#|Fv z?0NqV^=0joQrW(u2c%Ti`+10ZKT+(EJ$VmkVo!q<+ir>F$v%md$cJox*lLL;gypkB z-0bd0k-`Nbvq%mu5-X6ap|Vl5MQ?*-?)%&wW)78QgqGJSQ(xvDR<QS*#k;#-a}A4b z3UrZ<Y$p7XY$H8strYDmpJqO3U&=BD(dM_TuMd@-w~;o6Y*f5W%HSf!Xrk$z>tp?T zO{C~-kZ8=_9aTyWqnz(yd$(+4QojNR_I@yqkQ(COuNp_0o<de3oHZ&&L_*|GQCZp4 z-AQ<aIte#-n~#!tOSd^h=B=!Ywt_m93$jC=y@J5S(B+=lc7e&_rZBXkV=+?*rsFZw zDVR=5EQn1wtrytoMx2INTuB<<{(=#X)*-FYlhe2rEwo!1?p&*+eUt6A;)m@o+4kRJ zSQ|lokF=4fM04r+#!?YV>~={?MC7HOIu`Z%#gvcOuIA=gLD591&|!`v6je*HnWHod zlwQz2JhaVH^sI$<3-gGclC<x#QS3s+?Pr@*m~$&tw~Otcm%t*-_Ub_Ugu1qU31zq< z>~paI9bwN4QDB=8vr8jPVuTHY2tH@TbVbc@N@AM`3p8I-lQL%_Bp07C=_1FT(d&+~ z8PO9`@5FaU{O|s^L_8Xa2;0p0fGGBe{RwOA-I>$a&D&wQdChe5c3Lc2$YK{O?$8?w zrs0{a7d%Y@&HdOd$w{*Pj;Y6~=JvtKg|6;9+G@F>aR#4alO>`j2_e3-Q$`}Rw}fH8 zqT12zVCMyFUmPUQoS@3O_i3!1A7i<1XNA?KeaJTQhR(}Vuacr`dQn&@HoZ{Y&vQ&M zy*e!#-M=P;oypx2Vwd%j#Lu`CntE&ccljJtc6_erj^GZqVY1US78Kc`h%a*pC54GT z2TwFj45^na6GQe$DxR}@Y<HPvJgv0(ja8?%HfqfKGsaF+#_^v(vCK*DPdmhgrGoww z=pB8Goj_eU$xd2hyK{f>%~RjGi%u$Orapaw?YA9byRfnHXEmTc2*(Vn)qn&?Vg{$h zz`tD>W2efEYEG)ZC{ulo+%a~ArLMQD9gmMQ>WWk!mdrwiCW2n#@+IiwT|U<*;>Tog zks$#Fng{_41cpm(ur<#<q1j%0V~X5#^Yx--8BO%8?6W9x4eUWO-ycJa^H8~s3*>1( zYx(~zdvK7~=cP2ZJ)r-BR1D=o+(?^O7d#VjI>A$_qOh<rx==y#MrxIX*xb?dfp{m= z)X{>4c7M6UQ|b_ni}V&gFVXB=8bQCF9pYo>5ZNfnF4?}HUMG2%BwK`iF2J;qqQR{} z&ka1IgP|;WZn6`5gme=$mGGa))LL#~r~hPT5%hG>ZrDl~1^S#w8+NNh=tGzs8VV0Z zEr+7;WibCVmE;5R5aec`<2U>}5uX}iJ9N)AXTGbn$=PK|Vl&&}PxZ7TPN^tWKBwo3 z#I`nOdQ2ZPEkW*~B^WlH4@$CAwr^n-(=fIhRM2yscII*C(I*dP?KG^F=F^rq;Os6X z`$hKLMfRwD5wrg-*_%}uw;#joe+7Fa=`))xT<9R{F0`5lKuOnZ?h!UNQTI*MhHc?Z zHgBUgBHc|lok=taSCbr$fg8HIq`&RzD(>o9*wwXxG@*-<ztq*^i3>5y87`_;*onW) zm3K#E&k<GrxIJ%qRZla56)G52tY8$$xDWoBF8?f-XExne-c(_&eg7QVS{x1jRAxu5 zmP3+){M&EIzyGZnm-GKs##u5X|EsxQwH1AJD5)UqeB@jgWt)F4s^I*^sFjBL<lMz} z$zm_$aD$LP&z?KaZeAk532)JPF8b_jj=e{nsD@Z&W2Y+oHtq`zqBc=8(w-MBs}#HW zF8>0Tf1#@&c>=W-*v-Qo3+eZ>^#|FX2y}di<EJ9Oc`f;=VgDjm*uU82S>m$M^lh8% zFncNYH^VL1Cr5)`>hjEC)2ferk&k6jA4i$a$v?l6{DElspgE$<CEawvHRUdUh09;* zqDi4~+^S5s6RgVnQny>RtLUq!({LBZJ3MpkLP3Rnf@is%WSorY=rv{~IylS7pt+)w zx&M~6^CBeCzVTF<7M%WnsCyGIsfz1wxa%(6cbWm3WdxUrV^UXioU({9iP5-a83|d) zYRtl9=!uigVm8q1aYPXnL~sN|QF{hv7(hftQ4wTu2Zdf_MsZhA+1)_g=<oba-P_#* znEc=O{oe0+zDMl2wO5_`omx)SITeHCf(Fwa7};`msGB)2UKC#(OFR1Q2+i9`8jo+J z0l8rp<Py7L4ZCAfH13LZq;|&|-i$Twj<u%Vj5WN43)yeQ8s3i47DZZ(Z^i2Ga;((b zv4(eI<e&s?sdr)x@5U%>SFD}9+EVYv8ur9Szeh6{RP9oGVhwxQ$(nbmy|IRU6kFQT zl`+{z_LT$VAUQ-<%cG?)kC!J%Ek7?$lc&qC%5&s5<S6+A`4c%to-cnTe=9GLf07sb zmkR$%;ZG9&bm7kw{!PN4Bm8;7UnKk`!oOSiD}}#W_zw&JQQ_x>|Dy0;75*0CZx#M- z;lCyP4~2h;<xjNyt1bT;%b#xfvn>Bc%kQxKC6<4W<=<!dD=dGV<!3GbS<8Rk@?W<6 z*DZgy<?pfl4=ul7`4epaYTKV?`?G9+j_r5Y{$ks|!}jm7{rhcywe3G{`%l{b2HVfu z{)@K%y6tbb{da8tJ=_1-K6gxNU##JMqKiDDqj6u1{t<_?Hh0D6q~1s0K4s$xHT3OQ zB2XsDj{3kB5P5C19ok_3J=jLz^*l_a{8n~qx6M=K@q~bj!uN|43ug=r-aLKZ=<d=o zDqH`iEyiPc#53seJd;jDhPkn|16Wa%^{brb=@o5b#|8rb{HPCY0H~YyVc#cs8I`h) zd}ZcH-WBb(b6M=6$8^Qn@tdyr@qB{@E^KL^q4B7OYKm>^PX66hwt4NbU7C}h=c~B1 z4A@1B8(iJ2`{3wI{o8gV3yq`^3EG2KNYR~W#b2vCF$sU|?!=||>vSjnfxm8dVl0&0 zeou{SqG-vAU{L)U{W^h5B)&kuUf{A4$I)*haM{qjlQn_MDGszQ545gVapc@W!To2U za9N=+xlp*SP?%jPke3v>j#eWO8`k932`qkh+GCr*FiHHh=jOJJ#k>ziJ{jrv9IfU( z@?TUt@CepO+Kox5i&n#(wM`Xxv9Q9U1>a&QDILp-auSySX{C{v#W$#Pm1nWKrAa1f z6V%b#l}mbU9W3pE|LR(*0Ov^s<=WDmv4@O1=hGx~QdxS+udOUN-$fOm#b@7sK{eA{ zcd9y;vHfgQCl{52!k(#HdIBHyM27wX3Up8hOGYQ~`{y6xfaGcJ=4-J?_qvG(ypUjq zQZ6Xv7AX}S1~O5hE)k68rG6Wc-2nu1QS;~l1gk{lpm@!jMlW$4D+HFv<r;EDnOxBl zxm=LT<XA?o=t1N%Q37&BOXP|M<cb<{MJYq0;AqH2Fu*#mSTAe=hW?T$h26|EHq_L< zh53TuUx3Vzv0wj|9ho{piVKO|E~IVExE|IFWyZ==J+kfT3yGa~$J#I4n4dNda>1Q; z)$`^x`b{=e_i%((f0aW6%Erb$u}pT<C5{EfzGr!MIWj0RQW+N6QjIRlv{XZ}fUEU$ z6DLdmw~g(l%{i7$xe?rLJ^3vCDfdoP2V(-)FZf#?Ay0IsejoZYaSi6b=WWY%+!^N0 zQsVHeOw6M`8QYU|JfhwpB3gdzMjF)8W4j^lJXDy+Va_4L91gP%8D?>qIAoZhFp%*D z+a*Gs3?l3#LiM#Vpp2kShP$;rgSPT#GloAJ*xrnFEvVmP^TV4j@)qZ`Vjpy8>ffOu zttK~W0_~2N@&ssp;*@&FC=V_M>tu5|^b{)a68FOC82yj!$@(qUBBRBatDopZF2Wn< zqgT1Pk*i!m;Sgz#zK@*uu``p+*I(gaq4YA><V&U*tz*0S9vu3I!IY!Q)9{^JjeZ)s zrBKKs+L)bo7L^>c3sb-=EXw>YbkPNdaayreVr(q%*oZNmeERh>Y(o26%FkS?FH{aJ zdJ<OC2%v;!Ho*JqynVl%Cx9jo8q(0O%MlY9X_?m)E1x+MuNzeqs0m}5%Q8!jHMPJv zMVvVJI8$P#wkb{xsa@67CrKMxYLM5*i^DpBN44-O661LUi>0YQ`<)l00fEYUl}19W zlrUJdcG18wKbYwLL*^!Wrzzu8pO|lu8P{MeR0Ma$`d0*FNShf$FeoLRp_PR~p0<Y6 z*~b%=DY4%oy6FFf15XAe3q%*RyWg>&P*$l0VFN~eL_nlPUXmycTGn6ZSmui8W(M5Z zhV238jfsI^>e_C~Y;{jAkCxh=Wf)?ysa1Jl*@Xi(uuyG_jn`sX`rTu2*ONF50~p4I zY&sk!s5<%8J$XXjtAsp6>K8_mky7Ax8rP6ZHcb`Cm4Nvgmr~EAWGQh~v4pm=64uye zbbN(!p=NVNZM0;aPR;I>-Rrz>en<pxB)ZKV+e5#3>Jf)#4u>YLCKvv5#$uk|W#>CU zr6w~Xz^1^-K@HHuX&Om*;_%SrPG>R};>Y;7UHJ~E#jGae(v&;&A(|7SoXdb>E=+|| z6lfR2ALlj1v?$h;Q`V3i`WH{z^I_Ch)0$WqYHDxhi~ccV9f5e!G`7hBR-88QU(noW zj@dUPCp81zf-$lw#ugN~K18C{;zx7PNM2q<yOJ)$E(|UOaaIuSa_`&)4I;wvFsf64 zs36neGZ<ydax?*GdeG#pd~*1zWo8swVXy+gPR;=+r*>fpSdnxNnx~+~%h0J^)l&;4 zCv+@m50Z2?IbM`@(^U@=N`w3=zmEfUGdI#ALG{Y^e~=qDdMz4_Rv|&+JIJx<GC7te z(*K%cwwPmBs~jj=K2#F`$g5C!gduW}84b-z2ZOU0#VUilVPLNqNryvu00@b_jm6)0 z7<@r!AE*EO<orTG5J%)>|4u^kCBd2?i-C(njI(ytMvP4q2ZLcnJFIQAQ?vU(R@t8H z!*ON*j&vSyaX`|ftHyMt*>=UJ<|X^c{`1n1Ah)NXn>G(~hKY;g)!L@YWCcY>tt*pJ zjHNV24*he%7>n6%S44xeCCth#)je7Bx5>$yH7RcVIP?j|V*LPyuWQ)RjLn7}u_sHM zP>g`&Y1}+<!WCl<so-5d-iZS6Zjd#)D#u;I6b)BeSccGwz^np@%scEpQFB{_m&~S~ zSl8qzkM`Oomui@FJ$GzyyS{AfG|R1RsANy#M$|E%3vn;4e7mGA5o$$9;bh1$UHU@h z!RaW<q-r<GL>jkEm?@DwjY@zlo=ZsTKb$7!L+3`JZDRtSV?Sgr%m@v4Yt_#Te-ZwI zT;OUgesGNr*#w|B%f!qE@Z4#~D_yV0%MAZ9ttp0|PwT4-2Awam7l`vk?gHz4k-xw` zpT2)53`s9==-V+XJxHU^7Xhs=a4Ep0fFQh*W?K5RY6iv4hm2smPG5EnIxmB-QSaO0 z9QMMJ9(nO95JiTcQTn9*9(zH|RD-|0^D$j#xTxJ>iD)<0m2G1$s30%mwSJO|MwQ_c z9aXR`)G~0}aO2E5h!aJ1f^ge>!xvD$ARc^kX>k-a_JTh2*@xCrh7Rlyjqk)d+7U&H zen)W5VH#<*p>yAjb&gN)8hL#B!V4;?A#YR5epfl?!VCHm^*MvU-H+Yx#yT#%pg-qt zb5#=joj{({)B)_WYRsO9X@<ok{93Asegjbj`k&(cZs4eonE<HIK(m<ce=5hdO(kty zcGy|E?65Pu?7;X7Q!;so?Fh(wQTKRR)?sl`L|{2CmZ<Rf>v-Dj&yUxMuLv3oP6@P3 zK2zJ|(O7j<<AOL=iwon^g2m#(I2MaCf=wacE5w4d+N<!qlVZ^Us|y9x%yWYdLM?F2 znkZ1d0j;=231aW_#R(TAnz{acaGpVP!dO&b^x<Xk0G`8<uy1=W|JGta|1|ydqv3Zf zIHVY%cOIyZ1Vsl0)EdC9_yHpTB2@ms;Nwu86Ox$MI0t$jRN|nZk%OwTg#xk*+*J0^ z-TMoM4Nou~9rTwqYCo5T{__U)=fM=-92As+sAHJ6Hb#qprxL|{3=g$%<QUDM#a!&L zrsqTk-5QUG>d2ya#Hy~Wye%HF>CfVL#Ho(l9*?-ykvrm%Xm#YycqA5$EQv?FXymSV zq#_!*J06KgBTM6vKGDcM@kr(2k$dBjzK2Kdi%0q$9$6NT^gldue;m@M<?+aXXk<k^ zGO#-GKs+)i8rj8rxd|fJlZr~SCpB9vu<l0M)OHc)8vm@cyJ%l$Wo7@aTzgwP{b46A z^}vn4?4iyiH~qBXhkbJGG+NJX<6qvx_oLB{9J#i!VDjA5u{4kFW>Lbi;SRaqfN;0R z`;M;h3EZ2Zac#VeKw<e!zW5kg?}}yh7Z=y!Y7K2{#3G#R5!a0fSiL9sON*FHeU}TX zTy7?dQLvS_M|limX1R@f3b(q{NXT}&%uY0CM3@U*J3-x=XRCD%<(iBk%hHNqC7bJ+ z@8UGzEtYf+#63VSo2!4@6H>y`A2e)P<WF<S=<1QxkX^jPa{hsVM@#j_OcoV_eC1u+ zp}2o5%2$pD4bWMG`<0OgbKwX>Htwv#h{Q3)i1D(B<&lD(7EE4QgS{ecOd-oJM#mNE zJ=`sR*e!!AOfQU0^e^w9p&fE)Cp3KjN3jzfGJ)9du`J-I%y3#-)4%4GaXt;8b$0!k zVov>>3Q%$%XL)9k1g3!||J!N@7%?7N8W<`p6%)B_plPT?KJPPN7V<jn8Ycq7h5=q6 z3Nc?bi-8<S8VMd#95J>VmC51o;=-QM@5S;si$JG`fO5l+Xq|v%1HO<BsZ4?<_6l5s znB+v#SqL_1ZLy?6&=!3Hj2@O^CS6mVA?vYaLwt@-5vkH2@JVw|urAJ+)j=l5b?bro zw>;O4<;CHhAnZ32zv7E)Nw5A&+NrRKwn_;hhX*LueQ@-`z^-6Gih)KLn>Z0@kF_T& zuq6xZndSS_<WEf-btkS}Q}&~lI96EL8rn<pef(=sd*T<wMa8HoRI^@bk}d=-jn&P2 zVwp^^BK^>g(k|<ja7Bu#dt-R7Cnp&^G#L;qu&Hd9-rP18_qy5I)f*wG5RtqQ=N=Pu zJvImVPMV=1%#C`{Q2LPXTsQ-EoM;J+dyu>j;V^8;c?(DzmJB*gdX=Xcv}Tz%^28R> z%NJ$5S>ktWI8d<kAmNRU?Lkp2;8mrhlv7(N5aT0XE$SPOaHZ2@d$OxHCLPFNf_vul zy}wZCKOfo;Y<Y74w`!M-I3}hsQip{<geC*{(4QMZ0RwtBkK05G*`dc9QSL`P&Q#(q zQefJXJU?d7!%Lp9R*^CC+bqj(_a7Le-+?g-c8oglU4v3SSvqL19OYZSp9Xz-W0m`5 zCftl=!;iBqEZ-g6K&k33+Z=xl%Xia@PzS+&_Q*qJlyr+Z@P_!=LLnarAQNYqf#{R+ zfFSU|T4cjKvL=r_iZc5uinGhk2b+TynDxO$$84?(>fJDA0F94eH1E!2GtD!d+^882 z+g%u6ijML1(nCd`Rf>LDDLT~SCmbsJjiu<9bMzRZ&qQq|9xDD#rTACCteX3k(%ge< zihLP6N3;rsx-+r)U1Q}!(N{0I?o4cyps5Y}6PUomq9EF(?QSMJHHY<20<g*Eu$@53 zSN*5x*m4BXTa5gA5;yG6<~FpM18@jQOAK>68aI+d`94@J^z}N6lKuWgPp7je(zlDA zR%c0;4^OZe7d_d*H1%!17shvqi(^A%&|n;Z(#Vtv{p#Btksh%|Fo~*ixI@9yR)v|6 z5qAXDJrcQ^+g}}+4T!Tcdu!K59C@ONyA97yaUhla0+$^E7&p^c0GiE&GNRmq`ngo@ z*o~ol@@48IXeV*^pTaXNOY1PHj_u}->>o_duq!(v>}e@jdWWSC`9>mBxqM=NHo=>% zPs2iCPbQzs13}P0ndabts4s>G0JnY~9~4|$?t4?{+gk2>W9WOG^S>);OaC88+S31D zNZQi>tCF_#CrjEQ{SG^n91RBpEQdSnljLx{ByH0fc7w_XgxsiwhR0?cDl9lZnt7<O zOHJ7IjIzPK!fLR#LEx-|1I@t|0&ie=9bF0x{`OD~d5)An7ZAD8si1hD$0E?kaA_%& zZKa>uOi3DMjuuvx9W4ZHp6x`Vj=P-Zr-TgHmsKKmmQ*5k1}YIO?ee&Y++lfeZ`s*a zmJgkp!s`m5cOZ(&(Q2ui8S68}W266E#%{Z|Cx=K~@m08Cg-8_QQr(@h%!f|lW00Gi zz=&!lJNjOC<h^brIF!5DsfZbKvBA{KrxK8f<qZ4qg=U(t2r)Dj9#)+9uyJwt8W-aP z7#l*Pdp_V5i0%`$#n|vlgYMx>$@ikm=+9HPTSe>GfPr%%_Z|h39hj0}-ZqL-2FaeI zf`k6L#fejYwNZm|tGn~1*0hyLD8ojvK-E;x2vkk0j6f9&QErJjp&4rq;5j-UcQ}+M zR6E;BH47lcL1pb?E`2DdVxAU-kPJYji#BeK`>bo0J~b`J@=B)?d(!^+Nn~M(du&hf zCKEOdd2;}qbVuanz)BCdNvLL@qh+BS&Q6=0gw(u0Rj}J3_h7hNqU?6bO&b*%1r@x? zMET)uQxtO)AL|Z8z#<A*6a-Y3AL}+n*+5E+O7oHq?Sw1BbDB6*kCRc}D>F%@=ttvb zz?)48NWFODm`q@^j9ml7R31k#Cy@!bjW7>i^auNAc=06=FBv~<tr?Z6ygtV^^$j!c zm*KwX6No8sjQ3lCtyD$-Ax4bLVdQS>OvbyKDidES^QMlGB8>yq3R<u?RVMqGElIM{ zM1h<NEB-W+&!=z)M`SR-o2-<g6Su>0JXXmofI)9ie<!hqA3$sP0p0+!Knd3F$v&%_ z`Xu{8IhI;j$?MNitKwo<Q{O}~65$&Run&{$2O7~=KMKbe$ig&`V#u?&x?!XYrMNgJ zO}ao(vLE1*><_wd4#k2XjQCvd5)Mic2<>qxgR0ybR+&h0do6B7A74&3p794Pf>H$W z&ei)1$T<*Zv^lWB(_tA|OZ4Eo9yo`u|4sy=)b4CCG+PSI2BAHnZDTg8hs^SnBpF3W z{TwF(w&uT(rt)p+9IS4;l`bZ^v20+6g=;w|Ezr_156KJ`m%oPe21#q1V%Yk{JY10S zSbAe(9x9LqB2FGwtN$Qa`!kcC6lU4lpQ6!ZL|)AIh#l<MCmS2zCL^i`ty-}nYIk7N z`X6RU$d)d9?2cvUjt%do;dVxAc-^2Uka>NT&rk=Ki@qpH56*;wQ(=d7h;sS%;;tu+ z7&PiN!ESM6C@uU@hdB=39k?C_$N7RscZUDeVxxb2?jo~s%!a%FXs|6GVfv}ZbY_#& zYt$;<%|y4AJ7VS0yu>MZyP}DA(gC>!o&rFl0yAm{zz%sb%8r2y^bFwaSTHld^6(_i zi(MxDAg~3Z4&eI?VT33~C)1}0yRJ}3oX^WH3}B@aW921!r4nPAWQ<GnvPq9K#BdiY zmgp5rEJpBfH4YJAoRk8mVPtRu%=iJ00Gfeu7R1M>0bEm@-Lr`L(*Q7}i^W)w0>w=3 zh{pgk9Wr<Y>{ymPdNHWjK~$xnRFCTi>)_;R(pB~i>rAb~_B`p8!h-m>a(p~n;@~>y zK3+9+hx_*M2s0l_)P~}DfTEnS{JQCF18afZZnK^U!?9$+&gs|@w0AO*t6nS=p!0wU ziLb@u0_FvjD2R>~FW&)hjoE#N{EZX?r-Wcv*JQNFScj(w@5dHFQ4?Kop95QbTnSE@ zt6kMi>E@aZ+^`WeP*lB&%NnLGS71M1znrb6cQB896pD}`H_Md0MAB_WB&^r^nym8m zZTv(DUie7Ubg?mOa>W2(zQ%40=P?$?C8qNwBQz+ADewau3Q$hQSbm39v9fnoW|%8n z$yhG*82Ce88hD8so?xYwf!X{66NV}vL@_VF=%7pn(C(+`4AiP&aJTB0My<M&*XO^- zva+?l(-8xpHg9%&t=R5iQ<uSJk4F$l4Kq?0fgUdoGtvrNe7&bUB_N%_`X@VNGcBca zcsyptfOWi@rLA9ZVSROQVSQM2su~*GPD$x!J~kZ{U&OKN#^Iya#7C})N9wMs8Fify zy<HH@(@;13r;*z3+TkZ$AgVp<91;EWv61Jk$O6u>g7&biN=$8D==4%0ztssfkRA-` za$I%l0ob=#<Uqr<0~-4u#p<4mr9O%^d>pHLA(r|$*04WT_fjmiKh{u))x8o+6=Dr7 zUft$cs>N%##H)KPmb%1i81L1+5lfBt8ZPzfw#8DHdJUI(bvt6I%e;mOUfr%(YJ%5r zxmWjQEOoipFwv`fJC>U0HC*A<y&Fqi;Wb?8)$NU?uJjtN^6K`*QdfBmlf1eQVyQ`9 z!(^}SqgZOP*KoC0w?CG;+Jgpf?{+ONw(FswUB_3aR-#?EJE4`nDPElzGX;t7aGb+! z=Nd2Eh8vx?P@-JZaw0U|Vo-Q{)NSfh(=yY=I=9_z>RZ#Y#%=0X)8aJsAJ&u@)-<4I zLes!w#y1VBX&Kfum{M0EtHZLe@d$pZVd(<au19zE7+;$2{T9)`7Ne)UxQojS#sgP+ z9UN&sP5*p*m;#4vp+Wy&7Vaoa$A#n3C(;Z&@<a=@%mo(z<9w=~jMwKYu+WVsK8C^! zYMCqst)w7$^sgB-h8Au7JW~JWhvSX0B~6LZ(`dk)=821%2GD|bz@XzO34$Mqv*kd1 zJ{o8Dn8)I!%=j%KOeFm;3P!B{&5y?~8Zd_#k;?fsLMS28h>IdR(ciW4fr^&cQ>nzM z9t~+#MGeTqvS?$niuD>Rk^}3msYxdfqY|O!k{rZpEktGt8XQz>U~(`8<N8~Ybz$(5 z0fIOmFX^VM_R<7XF``JkN1@&`@=@2~uEmO{BH8EADK}$<IHIDYM+K=r&35+`Ri{Qj zhTM|Tk&n6de~vgih&US&xs}b2#c4o0g4Ra0O+&~%t!Zd-NOCAe_J(>x_%d|G=$TyG znN(XqFiWfTFyG1%6*W|3J4a}z2<>idQ{QaTt(#g?f4^mo>U2f2@1bK%>m6ea$5=x# z)=-RoWqCY8G5U>2yhgKr(-10pNT40i$r=Dd0_}k7>32ZjvJzL}H*ndBe`4zvxE!b% z1lj?=L}=h2O`OZx2F4Xjj6jUQ<-ul(92B@J5_R<Z>A)3-JV*`>TzxPw${~TPGSEL5 za%AiNLgm_z3iL0wzi{^c!f*B$TKV7d{e?~Y3(xN_6!_mUg+e59r(?%#=U2gc7Ecd# zXJH`%-D3<yQ1?VVH{@?H^0C;=5+6H|RSq!6DghUl7OO*EXX*O%id-8GL-24B)~+;{ zm`5~1NZ`!4CX`6t<v<B+3k}Jk-gll2*ubm^Zv0Z|*nH&875T&>bF5-PJVpyr3Y`!C zFn9`r)ftsF2(`*WrPo;=ui3<dsfEf~#I?)gCOQwDYB*v#<uQkL{+Bd)R+A1i&|%u3 z_mIA{IHa!&hV<I%)G7?=_wbNjO+$LCH~M<7{(3KxYV{hf^G46|>SvMTIt=pnI(UL) zi#czLmQLC%etyIlI8IA@Zku%4r419NxDE?%doEw?Vw=WTtNjK7Xb2mdpTLQ3@zd*= zAAZkoiZ-_5CiTF12pQU8T$S_WnO!E;eB%=heZ0_zo%2~EcGP1aj>{*Ew89^J28|AR z3LP8fZuQFD?vhSNkh?q`Im**Z8Nrwfziah9<Wj6MXIQLmyjO<KLDlWwC9RI2y5*T0 zlLKtIsD6!wCFXsO6StkocCg6q3D?=-@hK~lvl$$o@eWeruWhQx!d`UZ3nF-RtMNLI zM))Xfvrr8{hHi*s7MYj4a<Z^+z%#fDSYSg-F%kKMR6S*>@<FP6kSdF<0%4*fFNYH~ zI~l#^&971>{CdslgUv1j`8(VvPLOYuSL(?4G43~$^Is4<4+1(J<oRVH<R|9t_R8Jm z+}-lr-7t4I$o+7{#@j+>M=J=bJ~llCi2a`wQi2|I2uQIlMT6KuL~>JbJp93zRwU z=NLFPn?V!u3=eAK<^8(c!R49Zw4{78-WdIX_)4VZ_>Fb9#rbHaA0MCP>*rPQ+_}ci z%d^-7H0(S$Eij@z-RYe)D+~Wu{yQW1d!{^DeqMe-eo>wxza&qUr^zo9Z}g5!vlc5@ zI?|O<8Izu@ka5{ZR?5DzpX@Iaa)2Bt2g$**N**SM$f5G^Ug?jJN6MpQwfvMkTGq&8 z<fr9lq?EoqRt}TL$>U|MJVDk;B~O%E4ws*mC&|y1Qn&tJBEbJo{v(_e_`=Tvy@&gi z#o>N^Fx;O|om!3IepT5vX*Cb(55bN_%V(P0Y~2S(*Y6|D#%N=FGQSZrY9$`sg8#G% zF=6BAjcgcTW6W;(3^5vP{K3q@Wi*>N>WL#Si;59RcSMjbXY6kCvsWA9u%2c7N8jiL zexvufxsm%^k*~kev%_R%!EaFT8wUgjVRhH?<8(%G)cCaGg}R}U@(g+?gPt%0RxqV` z00J7P?z$TA*uiDKL`h!y<SGr%jU0YJ;9e&;a<3ysR3zSugw<<`)=jTTMuQdJ9g#@g z#-jtLNVJo4Mi^>Ar?hTn=(H1;k@Nb{=_D>B=d94_Vm(oJL+FeqeoW3ALnklx>Sl*d z4-)RWn?h#=cHMP1ht4?eG1TS4TJ<R^o0`R5r58PNc%e`!_7}cX_V17U@0$IE&HT?U z6lw|uiU&I=Yj|wKd$uJYbtl$A?8tPw!OI0es=t8-%L<vG|A*55wDdMH@UAVWSHIsA zc!89oa1fTGr3BuH#F;Gdq_{nVgnU3}TK^LY@&TE{23g!7%d?Msv{0~C777m*3iK6b zRl&uIuvor8*xQS0f}#^!OJz<kmf6XjnJ=y0sW0=T)jM@%zO;HL6y!^*ck0)C(5;Q! zt@)r=zt6pz4?6X0+^PAXPrtx@nh(14IPTJX(4$9ikLEw2L+vBapbq`({=$^~g=_a0 zX6!HAu)lBz|GRsCp<e<21{Mm36$)nr%8?K9k~xR-&vV%JL^PKlc}3JBnq^)3%)0oT z2;Yh9`vQDlVBanH+=9=F(Hro+fxZ(hP>lxjC7pnNK=3W0Q!s+lq0?dnw}nod5!@a+ z`6f=?oY3hO8yw{ZM?L4r%L|3*cM64{6bipA6#h^s{Jl_^P$*oE&B;SNQ{RiWom8@) zF(NdWF(S0IK_6E~M_wJp$2xc9I>br*o0`#GH@7AiGy)yA;pkk@26R|9a9P}8nZRXp zhjj$$9PY5rz;$r_ts`er{eM#^j4c%Y$^WSGQwoKdhTk4`Ac6aCFLpEdth?XFTN%tb zP}}8^SoG_E0I<jPjIJ3yJLI|9p2&}^;b+u7IpbZ<|1jfY4#@ZzWc(=8{KrIc97c?O z-5vQl$XPLZuRC%teesc8cip@qT|u9&n%pNaw8ad)qEL8*89EYq%)#y1KD0y{kjvIz z8e<b3y(h-C#fdQ!OpJBasWq4w*Eyk~+^iEC%6;6aIZve?LU68Z-puozqM}W@wN56N zT8BtaIpKrJ&Fi4e%dY{`7mXm9=Cw}!#}?RXqXQMcD~-)A-XwA0M;4z@;X7+grtp%m zY`01qJl3yaE4Q>P&BpU(iL1603J05tGVh)ZV=*_ZD@U8NaruP>l-5{VX7|t89Xy4G zJgYWf@trruS#c1?&v>k*-FINjC6;u<P}f-1;^5HMi)RS1xE4zK#~T_XJxb8N2y*76 z7m$VaiM+Dzb^_~WnHrE&MhrcB9NXCVtD9dF<cKLBp9IY`t9sOl@o6SO!D6d}PZtHm zxuAzntWMS7O}nxl+~h#*yG0bQ4w;^O5<R)z2`t5B!;f6}@l^Pccd|8u&cyj-3r^_+ z-wk1K#^Gy=#%$jPCp6nf-o|Vnn<q79`<`_Ko+#y7Jy(Ly`XYSR2k?0^gwOMQOt`PC z?o>q0;IAMuMfyLgP^S4Q$Jl;s&W9gQIa$-cPjN}%x4{I8gbV!+f>E*{cHTk$rSGg$ zmLP8u5MjBcgp&m01dYu!=Ny9+zN;R@VXVLfrT8+mJjrNT545~kM9YGjAYX%)^#`Km z$s$@_DxxJHphZ`wj=?K_#o^#JCwwb$6AcH$Q?=02EyL0q4(_@32}grxQI)L@l$cLK zPd>}S4_3o{)|uNj&KOeY7d}Fh8}n40jVI&|+rfa?=D_y&Kh1kDju{;&;W)NoU~EAz ztSQ=p<~&-PpNdmBWqWLxEqhUN$lJ&j$lEqrkhkSq$Kh~lNS?NqhYH6#Z%)blAU9}7 zaopS((CM?)sb>(fvq-3Si-g)kggPnp8S>jxMyS2L3AF{O-zyPnONmfh4oRr)-h|r6 zgt}SuqSTh&l)CjGN_7X68d0KD_a{;+mvms_jWSwPlS|j#Rs&=3b-TTE$rg7&%R4@q zmhYF*lH2@2k(NCHEk9SC+KiA7%c%7cPcq*Nsr8zZ$=2uGNNT&2s~J3yDz(?mG;eok z;uI~l-7t-vgL~A|j-Wy8b>e*3%1OQ95R+^T$AN7q<zt=_(GSh9Imi>2wfU0RB16oL zndYrd&7d!04j#9Wul2$wn`wT-VN*L#hel(!I>A9LK0~9iR9f@4gVJyNFX^`x({FQ7 zu~8dc7@d6$^{3WQ(%?z0H=MF3wV1ZmOj}cIFlZr<txoX%Sc_XU>$(l&$0aTrdsb17 z!|laTkqr|ic-(jcRVI?AZL3$-Ji=kuJ>Es}b^j@&z!K8p8n%RlM3=hZkn>E?{U=wa zG@ih6Lr$9H2Ap($q~^TOr&hr*z+?(_9E47B!}ZBN;<GQL-bJTg<CdT^m8*4xfsX0; z51lN8UzzNv{hY_N5=lM+Nv_2!^X?U*x4_XnOO|Q=(8=Y-Y{E@$zs0KB!OFY{h3<Wx zgPS+S(^-DBY%V@=le#Ai7>g;2KJk+$H|4~iNvD+OQZ{2f=6#IjV?IEGT3y_p9%pVa zXPV!4Xk<XPDT<S6{(!TDcUWAimly148i)D&fm13Nu59p~Jun;ki>t+M_|zDrcGF$l zHa#)8a(x7g=`O6DHm;i%WoS^QzTiX_P>!B$-ferH2dEj`4HW?AsI3gdnQoaqW|Ukr z=+uBoh6aT0Wt@3a$R{5<MdQpL7Ww2ur<`OZrnue>--gJ+^wBIg9B!Ts@b<;()Lwpt zp$s!Ox=|X6&&SK29nremYwGT(DZUh5)T}WBbnU_}98cDLcyu;BVIz;yHGH{9T63Vb zMt|#<R9G?r#kDcd#mSu>2U|VVgdTG}eYOkzfgey6MGueOSo&Awk!8|pelAXFpO1^Q z+;6f<Pw2@(OU1l)-ikZU`MNi~bWhTmh9^JRyK05G!gP}x%3$BL+3eT>GT574$SIEK zbq)k6?k!$ESAPqu+TG&z+BBp?jy*qO2zW|$ss%%PTNwe{-CnO}jCW}izSPa+Q&+&2 zS?WsHyq>WvCQ?`7?o#S97q4ebaD&$~TFmPim(!?zxm$WY1K$%<6Uld?8@`?~$HnUz zSJ?mY^^8m1vPTrgy9Yd?aJg$<&-gJ;nv9j$@;GN5;!%wWu6a^{|FU7<AYqUfS~<bg z(>$$lg&VviG4G(KHLh~YpVpY{hEHoua(g|k@gJVkAR0Db>3-sK8m55%z2`J0x#4>m zlU(zh#w54wIgQE1;K^?IoW@)iN`kWw@tj7H2%~#HrxCQ-DYiL$PUC<lG-CXOhFL}j zZT`=XXZ*K*FMB*Aa2)h_#z8#|xCUKYd^`hGGJ5PpC4M}kj7m|ak_n#dhL2}-6i1>L zf|2M;)u~IsNS(#0Z(%s~jh^b(oOfz!81C6HI~q1yMBHsmP%@iM1DjooEN*l08Zysl z*TTNW;*xtB+_ytvDf(md(6Hhw0hq*~d6CIAzZfU~JTI-X(nVinB^&04j!iaSe~lYq zv)xv%k;a;Jg+k*h2U%z9ujMi04uXIi-Aps+Fap2?w*T5Z#hb8^{+iFaJ8RNoaBqfB zd)O*To{FvE-uYuBGK|moqM7kcvBXyc4gOC}fF0mH9)UlUcXJ}@5&B&?@JPPWTK9&R zX76;}wu*FmLie#Wq7rhT%)itq^CulxS<G>)AkrE;kEZ;Eo06rP1=U<qlSaeRb$8+8 zTKc%VCY|G~v%LC+S$@}|*3vHJnDO1mjPE&SJU~C&H7AtZg71Yk2*!8k8Xt9+3_Qz- zPhX6W@5wiwxMUz7a<iKlqH#9GV2u>|nn*bj8tX?zvQR_LOF^+3hfN%T_H>!{KmxYJ z4IfQ;iSZPpf9IW6U7b1+w*JdNb9ZmhTo2I90%(?&Kr#y;xu@j59`1Wxo80{~!@095 zUC`P0v40)=gJerVsp~_M%`zX2EfoJ2PlPmE=7vU6U*_ueqkrdpnW{SzRk`0COmj(# zlNsL7=E3^xj-%_Q)ErwksRoXFYU-{oI@-jK#JQqwa?M%G-Cv#^EZt{R6HVIyVCe*; z_bxSbq)A&)QAC;xgx;H|2qFlwp!8k@G)Q+5DWNx&j<nFRVNe8-Boq-95(Me+@$h~> zax#;>GG{ZBlarmj=boL%RPP_$PWw2$=M!T$<KAVnKis#~l;6}@J|neM%AebE@c7Z1 zt|49Y1FD&pwZ=b==rC71^gVH@fA7xzYc652n5t_k-TIR2^|=i`PE=fM=^KB8$)E}4 z=W<C$BQkU<olCtd-_J73;r5|owN}u4&C$jAG*|nAG{aC;Vfqw%zWpwdwjr~rsvdv! z{ohs{U(O~9G)R#mGjBi1pI0B>)O+lmGc9;aan(US#CfI}6byfnhD!%N@^=Spc-EB$ z^AIXmi}`DA-Zk)f7;;Wy<!S5i(qJe54{_r6)NHzx*gs2d4Z2<0!Bm;e6J6A{dUf|q zGX9(kg4UtX7yDI5t)!XwqgR<nS^BVI_nFA$7DQwsX^vAxmTsi<Jb3j>q~Uh8+nP=K zCX(?ejL^am8grCJ%3<75i0mYNVc3y5x*{4W-~BsMejgKe^dvKJ#C4>8`22A3p|w6U zxrJWqL*#|wwQUx}ag=L8T;xlVE9<&IME;|tRklM;!kkj+zYxSuNaUMLnH`PD{7mb^ zXZnK+d56J$uHs&O-Il|8>z~$n)_H*k5siX>=FTjD2bKD;MB>Q%G1Yx!+>!RoN;r%6 z!!on#@X%JlRVB8-xVhehhcElq*qVB~ZH66=Y((F0^3Wf#i5l)~F@&ZRaU7l|yyseX zJ+u@(-Y%Vez2#7@uSj7*^XIldVkh`WO7uuNQY=#DXaVGG8tir&F4_@@1Lh*luEt|# z6+%mWv-HO4=A`y#;v<Vl#|>uH<ajRBFOICs@A!XpJ-iCfzh_%lJ^ZU5@!nN_q4F?R z|L|L+>-Mks?zBHl{v7Le_Q=RHd!uuE9C5(AZqwnE9$GTGFVc4S<$+1ue)ImD{YOWI znXVfm9P1$sHl60%BJ?r`Ir0068(wAI`r9G&T9${C-7yM6U<{Y%*M)A;1)2u0&THEO z@!hV&pS+I4k(GA*Uk1-Vi2IE!SPYH0vRx2AC$r$XP;?O9H>bD|zh0oCcwVkss{7ZR z<o@lrZq#teBHQ81z9Px}>iGS_$ka@#eUo^>hj&t+uAJJp-5eyZ5N%GY6sX@>y8(*? zJ?Nr5$Yk_PZ<(U&%1&eIYrT_)7c%v@Gt-J^n#F2PcO{u~XNkO&6vjR;8@G|>w`ig& z``J0aLn|;>tioQTUQZpm-5qMu*%*8Of{~J0(a198uCw_JE^*!LA9>fU>zA3pkTH;4 zWf*WzD!SHUqO$!N!&v^N(`SG+ro-iDQ=g~PYVANEsFKrhyF$J*;A&)Fpb1SAGnX~R zJIme-H!2bHL6e7a!^-LXGN#8-yD0_P5^^!L_-OZ0mK`jHP)ukQba^Vnc~j_k_bxuR zK6YB0S%>eh{O85mO!4-=1gQ^~D-u&5;M#IWL}Zw4>S<yp`6ep`l<jmKng?E3Wc(wz zvS7#sXCKY|UUE0*U+3S=)l6yjntHV4o;?jYa2S@&zBsc)$S0{M4B0c_L}vcBX8?uw z%I;iDzU?fnxli>=^PYNulLqJau=NtT!1Y$G&87S40dJX{yjk`_@x!IVe7{aJ-ZE2A zTIK)p_6qOxy`^Yl!D#%nOK#>|V#;ZIvzfil%KmotasNT>h2$rDQmsWn&V>}zOG@Ut zxef2t=Je_PB6JlqZvXJVIQLU|p}CzyCt#s@E;cf~GvJ!-{XfdLtI*Jt(*tby(7(}= zDc8Z{D_ya=zfXP<71K34<i!kaqyoEL9(z9HJYuaBV7~fRtI$2LV&&<jZmN6RrSh?d z;n)o&0ijl!y__oE$6SR5<`kPrjbHa~XOYk}p@zqM8GJr4+qZbgk#vsMd~NJU({t{R znSow1{pZB5+pQ5Bie;TBUYvb?(}BkE`z)RqN92v!@>Czzw&VRS`0&H%IVVo<l;Ju4 zGe_eqIA6CjjtTpPGrzC_%$etT9<NKfjnkZkW!rxAbbVAn6zOC>@8B$^Vhj65axQ)@ zB&1mMBH$H1zTiem@HcK8xn{*mCve8<TT7SIx0cx}d(AIVLw(-<_KYsq@92nU8tH$P z4DJwk_>Iu?k4_Q2`U=t3((&K!c!tdpjpCLe?N$c^(sy5`&Z|^lEV`OWP-fJ}6d-6+ zb;sgW)X-xlboF`4E6%i`5dNs0@p{mAssx6_R3ADCyRra`!mVE&yjT8B^IZBU%V>M> zpqbbGP1<3x0fl%<IWq8Ut5?$y?G_V{wsJN3pNr?_<qAp~t>(aze>1*kl}Tu^S$|fO zKbLM)+$p+MPK%1Myjs4)9D=h<L=!uJIF&aujrOThNCO$_c6BZ4g8WOP@2-8eT)5|@ zI4!M^?V!1#&h2tX;or``=kL2MpE<-SPT72}o*C=#qzf_ZoD$L$B&Ju;>%=^~{}eOz zC!6{F+;81K-PZDvhUSO1)u^o(`z<mr8cbh=jIOLJGTOdJgnIFr20O1_w%qE7E8TN$ zUdnU0BiIyq!zaF=SUouSZ(Z*1xz8W-!=k>?Mpr(SzQGc*6f$=D(S;F(Po1YX-cl56 zX{K4ku$n&n{><}P#|QYFz=Xq6wyDqB^%tiV#<oxKcKsOC9dfo;@$LP5X^L_Yna`S* z_LXnT$+OO*?7<YRDS0+oy*TgB(zC%0nY2~KkeQz$@iW2+tm;!aJsVMD++Za|12n%7 zvqa3-ol*1N<J6faB}9LepK%~c4^l3@gNUy_P~POtQ;98$edZexyOQr}t<#(+z+N<X zuC`S&z$f{a!2?PDFx%pWK?XUCVD{R_Q+I>#k7*1W#XtC|IdzP`rL}qSFT78abckdU z6ydaJk-ISar3$r0!6cF``bWIw%f%(1xXAGcB))RO%`Uno!RMuR#kW7bxlP)<sJqKH z?-*$kl$fxB7%4_8!f)AKgmsrh*mCouj0CeOAvMCU$x7=AV%bMa4#m`e`R2mdxOG-B zvn1{xiHT>=JGlg&r{BU#`<`=_OpDi^W@w0l{SShEEEw6DEbN;eZM5E@e;#<49<ecE zQ9JE1F8uHA=){BykITnErJSzL!1|lsuZqT6_~mYjhihz}U3qB0d2cGf;MVwj?ro9N z$l1icjKtZy6J06}=Q^CNC8i1}dRMAZw=>sDrAsd+xS>m$g&sZC^?acIz)jJeZBEo6 zyaZ#7^Q*>pyqxX2?tA<8#G~k>TJi_>W|<0Z`Q@|-InF!Jt{%P9!L+w)Z%S8%n7xu9 z<nv3CF4K{KBTemlwFM)QvEc3tgU6p8g7B{;x90D*T=5%w^qD)kg}F2gDE&H(za;fW zuS$BjJ^dy9u!b(b@$2tPVK$$-|6VZ{61gOF<MR7*?Q79uUKq@iDx;zo-K7zg@4jGG zK5gV!zubLlT<QAj(<a}pX?V7t<H3~!n}ci4&m-y&pLf6LuIeuDuKTsAwUV&{uDtiD z;m0Ui@l<v%v+W9ml^&KKW*;^mrVE#KS07d#*Xj<74=WGL4r>){6!e|Na8PkKrn^4! zd1OUo>3iL+8Y|?vJk=5|9JRyq>$o!gBRglQjZmMM59cj%7}YHbf0e;^-(C*V)!^O( zU){IC>GVCSyZ4s%rBcK$0dqcreVZ?;D0-VOuBdmrvhTh7HeXCp@wQgs;I$TXWd2<A z#c(Y(<n5)t@^3l;>w0nua@u8|ME5ZF7Wy4CP5*M(;k#oQ8TS>oXBPcayG!={{)VJ= z3myJDuCFm1rmu$-c6;v6(rs3m`r@nSaP6f!%>TqIf=75?EB>_j^@I!8E@2>?^_!zv zk?dDm@g#ZF$Je*2X=K#PI=@L!<ad+!d?igLbVEAJ+mYc(<L+ji^^!3CR?L$bxFt$@ zfKC1%ueqt)x7ZX!noc@(B3i%&0ZAs7lZE<2v_qkM*~iiBEH^HTglo|~6dH8pwQ0X= zR(G3XFsF{@m9vigSL7wf=?bIy5xtB2pzu4_h9l>X1`oXbr3U<eZ#=P<R`5SGPmHw* zzwlafIq_gK^Ej6M@@HOpL7K|hh0F!_POX|3x<OplyERqu=1br|b;m&orjnWgeQ`PA zb|ZbY3k7=%r{2|wX`OvEK0k8tRu&GarTNH1&OXi@p(^z%m~T*P{b8y@KRc<dH9X`J zCm6I9|L9e|lcV7|4HoXDJ6?Ft)bEIe_uhI#jVm4FKXRtFk2?KXG4)iKIZmcE<TrMv z7D~QphBVV>QR29!#wvv{d@9~z`rVa&Iq*@v&R2`sbl$E{m2*GwQ+YHZbBtI{W=<}- zrdN^I{(^snBX%Ev4|{WU$t0jPpd&V<h~M>I(~d>x7Y4POrcefaiOH@bfL>joSu2zd z2;C^hYd_Wr%RzOI{oFK_=Ydsh7`LMx6cgX`RtipEIKSvnn4Qu8@=WmbexT57L;9*( zP+6+>Ki<E;H2cwymrch>$4>;E}r6qeh1FS&I;0(fda_vgwAT+y5Pjg2W?f1K@g z`SAr5y4{<qsO~9Sq)YO)y6Jvhg}}WMg1W3YL1m)JgO1wlNSCwE<=zEzciFtyVESEN zI`NK%L^Z|n;;S(?r}xjTJ(_R&=_1zqooM~3)NxZy!a!8Zo)pYVQHMXw6A9MUXjiL1 z#@#h*d=9(YK6NoxpC#3=Gjt?YG^8@TJdk~}l)uPvql)5&uv)L#;KWi+0V`txYXL27 z>2=7SBW5VY85Q{j;6QC^)E&i8>8u*9w#E<q{M!XJ{&-`cn`7{PmY-?si%E*<rke6( z3o2NX_+n)8zV`%wgFB3j-%s0b-{<;X<v2VsH_Q5E_kCl-c9^Krlk-`~#?nq+;c~5z zn?CTD4~y>H+9scescYK>1+_i^Q;X{3TR1L#XBnKez8SkulesOUx<Q31PQA}Rn)8Qz zciEu7(Y8Vs%`0z~cQ<ePy!q15gT@RK)-@UUM%~$VBzNpXJAo_0igd#aRqrdUh8%Wo zg~ka=&u6Wx?P!HkPOhKcp$UBf*hC!PE!+F3O%B=#tt?flzW(&Lob%50(54!+rLZL3 zn6d2Nt6Eq8J^t5*DdmQaxAgmLaZmc0VVg~p3%@XB&67(N3*w6wKcp-fyAm9AN98Um z-^^bM6ERYD9&L;<7E0QeKIg|HQzR}OEV&paIJt(hT1~d<_R&i7aW$;{Wc7n!HKSxX zcC9>27_8ktJ^5$(ULxksCO>zmkE|aeDe6;PyG@0SsJ#*I_l5Y3Uz%>(G}}3$J_$YD zdHZW&hlYfgaqZDZZ(nq>R&yaVQKRWZ)9YJ7@75fs$1ev3T9AUJxdz@dyewfw4_R5r zKUvXajGvghO;h`fJCvy<bQiOG1NV33%JYk#my3RVY>x&aiv?tQpvtfMvl1IO`9409 zsuQNlpdC$)xx>x*;PinglIEb$(n88}LrGfkV2AHI8O4yPepe$f=<+Q*u*0&bA9{59 z;v=irq9+9_Aq8I=rvBF7#VHJHDOd>{HqY1Ri{qRQ4Tt}@-N0PHh3bcK7DZLJ;}gBI zJl;gzW6+cCjP3t{NJ*O0Dhy=Mv7n~>aktq|I;OCEd#A7;h%=Y^Q@H4)=%x4LL-Gr0 ziASteM;`Tr`=A2rq2!Huky<w%hTv-R)ZriUqwD9QBU5RZgT+?vQ(w?HkSG11yo&l6 z>#?hF10@?*X!e-(&DpEhC$g?vn#@V@W0tL(lly+QQI@M*JY19vq_8@e=}OSKN!@@G z(w~*Vv8Qp}{XX!nBXrqrRD?4q!+eW@dKQUO*)QRqRl)@xwq*6MCC)KK|CXL$xOSE= z`SH(ivEdNJkJhYbTXz>a`sugsdT#|zFD9ApC@|i0NTHNIKOaKpgiFxrOwiHYj53!C z^+i>aBssQC#PL4GpGkBEQ06lhM5GVn`k5d7QX}HqFC;P;b`3?dh|1~lnM0Xz?D-_a zxRKmTBJ`<H#j?wQg$)CmZgEqZS;vBYkIchkdQs}eeG8q{4gPG1w^4-0N8ys%#mGtR zVwiQsC~YyzOP49thl;p8&mr62YagAlslTY{!!5!)BSR@UMkli_w%x_>b$)Js+c4Um zdF1XeN8wsK$x}C{aNbFbbJGat?reTaUv>Yn!9fnh6cV~3vtH2~^wOf@&c$n?+=u_@ zH`VrZ#c@)_uz@^-X!nn}FqiFWA6ETFmkXL&6dOD-<|*ldZ`aDt{A6_c@RA<+<zC?t zqtnu)lP`7IX4093ohNgu+lG?Q$AzyoASSyRJH|Uo>aLGZzsy?X(?DyTlM~;3-*(@J zLn|RZ{l<=pu^R9b`(huysh0V{E59iCWt;CE++W2PvjXuF2l@D_bFRXF-sc!<v_awC zuF#Djj-m)(!pk>jM_+c800LJ=aL@JBtumU1-*2Svn<}e>Cxn<61(<AITa&9Mm#_0H zrC!<7S@j;0pY?b+l_c_VZsCzyK^B0e+FD9t2j$fiqi8?A(ey8P;*y^2@ygH?4SMDn z&NV9TdU=XiEfOsjWX`@BNqp96^tS0ATdk`0-S1zK?2ix7Tz-RjX(qh|Qi<oTFpu+n zKDT+v<p9`MR@Xuoo%eG}LVpZ@)m>e-BpA;_`_)UJmVG2gtUt(TSv<o>6&^4avJMup zUa0^_*RqZ#_pcH5TO-b2pGkkz!te00JNnXH+@M0t%YZkCyo?PgSTKK;i;H^so*S;A zqS#tv{zC}=xdJYgXtS!~)wxFr_B31Ol5djoe=jo{Hd%H_Pj%{-b+0Td@~!0-{T_26 zhi(i+d<=EUGBQTEy!c>jeCGY{7$&9^77;q~Nb0-|-bdNC%$w9*+(avI+TvUKEa9m? zXG#kNDc_2E)5CG@l1>s{`21O}FpG=PMb;aCQzGs!YeWimJo|1xFg&JvMsC}HWtbtN zv8l%GFD@@9O~G7gUP5T^?=t1f?hIp$CxvkiBK_{tMFN6hXs>l5KWBrx!q${oc!<P` zawxoH8YW1Xj1`A2Qh&VoRZthSEZgAeFr{du(GGQ^M4fx_VDaPI@Kh{QjFOaK{CGu+ z^pBg_VZ*mHvM|_>gObsW+>}@SP@QCE&92bF8+s2EmYO%$9jNPz4V0z|m{hpk2S=cJ z=YbltDjk-0d;enpzWMXKv&-un>tO}+D!iUxcjT{uWmIWCQc{yhNH9-~clu?m{(I<A zTFJ+Y9^dzmrr#UJo{B->DK0mPC5;9w(`dfd?31H2Y|wu$#hZ9Tn7aS+sY{9du6n!} zHyYpt)vwCd4V55a-r>{yinL4%&a2mZ$9^qdN87f|7<t<L;u?szT}yjGT6t!X)=8<M z_{X51Q9(oBq4@<=L5jFrL3*}g#Uj;DHyWob$HF;S<H7RnrUFB^o6>;+Pjq!^w82*^ znY&D>hV9JU8G_QBI|Xbh3u&Cl%!docfvFGMk|acuuIgPpvwjgURK?MeX*~5bFPve& z`kGmRb%%%XvM^wNrU|AIV-eDSqF!};twEWzb(fDUTs-hp;D9D+i|H}nL7_>tOolB= zspGD&fVOM!+h=tVMd_DgqNwRQiL@hW_g*qCMw!k_@rXWp-@XtlaP{GZn3f=wxzPR; z-pOY<v;4W6a*hgJ<JDy>{gV@8*G=TAz`t^Il2BPljO{49Ir(k9a29^@JHkc5gd{6D z_0T-#Z5M~HqDe!W@9aO`+L><^Qck*S=d!u)ED&v;>bT0de%5u#PG^+M*zsw5+4zO> zzMn+{AW+j(CvA6_WM4KkaP%WQXLn!na^#De_eFqtP$#TQ;>2CF^dt@59v2YkyV}K< zNI6Dbop)1h^~#sT)11cPsoy+L*YFAF`_<42e0TrD*mu_bnce8s^BD+D$^3KOZ$&)E zlO~FeyH7FYyeP%0BIhzF$4=O_d&7;~Q%keN-FkSuJ=`T@qBW$<Ql+q<Bpyp?knQ#A zsp^x*#SNdsP8A$veU*Q3rohkb+?Y_z+c$<1bA2r-qt{Z3QnFWX`pWOED(<Z&|7-6c z2W^}RuUxamxEQ{9e|cw#{H{2Uz!dVp=j8`8pH%aD#DU3XySYDo_?u~2+_~a{$@|5T zWhkG-58a=;KX&VexUJl&cdx`mVwB8v;t#tHW2YrIL^g|XcZ!c{zH^4vgqc*d#0z7T ztk&o^>&zd-y}9~_YgcKvenq$VsQe$|Amo7M0Ni|IE<%5xbl@M}xNBaK5qF1em%Uhz z+o#){rr1GV*QLTlKD^R=ULicsJjY>0#l);(gSuFWC+vlJj-$_uvbqM`)9m)q30r#g z5lgg!S;2O-g8IOOEkf65c|`w{@H)Q<$mUa3R?p^>Uxr}_t}VkLCVhV1j&FZAk@?+z zSGyR)$V4b8^LF50*E4Zd!pWP=y8X1*pF4wll%4NaSs))?@`1J7eudklOkvWy^1jL2 zMx2w$oW#ak{b0MQPZOE;_Sy<T1t0$Czh|o&o46OB?_Wui?QpZwQQ`HlWsAK}W7(aS zpu`W)^xt!|n=J3>&$?IYD0E$@kj!?G-l)}|y<XX&&~>$<EW3SZq6N`qw>%Ty{$+x7 z(r))!akhO}viX*yPg~g<`|h1$8@sT|4_ow`QDtjfyL!bo4q<s8wivsdDy*|zL~%YQ zwT&B8`t_V)Pt92z!d`#49Tzq{!NPEmY0l!f>s~C}xFN28UUtJ%|2SvxP`r!WhIVlg z>#kAp7sjrO%W38r_A3D<H{#zRUVoklEiPgYEBP?1|H&r&Sy>VHE?=<*Vl%Gn*4j~N zSrJG6xvgKveSO4~9bvJY6Bl7$kyhr)wrgG7ls#%M>tBhHfBkWy;MSObrLc*v->sN+ z)$D1npI_p_K3l%GUkNqgsB>+=rDjV7`E!L8m@C<bb(kwTu5g*Gv94H~aKMPMxOt8E zY2RBhWNwo#Z-ViLWj#R{?7DM{@}G`=x7>!X2d4=~72@Mw+_X=nU$zwB--2WQ@0IsD z3a`g3I~2@%4()EnY!qZmS#@9kdECfx#t`|<y0U+EKJ{haU*hyky8LhH??EQvZj~&E zf7!c9Zs5%pHNw892?uV=vFd^*GS0DT;Ipm$3cKT@mdUba*z1<QUhkobBgZ=84=UN- zH@K}<YI=pi(fL+&wXg15OSG^0R~y@zd=iL9E^z!lM!vz1YRjj?%FD{Xt~eaO?}mN8 z8yMyPdec|`by?j@b&81gTJI^J^4hwVNqNUvw`u`DGpDmNHX2{^A%eH#tjDi&CM}Nn znqq#pJyMuWLf8wYs%CBO`Ko{UjOB68UTLt#)c)WYMi8>4?&sdPO-xdBeA_Zf>8S8( z(=Xe__ZBf-hyHt<ACcd2?)uM;<PhKBg4!I1U18!G$Jsm8I=;b}+O^j>cE{Q4zxI+` zea!?f{%*j1#<F1<Sy<Pm^6lK7maV>?3~!bqDZQLLXm+?$&DAV*<;*_Ex=1sQs#$9M zyieuRW{2z5lg$xnZs52_HJ#sPX6+Y;6?ez_mpJj}h(61V=@n&1-8;8qHo{D5UvJWy z)V`eLXbzkBOubZ7b`Q&zEyY{kGD+*mk$XI7SKm$T-ZHuFcaT=5!8Yypv}F?2JbknJ zjw46I1K-V8wQ%F4l%wwL>L0$FowY?TH~waa_1Y*+=bt)yQj7SjWWRFVk!qz`>Q%Q- zrLCVC{hU6aU*lf+wmG8T($RM_zqZ98tf0)LWm2G7>a723bwVhnEJRuS+r0X>U$<h^ zMDyHCdB1yC|7ngGnpktl&+P2C(eZmdX4BzkRuS>(GxhHlU$d!*8Jo4K-SEE7+J93% z^|gqFwXgYi580yUY}RI0s2n3rhg;O6_r)E<eX9?SPo-t5%I%klukQEHInz}G7CD;v z7p_eSuT_``1?QDWY3ARJXe&Xia0rFR*Vap{o|)3g{}54DQWv=4<>=~HZIljs{JQOH zrisi1@8c%Zr7BvceNsy5#kucp1UYgvW3=<}x^FGMXje^Ie9`i0FR5=)FAh&A@z!1m zGvxSvLu-ZBaCZ4--}&gs=8`*sxVPz2imiE?F;Fyb??=Dm5yS|YHTd6C6YcK*{0vK< z4jXpjmlgZEk5j1SKfl%^b@hF)RbmNZD%CH%qZTpoEIt3nP0D}l`fqC2nyqt6mUMig zOSrZ3qs$;FiMf^KJ>S>E7A;ynjU|YQ7M&Gl!`Yt|lIiVZALB?JLa%Wj$NakbY_>G> z@n$`5zI?1sVoc%@d_Dg0_ybP8nfplX-*=9pz`X05g`D7VKlgmhnDHqqLCpN3|BK~G zm-6Mvwbk)?blh~@g1mB4iEv=hyU(GH=M6MlCi~OFupg<t_18<%BaTmepI9*@^!l^K zFTu^Y;|9RvUh4XOd=gCQqsKjdjsCs%u7TsC-~oGMLJ5LU&l;=gQ&Q3{G&x^^5n?Kw z2}`X-Oi};5A^3X4;=T5YmSOkB;}&@Y&FGP*yO)>$G>45?DCvYH)*|}rHCL<+{qEzq z4cD5~2|H#UtcG2yQEaQ5{HuR)0{@+KLyNXgK}oYtKI>s!?H21cVu$(xWoPYu&0Q)Y zqv4-GHTQz3t{Dq|?G*(>j=s73IGuFbcYd?PWfQ^8x{`mIVV%eQ{{ZnLowgY<73X8f z@nr7ahIRV1;qmC`?D`mXtyQqlAAd8JuLa{0)8Q+b_i={lyHg*jXAh`-4Y>p-i7P*` zdUgD3D}2=c9wN3JX^8%uwtP3B*E**ozXvEd^}8au?#)*V+tw9h+c|yH1s}@01tn5# zK26m#$stba1C#dzgCA8R>QK&RNq;1+>iFbW*9BWwS9b{N9uL~bUry??lhimzA@zQ1 zRp+iGyxGHrD*HMY)8i)rXZ0CtjkXnUTaKIl0qTTvf2z*ea)iw|cO4HGz60?mJ%8RO zX0<Cl|JTYM{SRti4?f%Ab|1L9-Kp!_N4cbnP6wyRN!8r#D>k+Yfs=l9Qs3&tR`062 zZ{D|^CHUz7j-?JZJpX&yFv;G@Z1`c?g=<dpE3eaCe%y>%L}M@3xvW)mIGg1<tyDcY zJ}zO(e&%m_`<F>jEB|;sWOav-*-fXVsy*k|-zKTYZqOfhuZNheF5X9f{%^XJG&?;a zTF&{Psl=vjmy5EY#HJ(cWeFm#UTZhFPeC+Wb8IrMh0b|1vl<aheOCWVe_a@MvO{pQ zqFVUg<{d+hLE(^1N340B!;h5~L{Yj_kS?JL>HPY;wSbe^)fp4t#j2WWgzcW-(|gw6 zt-&^*lInKn1HIy$yC%TmN(OynoVACdt?qmb+v;Eb>`;}*4om9a){K;byT3FDVlpZh zUHTd71Nej=8Q1n`jh9O+{$Qqm?|#4SIN+;gyZQIak1q~+nVsQOeUri>?SY6*A)X~} z_y<)^^5=_mijJX?UEMdwmssf(XTl7>-cORAd0_Z8B1u~3xE65ybX>D<{xX+>e&)CR zHN8k^`>TD#hnLkM)6QkFu=Bw2<2q$5PZ;W_U-?9;#Wz<yrY+-(ImLhTw}#C29!WeZ zy!FXWDM$&Q9Ut8=$Q94@XonyS_`g~khxd#lTATQ|3@umaztNqm_rKA!+2I_0VfqXT z9}_V9)wFJbZs+U+|2SS1zjJeA06<C#`R?R4aGCGRGq7Fjg#k>HVs9RQAf0L1Q8+!| zDQ_K#|Fb#fwD{U)XS33D-jXH5FlznLRxbD=a=vQ`9ac=%`)d^b5o+1__LEjS<#9l7 zcy$r2#`hQj>me?Y*TenWrYw`vntFHtwjDB?M1<*oj0t-FY^XM5T*&}zXROd2&<T|5 ztSXrL&FoTl&#=p=AXI9QAtH*YYbf33@EiY?sg-kzN>|(rZ-4l7G0tz1t@){#FN?UZ zQbt1;r#qct)OT~-rr;KL@2Tp@FR$+osHf)GK8k&mz!`mEgDvwKJZk8gSL*nCEB9wm zdQn<-XX@Ra{xjc78of?KcF{u>ET^c0k-sIhOa8Ss#8UL{UE^n#Ru3@n-}UEGc+bR) z`<HbT;O98@Yx<lbG~{J%H<`(BRGeSnK0S1xVnsvwVYpc6Yhfh{vU)lo-dsH6*&@^) zAQWW6c_q`zTT>)9T6sI|w9;a}Kao4{>>T!RJ4(vNLOz1ZE4-d(fEa&4{xEh0C&0?o zWg!(f&Hty&l`MQ)s8Kj>khBip_U-EWAv|jAWFq1zSvP8zJR=<cwAJK+V{%E>ps~|) zwOmBaJ-!Eb6~fb!TkCU7v5&t*o@LX(9dFs-C!YTD^q>COfu~>D6mHE2RNv?R8DJ!G zDMQ2t^H0nxhwbUxA&wtREYO6JE?&K6!wbES*-zb1JZjGDei}&<;SOx&T(@1P{<it6 zcTz9(aktF{nxg>es>tJ*lLf8A0?OSburct+<7UmJM+fge5V{R7obTq!UvSO5p|5p_ z5Cs$T_qi#a{yX2x+jHak)1c?8M|{%7ESooaXnUAG^oY<aytB2NeZU*Eu9U@P{-e(G z<!X1%`OQ!I5w0-p=Aop%Fw3(*CWTFrZF?u<uLz-?47<(!8|xb8D#rmdN+XZg9oK=# z*eIKwI}{4XIpOxJj)O%6ao@UEyEA@0<QZEVRoZKzm!6DdX<xr_abVwpLTDf5rsLK| z%IT|$0<K8W51w_%?0;)$FGIJZ6Sq(E`_w}-=IEX1Q2j$kSTRz@aIAmP?rTy3Sn^!u z=$8F2kn503|MOl4^=XRJ3CS_EU+H{)EA_tVsZ>$y7A>(HE!w+Hja&2QQuzsb>h8YP zKV(Xv6XsY?U8g+qsrWdy)|1t{w$OIiQG<<qv)>U}kO?10yL217!qOS*Iy6B`B5a2O zlv1Y#M@kkoic8NM^+0_hi+qQ(`sc>jCK5KIA_CL)JNK1+5l)KxllnIOS$$)a`P*L@ zwJsd;5Av1MPnoo`mGen4NN?8jtQ)Rt+A-$JyZs;-i|+MZ?z^oYVNkyC^-x~)>F>4U zs23LZM8esc4x2EhYG4J&u+s3TXO4$n;WGK@a<rCjKY?B9K1;~V`Hw$bDbM&{o<p5C zrS2(zeyN*wqWer*IK|GTzRlpv=C8t;>dIl}mn?H)vZH$Ia@5zO)^sM&NK5C+KSLG; zrPM2mPtJ>bp^#3ec3GPqdd5Xb|24^VyBbZONO5$}{Zcfmo&(Hs*D3YRwzvhk10VCu zH_e?Rti|{<ht`i?V+uktD<doLcAtx)F}tMby5PRP?nB|m{;o7q{g`xgHt9$vDxfy= z#^~Wh%m9r?2bcadwl?-dZ1+=4U*^E6&}Ko&$oSjsGj2JQ-dyJzk`p!fPSy8f2TxzY zY3|%Qb?b3v5mNEgr!JOMX8)f1*mztb_Rfze&y3^jgp*hwmV1$h6jJ<8nnaY@lobQM zJZ^9dr~_j08}z*ThmY(?v=nOf9#kxmG^zWXgW~B@3(*~VblXEcM@2Lk<}3uiRYLUN zUV|o!2+-y9Wf5l6^sMl*7{83}SPe&IX7^6fbJtY63#Gc58otC_@~3nCFh$`T&Nn~E z64k04m;_wZUZ2A!c7OOFni2mj#dWM#ZT}#twaxO7@q*K$dd<HN^eaD|mwltE3n&b- z{3M#{6PYr;xX;8g%u;Z0;>zXRcPTg9&|1Vxyd!oo_a8nxvqfdsc;>mw@uVomeL2L6 zsp@ChS>1JAJNnQfqi)vAwUXJ-(xVAd4ZS?Kct2p1pIjaPr$JxM6Wc{+8B&M@rSw9X z!;S5prCQ|gF1oJoxvkR1Tt+q+PB}0shH=FXS_(JR4=x|xApO}itw)^U9z3v!l2hJe za*L;+mLZDqmu|(r15zGx$Sd>PHahDQl-Dy(8FH`%DYtge&hHKRvc@O07hfjQg^~_{ z-QCNk^yewf57U_)Xv-fmZ9i9(`Ul6l|4qc9!+RJqD94AQZL?!XxG9X)GSf?<*B#Pc zi}&!ioZam<-cOgC;}xItjuk`GH{&%;DbALzGoAff3q15ab#yA>2kZC4Q&ei=i|-d@ z)`zfcs$b6-30yJcN8C828h)!V3~~SVAvH<~MWal7faT{kilFK_$fw=4Z*|i<eM$7K zEt2*8$A@P{K280&nY-6iLurHbcvDP%PBp&E!M~XPy!b_8V$|s+>(I{q%6(2IYb-C% zu=c5d*Iyb2`?EsrZ%&o^yMc>|YKC8>iLPq35*PagDCLxIxbxOf{<{UQQeD~QYWH-! z`NEs_ZWArj^Y){J6riBI(?ShmyLe6Nn+4sYr{|xi*OnG<KaBYhz{|NuwZawmN+9~X z=dGzo_E*Tu-_s}-E)&Of^6d2%ddV|^r=bNAHU52s+ALL;;mr*n3BAxXIxJn3KmUzx zL~~dQLhs3L2e)7Z?_jR*nMLg&o?sN`rSqG(FVBNWvLW*k!nSop8hQ#ZYWEET_=Tdx zS!U$QKXH|aRA}$7noj4qTo&@w6A>!W%d7WJC4O;txv9+}`sMGbNag6mIdiYpEW@w! z%KVXexl3I6yEnBpA6;o(`@m~@HEnFpJxSP1bB2jZ9p{hlR-z0;eiYo`kdKz~d@=0) zu<!XE#TkMp7VdLW=e#P}ydAr*`c}hVd7;}dcHn&Ul;_#2dP6mu(E(drrap{QN$h@R zH{;dlju%Dq>3uqDdzuPUqz`mcUy@@AkHqGxu~c)#NRUmL8ZT0q?uVF)?cIHw-_wEU zUlX^BiL&-jEPF<QkI6W@Jc6RYs=M^ZeyCcm{gwHg?Pddzmb*`%FRN$pH8P*J(sMCD zEozcYDuF}jX3z`SXUF$8F2M%<{a<b#3iSSJSYwSlDjHp-lucK#<2kFMdLfB51i8Ap zYW(dIeSik>KJmD`y2^~;KQ7eyADz#XU0V3Ix9UKr($(=`>q|$~{V|8}N>BE-=*Jq$ zG|PTZ`oG{p9MQ5uZ#SkUoz6eV|F%L^FVijBs(AW_LfT-3=mv+@6I5u(IrQ8MHJ+zY zzxq6AgS9-^`3g4+Pl>$|xTQ1SSb*#*vYqWWMcyk2eiCAML(8b}2bXH@RtQfhQ)^#6 zy1h5GSLodo%U$JxS)6J_(WIP<`<#1B@6&lkC5H15h3Oq1_18uv+e<5jQ{ND+1t}?> zOFT{;&ZefRdB5eakIEIbDJgnQoGZv<56nYIbme)e<|VgO=ACoMjjO)>q_nur&01AM z%BB4CXdZu^SDgygTgi}$Jhrer#$Z<z?X&T3xVM}a<y2E&A7^f)d?#iC%_~!B#ZIJc z8<z*{^OqT!?ztCMi`CKXY5EEFDbPzz=<aJ*Gv$8E94I(@-LqLut2xDXw|YONqM*}$ zvBgESp9-pFCW_EKyO5j@7{*8bFj&)2;#nh`NA3I+ub%-!MZ-rNy2q5J!j_XR1B8`u z_T*)6OQklWW<jX+EW&!Q)Z7`Z<~Kfj-&Fz&ER9kp?69i3F~;)rwZd*!^;8@(0)AzF zIUUT;QFvKOkXtp1YCQ9`M(l6DgxA{FAgv(}xx`y)hIgd`-Y*~ap%@mve(&*{H%AXU zn}%tq8@UdYMu?(V`L>62mmUl4tmh-v(R@5ToE@Xz7ABcv0k|=DY@*J6v^-uWi@E$V zZT^df{B;vw21SJ<(+cUqJu$j%HNy?2M3S&)M`mx~`0}6Yt)ErRL;@)f#xI{AEM)eq z^!2dD`*Y@UehU4hGAx0zMe9E@C0eWh%r5#8<>XCy)u)^~ByNXWJPeNBAI#NtuX^ik z^T<oZ@5Xoef%ukkqd9LWWu1JhKSSpq8s92szqhC+nJK^p#7gR=1-B)}ccfVJ255vT zuGIae=-!h{kSUfZuL#Unk-5WwsgZf)P;NJ!zp<stQaxsOq+_>IqIr9qKuyk4-+#6k z7X37`;GT=H>h&@Ww4J_cZCh?~U3XrJcHT&3TV7o!AScEKwucqzi?tVI8QqF07#R-; z9X~axe!LP#FsR~dC)N7mDYQR_xN@Sq&|qWR`%@1WEMg%a+koERcP7efjXJ%h%BuM< zdp{g1<U5nfw*n1kmB`nd)FktmzVR2VZ3AA9q|IJq-Uwu#0O9$237T<i+WnUu7kSBE z(JFjHzUOlOrbtct5R$)^p8rl)s&WbML#t7bNX4|JUGrF1!e@1)RlZ8x;eSW1+3UNz z!n?zDhehnovQmLeO{>(Vv*|>$@-5sLb!Q5~g7NS2R$efab3N#p_1$Qr&iJ<03>nov z<@JJjZt;Ms0#09#7=q_gn_jsaStICLIX%NXnftL&KVy&DyF-E8@kBeJ<9Y2LcGYY4 z+bjXsmAv|Vh3DUTd8n4n<>wF&z4c{o7<a$Hh#z??#pije<Qs{#7*%RZQH&M@@_IaJ z(M|C=fBU{TChlutz@fXxtY<`on>}??`*`q!D5I@+<(d4sR<eFi)Sl(tUoSwMj%3Qk zI<+3yr)g*g=n?m~5_YtnaZF!*r<bUuTVtRg&IY!H6<rY<RJl|*6=Z|8x)`P!@>&_G zf-AM~<G}gwek~~6dDq|#tL%JNrMYj=)tNVpD}1{xj2~8*Pd0N;ANNR!SN`xL_jLwe zb5W<#FRv%fiAH(c-chpn#b~XvQYp=NQ~QOphz|qQHT7BtgXJ-!dF+k(uZ;bcNPrae z*KyT8(Ra1Cb1mnga#g!xLgA}e-i{7a=)0TPQN3zjMnI{nYK@4NrcSQTFFPO4+{#oE z7kX7HEx=4HM@8~!Z>hT+s`S82R$VQ+BAVDbKHc7fYkfZ)e*I90;8yRk1iJBommFGi zOO0+Bs}{+m$(K)c<h@$;qnyE&-sD5+Ju_3@QkKCT&u-YGOSx6IFe)^UXH2>5A8XR( z6;<0edurKU`(c0=W0cAK*xfI)D=_AJ{>I}n`J|EY#Juqp^Kv`v{Hpjh`tzilUp{hP z4N(;eE%1=b2%#y@Lgz{--ki|YIl`(vHrnm;4l}ypz5aP=S_PZst$@z8ep^dsy2gzO z^$Y8(9?)6(d(CFi+vVyZ0jDSOiJzVPs(Oo0!oyef1KD(l=53FcdYq`Y^g}aaE45<( z7}c<W7m-85E*?@ls>i?AG5uQK*!(O1sg4rHr|+eWn#g_+T($|Y`=@Npt!$P^uypA; zeU}>@)o2<spssGy8`s*X^}1K;vVyq3eY0}S%iNXuTq(8|`Jvp~ELz#F%Kth$s{AI} zTM#$sRBKvwOm5ST`>@Ma%4mLjc3-XE_EV{a5b92=&NXlL?ovdM_WR$DTT18iCJOl8 z{5c(tRhZXU`l|5m;n2>*n3i*`{lEN2%O1&815)k*bj#lCip8Pd9JqJ-=x*s{*k)Oz zv#phjbr<}luHI9wCg}95s;(r2YK`rjHTlE$BRWX{XMRm9`XRH3+bCrS{<<0egMy$W zcXpnT?NGDgB{h~UB|CqA%}A#OgGKqjUZS72M9P<pv&k-cAy~EKAMWhuyYuK(3haY2 z^RgeB?mX4F^?5kUL{!!Nx+)DH-CDirMQfFx)3~g+=Bm$Lt^bax>Kv5^24uU(?0+FK zDZz#1_Zx%Ck5`N-v%PN(D++$JKbPoWstJtmqxdtlM|2s_-SrRsJh|tDJ$m1~sAcPa zG@Nl*_nqRg2$fUUd&3A|Z6#cEeV)d4t!FqRy6*eM$0D&#U9sZlX*<FaFGrjX*wnf~ zt!Fr1-`qdz$Q-go9$L4sXYN^7w%5y8oNszaF^2TgVjV+X*P<W8cxmyDVXkW(Pquk| zg#-x!oEhRkIx!$_$O1?wX{HRZ2^*3%`{t#E4I#mSdHRf!{~-?G8ZjVd$OyPb(o7yg z2qVdwz4H>nNT?7yPo05=3h@E#87JsCjvshO(o7xV6TU&#?4Oqxz5x{u%%9G92^A6o zxHD9Ncf^4I5H}=GZ0zx5%b*cXh2%+gSkLGoF=0l4n{0<~Od6uhF!&$o^W@6l60U}7 zNOl9B@k6r0a)1rlj?kDg#E@YP*g!SJ#{USDZ~^2?n!<X<4oL|M0O!b4_{QWR+6*(` z9OO%E?Dgb0Ax`)JwhTr<kr)s?WB@3VT9bxogn7uVJ@aD1JdhK1o-!j9Ize(k9Wfw& z$Qr04wWbVl3EPre`{reZZ6T+Dd4`N?$cYfZm2rY%hs=OGq}JpiCSeV7Ywx_2um<!E zJ5QTY0DZ#;aAXJocZdQ1A!U5BD9lU3vVx}}k0c~5pp509f_aI_6yRwxHVT;ldGv5x zI$^>g(6@nk=8O*L8zF!vLkCzT1|$wS1FNLg)FC0^5OQn(yy6Ms;DB?9$q3MtjEzH< zLmqt`C_tHzECc6~u$-VN<dK4u0+a{-$7n!#GByU84SDo(m;jCVWJ%bLggp+0fjp9t z+(09igAukPCZDhbWHaQ^&*2C(5|S0*DH4_&^o2Z9k>`NM0S*CpikQp<`jWB#F=7vg zArOF17Kb%SST;})A|@f3fdDKA9jr-ArUC`Y*l1+>|ClfdN*baPW+6j8^P<8m5P+Sh z$Vh|$Jef6v4iF}iqlPX4{~@Fz5HP^O1Gf^B89^s9HXd0A5&Jl7fdE3X9NbF6a)C|| zG39@Z1%`;pbRa;+#v+f4y&T6yeDZOTggq`EA43WsALBS-EZ{>jHWB#-BKC8*0%St6 z61+{q@_-K^Vk%M(AP;bez}v*+|CmG+G8$s<L0<q6c%w5gHA#XMq=eX$kQ4v{i#`QY z6OB%Rl>g8A(B=SwU?dI4kt8@leTY2;DGDG4(5!GA(TE<@CriX3iy`*^STf#70=`U= zU<bt@_GBa*kc>ss!<UIh)SwtyA_n;wV(&#?{U5`UB)CBjh&>gl3?vVrd0{Nk=rrg- zmPkN0LhSu$J0O{0BoCi3cIZhG5&;-t(bTX6(TEacCrd;llc6U)=*xf+-bf6VAW5)+ zCrkz~8bEWxr9>kJ(2^_>kF12A^r5W)BZ84ETuPGQ0xh8@DaaFc8ulU@(Sj;uiCAPF z^rRPU3XI^5q+l<S1P7=BJxM;!{yBn0pD<=HkSvjiY=fTkqn&^ef{`M;K$74A1ED9W zNG)Jw04)eF5RLw0rBTRu$g&5m56Iw+L}5-+DJ#eTStcQA02wTr66PctQGg8p&-&1o zfDFM%2F@ata)QQ?WeQRfkQqR;!&yWl1ZYeyjYF0}mjAIzypbeqMJi<nWg*LCBo|PL zMKi!wL?aqdmRuTxd<t3iqK*H@aHLXh&<C<iMXCdp189C2M>JvreaNK=$k&i%KiUDP zBp4~cCyXDONJ26Jx3FkhSdC~z1@e<iqmhrGi5~P7;1=FU99AQhvVkW|0k}1Q=7wJp zjTk`(a%nuW7Mkco+W@x+Msn~gQYjbc08OMIPZ%>CL^PrUwaKNi$Y;<*FZvqr6K{ls zgGi+upf)s-j1)SKlS7{{7BGxlnuzRzCi>AXz)yma61+hw<pIN>iBzO6@N)ny3~vyP z{$o(zyo@ji0R!`h3=9Mi$ebBS049>-hEM=Zf>MS!guxR8pC);+g0zrI5|Rqw!=fqR z)5MWeAT8M|3K<Kj^q?;SeE5+w|6^Aml@z2nz&C(qgVTs3^xze;R~)hgQt3lm0DOcI zX*iAK#R;A;4j>PUM!=@T5o%D1>=lDVLn^&!BOnhyA_1F{yx2i0NF^D0!ua5E;>c;x zo9vZ<d<m)aqwRq_!iYRPPV(Xgy&;uUq$-d%fIeZAAouaVa%3vh)Pptv?C~RFusq3& z4djNJl8}smJr+#^%M(XVSUj>CYU)E<1NMXwS-6Je#Rb|xO({q@z<vPD1=kQq7(g4c z*9q%In*m?(BT}$0$%_L#2Q?)l1%R(uG!yJg9H9lzk-cJ(1yIuoBaA4*ze!#^;C-km z6{!P!9Y71gzlkHv;C-@JBC-Q&>PI^RU;kr*qy<)x84657(g9jnG!-mJ9H9W2$qP}) zL@2NaeF@OQkBI({U4sHskVrsl0L=jx5=Ri=HS$6n5(5SHp;3SqVMGQlBrR})CyWPZ z!=f2sC*lYVs7PLjLFPb#y=W7l4L>3YJCPRHK}9Gq8F|74;BUkcCh!h<ApzL}1@@yI zfi}X30{o4%zzyDk0#lJ1K-&QNgi(RQ<b`Nt2DILTHUvWPBjPYXT3`c(q4gvr3lNG$ z)4>37<b=f|>!I~Nv@H-y7?FcJNef({3$&hsR02W=&^&M_afA_cAupV;UNi{o;75?~ zL(&2Vs0XblBSnB6ESd#=NF1R9^~ejc$RcR{gb_xR;C<2p4;TThry|b-I|FDDc%L}J z0!EM*5|Q1|dOz9~*!ho9k~mpG3Wy;Ia|)orqE5k-M43|{1(`Dn69qB!pe_J3c$qW* zV;3QY6wDcbW&p(s#}H-c!HZ<hILvd1p$}ya&=6##;TRGpCwRhGfp{#69@ZzyP=lgm z&KOK4#L$bn3dG}OBw&3KCp#z#F(hM77%$vMlsOH$lQ|PG4G=>=$_|Jp$jHNeBu;M7 z9b!nuoCV?sP$!HMWFvD%W0Ihh9@J&P052m3i;+0lKsG2P2}2JUU{Tbt7*Xbg#bYX< zls=RdU_g+Og<p_3xj+jjB?Ti57!05|;TJ?12GD}cdBS>8roaGRMhf;IadLplP)ag} z7Z|{zPQxBV8Cp=8%o&Txg;Gu!K}HcKk~n$504OCDqX`TQpafweQHB`|Aaf>STA`GF zloK%UA7dwFv4RN5I0-`yNMccxFgsC(0z{CrqA+ogaSuu#ki^S~{*M_!#wi#HKym=Z z4nHBvAV4E>Rve}jGVViJ0+Iw78Tbh)ixWIyoIn{C#Q<9pWoSSda#jo`3o`CS83Sc_ z8A;fZl*JCpK*q_K6UGlu5M`LaTjZ<+%qz&aALRg)5o8qL2~rj}cndO4#i#*g1E>>5 z1@e)zqA_XEXb<WNa2+or4y%x|*g!sLGzoJWxQ<29!YV|W6BduDfkyjKHo$d)j2zrV z%Hjg;q0tnKJaByg#SJ$RWf(zwa@Gm!MO_0H@iIs_kd(y%YC)sP7(rkWi(-ZYi86Gc z7C9>xQwWWoFoKK{yiUsE0YjnDR15$t4xohLb)pOl7)s7c#B@TV{U{e;@ju2uvSI~k zAlW1gCBTJ6QNRqu%2OZ>*(wSX1IhNFE&^Qm$}|6C29WG=3P6Bs0L2EU5G(0H1F}^d z<^?3%hq3^;2$j-s3dxESJYnp>Q!EMr8xt$3K}oVz4CV<W+lw*+p5iMdU}KUMJ17as zCSy(*A3RE|JPlqaTP0wcAlZJDJ@AxJDG!g5thmAJkZdYO1$a7uI$@L`7uhNrlLA%s zpbP+Oe5DvHOR{1ExuB{f3<F?|MbW^r#L5#EkEw#H`cT$@HK9@#t|D1+f!0t}3Pu*N z9zb!yRm4gL(3)&@!g^6=z$Cs>3icscae(SjRWgPjn8c!(U>{;7EvQbmipAtZRVR#4 zsR*x-ta!j+s45ks4NMN8gy0ooB{LXIwo1gbLsk7KXJGO_#!terf=rN45{4E~$D*iU zeqtpB$VA3PVG<yp9@Hg39bYN>KV}B`q+p}~^#K$IoKLJofM#S|9Ht!d=|iCabwZ^K zoKM1Wg8#80Mqwo~)H^RJtORvp=V>x>piVrQJ%a~uA(CT;OaK=Wlzf74F_>(~rx#@c zyvA2b!VV-HJE#EpBxAUN*H{!I>_Duf0TsyqWBn*c;5DIA0iGt|xIsV2ClzxJcs+m; zfTxL-OrRebmw;)8eEws>_)2kDn}lNng`kxr3^NdnMbW|9#7Zhqh>VNIq(dt`D8v7; zb`p*YbcR+^Fp5C%0E!20Css0o&SYFXrVd)^L)iksgi1O1gy}*n$rxc^6N_Sj!-$o1 zpe`8~i-Dn)-v4Xt-s57}{|AmEx^CB1DP1HZbY-`whDDKcl0gb3QPf$5aY_xgLQPkd zj$Pz39XiLjoP=&D)b1unD!wCK+;+Aj_PEVZB-Q?s-{bN7{{HZIeExa<@t)UbXZG{o zJ|4UK2}Iyt#W@FJH}jB-jAZlR-uggWqF;H=5J_SmW{@|re`SH;a4&t%mFQ<4nj=Zr zzd7W$j2cB~DutsE4VJ%`+=p4#QuASbg>Vv~$?%O34a_o=91ZKszh~Nr56e#@Phpl) zYB{V=3#Sq341YZ0gIQ*icCdauWkjSag(KO1vH-KJqn5zA3gHxDF~c`O>@mwM(iqN_ zQ`*F0rEn}_&+<ny8MOw^rG-w!ZH8}#L}HdX<V-lXo|-`1Rth;tB+E}HFJqS9vH*o} zGBK6m8zWroSSG0h2gs?h#8jnl48mpk`^YTpSS{uIJv+njO_4zCST;Ew4ydQb6K9k{ zT_lj@<K#K)n3P%t2hhS9#7M@&j%AS+aFd+k5K&5@Cc<O+sbnE`td3d=Hz|Zui71B8 zMMg4DxT&79BD$4A10;^+XOP#hV=`(T+(Zjqh;D{&j>KWda>#F4mwtW~RztI9`Lp3? zN;bVA6n@5FISmtSwzHV5%h_f-R`WdysihXe&I+L&VaAjjA$nLyCOHmvmQ!O0Go^4e zqQ{o+C2?#do5%EJHfY({vVFB(_BOVdNZzH9pO1+YtZ}{}JWt8)Yw&}=MI;0}*Hb3M zVWm(9@n_4^$Um_VDYX)IriIgq!%X>j#2*XE9?81&@=Gxh&6?)B!Z(#H-mn_J$@FD6 znA?Q2ebO#Rn{Z52KN5x1Q9r{a3L!)UGvy|TBNmcHn!+V=N|y*$3dbRiZ25jtf_=|u zp)>J_DK|r6u#g<m9WJS-EQv=-p*|ABmZy^yScr@YflIz+9!$A0G7;;{B=zB~a!Ql% zPzp7WiEQ~kG8gNtr53|m6+-*(St!<-O*+F{>nSs$QYq9!LfLYhEWtXZR4}}i7CI7@ zOt~pCl3BxV<&*)DpcHB$-fVd)c@pcaqn5#M6+#Chfhp%A-dJZAIg;5BpOivFWDi@O zLEgYRWz>53EiH5<J~8Fy$R4aShnx$)t*5?as!@m*8^4!K#k^{%1+cY3HHk1_;*Ah3 z%qx@BfUV`!Xu{xsvox|8^O904U~5`6jmTr-$0L52S2k%6Th~*@M4nQmjrg(gBUv5w z6MRyknnEmN;!O|-%qxrJ!YAdF4zWzB8jCov@%za`nAb=~tDJ~tCf*E*#=LS!9(=N% zvLKq3Dh?9O#;22In3s%N3!nU!xiIm@h&gsWlhlL5<kUFAMX4Htn6vTw$Q<l?Ewu;^ zQ>Z3?&sJmCvq?udte!F@E-F>J$Z9qoCof>vrBo0cMyqBJ7nyidWF(siKb2GZ#5Sc$ z6PeA%r;^99>vdEB{8XWuN^E1|xyWqndKNj7SrhM+Dgz{ujn5#fvFkD_41P+hT!?o} zyg8DHUC$xC;ivV~NH!WVWPA3K8QAVx$_JjSP}vbSOpg&_i0#fKwcxpON`tUbsz$Oj zlEijPsX%xxt(r~{OwV`(!FFep4)EN1ic1hml@5ZiJtJ8i<qzLbs32lJ(_@0TV!N|Q zbNGgw(j(R@RpSs>wr4+i1l#>BOJ@4A8Vqd&Y+qfMgN*<ikaub27hwYmmYZ)2Co9?1 z24o~6XTvw@sfolhrAi;!&i15}RoHGB6$;;=RnEjSrpFA~j_uC*o@o*DlqwCxmhIU` z=41V}lpma=P}vjnm>y%q7VFO>4dEm?HInHe;cO31mSX)<YBiihs~m}&OphrNj`e4g zu5eO4Wlr2wsz$O@vIy(1qY!vdp>iOSnI0}8!1}XDTX;}T84}4#l@=mkdqy%D6%G&5 zDpz8N=`lxAu>KrU01wtvw#1N9Wr(D(JsIRJtp8i4p|F@ljAl4SEu%2a%;-_DhMYHw z7_GDz)iR3Z?2X=wY1Z=Qf6w+YobfI5G0p7gNw7ve&xqKkw9syu&vMeDf5$YXyydV4 zZ841)$tGc%S<yyts+^}yEKpjEZJES!_D3JYH0yXvhQG66F@;#ba7<c8GB-H2o;QK0 zQ(ACZHn5!Z=u4QUjJF0(r7fI@I)-D`vH{b~i5|(u5_U?9F)c<cXJ2$CmR`&Ag?$tj zlL<S9W87kdrDsNK!#;A}NT%DeoaNxrXR&lCZx!rATg)I%F&xvD<yd-l^fcI~o;RL2 zrL@p(xi26%YK30nz4wC2<yJw?qhmeKgfy4ExVq(9?P2bD=%BMhkiSvrvU7(wRGtWS zwu|*F4mp5Mz-3}!=HN033d&gAUW|e=1-F-=pp3_h#V9CzyjX&Q!oefOC@4;Nqyz=U z3hxr5pul*S1O;Ub&K0AeOvbqq6ciKukQfDJ7Jf*Af})EDicwHz;DHhp6ib{Eqo8=< zlmrDu6X%IhP^RKM2?~lCUM5CC@xaR@C@2PaoEQbg1&@=UpxEFa#V9EA@Q)G{l+n1J z7zM=+*OQ>27~?r&6qK2Gjsyio2lp4FpiIa8B`7Esc%v8v#S?FoprDMy9mOap5bh{J zK{3TIh*417@e2|Z6n#8KjDq5f$4F36tnt@k6qLF6YY7U920l@Yf?|(Pl%Sw+@#A6? z6drzDf`X!lhl){99Pv;I3d%(Mu^0u#8-FZ8LD9m!#V9BaxVHoa#T>5|qoB;jt0gEX zhWH*a3W_VfM}mT4i+>fPpa}4<5)=?-6s{!(W)iL?VL&m$GsFxiZg_@-0Yw}46EmPp z!~G--C=>7oF$2mRyg|Z%G8T6bGoVbt9V84W<8e~VfWpT~2?NUT2Nf{`iW43!VL-9M zUy2z(?iVdgfqKaM;W~rUdsZ(@(H)fhXvF#5ZN84iG|qDAC|wP)+g|A?=trZLKy1AZ zD6Y$~1v;Q;Oy{hU&ezqDxE++vhd6(@E!X+cXp(NK*ez2!3F4e~TcVRXjk8kvqtQ|6 z28eUUZH-RqbWX5zgKnzC?MI_iZoWD`(>N=nM!G&?w=}5{#QD)^IwwfFT-Qh9CYCOT zIDfjW(((Dh$W2!!b~_+-gE+-*OLb(^IDyh1jF`!|xdarq6H*l7kZ$XAWYamTrKqk< z@}p6WFAYJ!ETuO?HJ~h{O`#f4ex`$<8c-I~?obUV%jlg@4JZp}eW(VMpXdOn29!m# zGgJdg0KFBe0mX;5hH60Zr^BEcQ2gk*Pz@*uodneYBAQQYK)@`aeIOAizO+3g0%a*3 z35h^iNOK_(C_mGIkO-8;G!GJivW$*{M4&97^&k-_Khge>2$V&%BP0SPfR2Ggp!m=e zArUD4bSNYO#gF!eM4%w_9!Lbpz<gQ@0%i&A2MvJYOFKXVpe&`Mp#e}9(&o?rC_mGy zp#e}9)3c!gP?ph&(D46sfw6!#ga$zQiAJCSP!`dy&;Td_^mb?fgpI3m63@nbY!8a` zF6Zp*)!Eq_&v?cpFWMG+*!uFDX^qR)B>c8H+R3}|%D6_ewUrSaGa5ad9&hRx*XVNP zP4?#1YZGKwjypBf{>oXK&~Rms{8vaTqs_y?A*W(F`c5&!pbjqmBq%AcO<t4g)0ysF zp*Cw}S}mV^y5KH<Ej|=^sm=95YKJ<vse8-iZHWfWZ&eEOZOJZ$MUCC0WZbR4`Zmkf zpRq?8b#Dc`)ZKI;D*t?YD>NzW-i-W5c?R3oyce<+&Y#Pjc(ZPyLNrPS3DF4I3xPiG zQu|%ehn;IJs*a|nmeyOfZd$R#xBAQY&co4p&cR3XUk^$HA}3V4+lorwv=paxwANN` z%6-|bIEX*p72+Xpe5EjIU5jlBf0Fw({Pxh{OpUDj8_#*!h97y+xHP9@>ZThOJJxpf zm6ljt4*I31<it>ggj=bbb5rv1R^_j^gKwI}cE=o#y4}&%9oO!D^=;w>&wC%zxXFVa zm-ocP#h0%snHp~+v9@~F@t0Z7&QTh|^v@YOVU26I^%<FL`P5tPG<!#)L3e>w&EobG zuyxa=7buGA`P`b>bZOik{W4E%4NF(G?mM1d7<MEwyv(Paf2(n0L3&zrvf4n$T}!*p z_}~i79wP0gS}-=g_`1QT{)X<o9zH*BT%w+r0AEReY$e?teUeV}d{=SHN}bx&(GfAX zYeK=jrof<p7Ys6EO8iwnI3ts<uTygMUPm1#<_`ZBbMTIQ8TS73`wbV}ge4wowzMh5 zck9K<{x78^$wMk}>eE?Irngh0H&2*3CF((}32UdHJxl**cYWP9?wBH(S?P9{v(5P) z7tX!2C}_2~T>W=Prr}hd$!A8pgq-ele(*S~xHGJ{s^P$v{4eC(^Yt@NLm&3$t!R4m z%6d(4=hM?w)r%?`vY**F`dUk;ciNpeeJs-VIh6ja-fXLR=&HCkud3ybs^wXg{wK~x zeSTFv(D4M?UG2ZBI<6<-<-yEzpQ-2jyVSOItU6!Or}je!)TUPIUy3bv)vj65q}JW9 zP8r)I2su#PY3=jS?oDdf@WwT$-w)@u`@3wnuCh<uk+)@Nwb6ju{+FQ#5mv1>4zuPI zZS~;Iz8J?dzb&Y^ZP?xI<9FxDu2(xkwjarQ{dVQ_h-X9R&QCsHG^#qeS*`Bv+Vs4| zvpRXqn|@FA=j0Q`!2_!WpI(|WpOc+dI66IOXlS}v)Sl4ToG`fN?U~}U+n&B@?n-&x z5cS`y)}8*pRqKj_uP(MNT9Yt1r^C6ry|*iBwzJ>PiH+~zp{nGF_Rn$67r)>7Mv=hb zL-=5O(dydgU$ZPKCP($KJIaS&x$4cfHpHpAF>qB?UcyWERxkJQ&7!!3!LjYVaYOG1 z<yj%@_hsSzD`xu^9C}c6d@^ry(&dhOz5iSaeb_stzueU1MosD)1KU`0qE<blvG&zq zT6-^dQyanz-^#dR_}$O1PG0z?U#EYMV8`iT-(GHP8}fUYz%J!|gGq(dBuVsF!pn_i zuRlNEY%$?Rkx_N>{^59NkKl4~@S4;4`K|BZ*o49B12G|n+0T$mVS;@LgK5JjfV7=8 zz4&d*-oX*=y<gv7;T(8|WQGYA3_ra3hAsKuS6>g63g=GSEvOs5*4fMb@HshRaY?2{ zk|?6%q+e`xvN|c$=>Ofi@~r91;{4%_`Ix!2ZTpFofY%EieATIZ?Qs8M(ZQ5nnZwC{ zR<!ukyq$%>DetcL-HH6S1wIe)TQ)`6eBDx3NY;vsk$rva5{qkz6H@54UQY4CO`k<) zdwP~&sb7=JJdZ!FY_GdkmO*~v`}IeK-Fyf?POkFw^zzy>_T{!NMdaUaBZaF3o4>jS zmkk<z)P55?vr5tZ5jkE~DCqL5(bW+O?wVOC4<#@C`oN>?ow=>#>5kcR?gq1$52dPl zs#Fgn0*oJY_sajSgZCGn{Lj(3|LDB2HFx;9q|kg~ZY7RAy*%eb&|Sf0S3IJs($xw3 z#ChQ@F=~Eicd7s>maTJ&YP(x^P5ox$@X0lGU&hCNGT8b>I@z%}*pps<YJH;f8@DGa zBtsT9Dlc;QtTz41bJ%m`-Uk+R#B4A9m6gNi<l3nAm*q`3xx%HnIr#Vm&w_*epc6}F zznEWW+H%~6z4YX6=fIzTD*g4Q6r9dHGT&w^vwyFH+2V{Ad;hbmkG9T~w?z!acuVI# zy!X_8=`_EqWw!agQ5nm_V~)D6=N@&B!b^7^TiX@WyK7rv0n_~c`Kdmm%d;PaoT0}Z z9QXdb$+Aex13NBTK9x?J7{1UwW%Iede_d<yx-sLVLT9z%%FsU^nsvu;hku7>=G<G+ z>FBW1^U;Co)~MfFt<EW-3qGGN@wFS~q{)7pG{frJy_8(bGfO*?ISXe!TV#?jCeAT@ zl{BvmKl78(^SV=C9AcK#FTdc^iUc>LTv`7ilU5kr)hu;1Yf#+()b*gn_lQ7eZgaY% zyY|pJCy$&~!>wHb7uR3$joS2ZYI4eAmk-bHB#LYbCr8Hq+BCrZw?a$h*=&~n;A2JF zMz^GrQ~nk{M9Tg5&kL@pVzQPt{|)zT;?DY-8j*irLKv`-g>l~hoEVHacK^>HLr-rn zb-25^Iy9B%A#P0B(w!fZ-B>ikHu|G()#<wpr|vdfm{DXIw04!fnC86Tf4#d!d}VMA zM;&i^+rlL{I@&)m=w(yNHOq7|rrvtnGQ!3*<(k*7ld}(vJswszRMOh@exk#d)p7Fn z^us&(*9%>+(FFxxxSp@<O!Mb>whwOEAQTvPuXR~bsu$MOTo%SC69ZOdEvoUCbtfOO zAIj3saBkb46Yh8<^Ug!RO(JLFa8UP=oxQum8&`&hqK~uAY=BR;Ra&cV#t*K%)rft* zK0auZ4lgu)qs*<NIjJIc%fHiqjndxcU3VBQ{QLaD%CaYL*pboPbnfCwExdzq+Rqrl zzKu=UZLQqc#^O`Of$Ns+SRvm0`_;bAd$(LZIZUYNEedc|+un~0V-{aI{daEWkphv- zW|#ZPUwE5yKd0M2y>@@cAA1hp7bai5w6xr=wDefbvd9RBU0(Ti<%N#5O8(UJkH!Bf z`!elw=0NO;D|1)=y04>oOU{xO++|yy$B|4YcHZ`;-hB54_n5rbkJiclx|SXq_vBLd zRbi96w6nHiaM!vO=7nKfUGDrgzFRxzTAId1>F6Vhy^sD`(d%~4vkKX3D#$(d2cP$3 z${%ab^yL{C7=%6UdlB1q%3tf2hv(Ny_HAoPkOOu9QttM!<ns@%_^i(OPgI#&>zeDz G)Bgi3fUR8s literal 0 HcmV?d00001 diff --git a/cb-tools/java-source/java-source/src/main/example/web-socket-js/index.html b/cb-tools/java-source/java-source/src/main/example/web-socket-js/index.html new file mode 100644 index 00000000..ee8db6e6 --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/example/web-socket-js/index.html @@ -0,0 +1,29 @@ +<script type="text/javascript"> + // Let the library know where WebSocketMain.swf is: + WEB_SOCKET_FORCE_FLASH = true + + // Let the library know where WebSocketMain.swf is: + WEB_SOCKET_SWF_LOCATION = "WebSocketMain.swf" +</script> + +<!-- Import JavaScript Libraries. --> +<script type="text/javascript" src="swfobject.js"></script> +<script type="text/javascript" src="web_socket.js"></script> + +<script type="text/javascript"> + + // Write your code in the same way as for native WebSocket: + var ws = new WebSocket('ws://localhost:8887') + ws.onopen = function() { + console.log('open') + ws.send('Hello') // Sends a message. + } + ws.onmessage = function(e) { + // Receives a message. + console.log('message', e.data) + } + ws.onclose = function() { + console.log('close') + } + +</script> diff --git a/cb-tools/java-source/java-source/src/main/example/web-socket-js/swfobject.js b/cb-tools/java-source/java-source/src/main/example/web-socket-js/swfobject.js new file mode 100644 index 00000000..8eafe9dd --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/example/web-socket-js/swfobject.js @@ -0,0 +1,4 @@ +/* SWFObject v2.2 <http://code.google.com/p/swfobject/> + is released under the MIT License <http://www.opensource.org/licenses/mit-license.php> +*/ +var swfobject=function(){var D="undefined",r="object",S="Shockwave Flash",W="ShockwaveFlash.ShockwaveFlash",q="application/x-shockwave-flash",R="SWFObjectExprInst",x="onreadystatechange",O=window,j=document,t=navigator,T=false,U=[h],o=[],N=[],I=[],l,Q,E,B,J=false,a=false,n,G,m=true,M=function(){var aa=typeof j.getElementById!=D&&typeof j.getElementsByTagName!=D&&typeof j.createElement!=D,ah=t.userAgent.toLowerCase(),Y=t.platform.toLowerCase(),ae=Y?/win/.test(Y):/win/.test(ah),ac=Y?/mac/.test(Y):/mac/.test(ah),af=/webkit/.test(ah)?parseFloat(ah.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,X=!+"\v1",ag=[0,0,0],ab=null;if(typeof t.plugins!=D&&typeof t.plugins[S]==r){ab=t.plugins[S].description;if(ab&&!(typeof t.mimeTypes!=D&&t.mimeTypes[q]&&!t.mimeTypes[q].enabledPlugin)){T=true;X=false;ab=ab.replace(/^.*\s+(\S+\s+\S+$)/,"$1");ag[0]=parseInt(ab.replace(/^(.*)\..*$/,"$1"),10);ag[1]=parseInt(ab.replace(/^.*\.(.*)\s.*$/,"$1"),10);ag[2]=/[a-zA-Z]/.test(ab)?parseInt(ab.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0}}else{if(typeof O.ActiveXObject!=D){try{var ad=new ActiveXObject(W);if(ad){ab=ad.GetVariable("$version");if(ab){X=true;ab=ab.split(" ")[1].split(",");ag=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}}catch(Z){}}}return{w3:aa,pv:ag,wk:af,ie:X,win:ae,mac:ac}}(),k=function(){if(!M.w3){return}if((typeof j.readyState!=D&&j.readyState=="complete")||(typeof j.readyState==D&&(j.getElementsByTagName("body")[0]||j.body))){f()}if(!J){if(typeof j.addEventListener!=D){j.addEventListener("DOMContentLoaded",f,false)}if(M.ie&&M.win){j.attachEvent(x,function(){if(j.readyState=="complete"){j.detachEvent(x,arguments.callee);f()}});if(O==top){(function(){if(J){return}try{j.documentElement.doScroll("left")}catch(X){setTimeout(arguments.callee,0);return}f()})()}}if(M.wk){(function(){if(J){return}if(!/loaded|complete/.test(j.readyState)){setTimeout(arguments.callee,0);return}f()})()}s(f)}}();function f(){if(J){return}try{var Z=j.getElementsByTagName("body")[0].appendChild(C("span"));Z.parentNode.removeChild(Z)}catch(aa){return}J=true;var X=U.length;for(var Y=0;Y<X;Y++){U[Y]()}}function K(X){if(J){X()}else{U[U.length]=X}}function s(Y){if(typeof O.addEventListener!=D){O.addEventListener("load",Y,false)}else{if(typeof j.addEventListener!=D){j.addEventListener("load",Y,false)}else{if(typeof O.attachEvent!=D){i(O,"onload",Y)}else{if(typeof O.onload=="function"){var X=O.onload;O.onload=function(){X();Y()}}else{O.onload=Y}}}}}function h(){if(T){V()}else{H()}}function V(){var X=j.getElementsByTagName("body")[0];var aa=C(r);aa.setAttribute("type",q);var Z=X.appendChild(aa);if(Z){var Y=0;(function(){if(typeof Z.GetVariable!=D){var ab=Z.GetVariable("$version");if(ab){ab=ab.split(" ")[1].split(",");M.pv=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}else{if(Y<10){Y++;setTimeout(arguments.callee,10);return}}X.removeChild(aa);Z=null;H()})()}else{H()}}function H(){var ag=o.length;if(ag>0){for(var af=0;af<ag;af++){var Y=o[af].id;var ab=o[af].callbackFn;var aa={success:false,id:Y};if(M.pv[0]>0){var ae=c(Y);if(ae){if(F(o[af].swfVersion)&&!(M.wk&&M.wk<312)){w(Y,true);if(ab){aa.success=true;aa.ref=z(Y);ab(aa)}}else{if(o[af].expressInstall&&A()){var ai={};ai.data=o[af].expressInstall;ai.width=ae.getAttribute("width")||"0";ai.height=ae.getAttribute("height")||"0";if(ae.getAttribute("class")){ai.styleclass=ae.getAttribute("class")}if(ae.getAttribute("align")){ai.align=ae.getAttribute("align")}var ah={};var X=ae.getElementsByTagName("param");var ac=X.length;for(var ad=0;ad<ac;ad++){if(X[ad].getAttribute("name").toLowerCase()!="movie"){ah[X[ad].getAttribute("name")]=X[ad].getAttribute("value")}}P(ai,ah,Y,ab)}else{p(ae);if(ab){ab(aa)}}}}}else{w(Y,true);if(ab){var Z=z(Y);if(Z&&typeof Z.SetVariable!=D){aa.success=true;aa.ref=Z}ab(aa)}}}}}function z(aa){var X=null;var Y=c(aa);if(Y&&Y.nodeName=="OBJECT"){if(typeof Y.SetVariable!=D){X=Y}else{var Z=Y.getElementsByTagName(r)[0];if(Z){X=Z}}}return X}function A(){return !a&&F("6.0.65")&&(M.win||M.mac)&&!(M.wk&&M.wk<312)}function P(aa,ab,X,Z){a=true;E=Z||null;B={success:false,id:X};var ae=c(X);if(ae){if(ae.nodeName=="OBJECT"){l=g(ae);Q=null}else{l=ae;Q=X}aa.id=R;if(typeof aa.width==D||(!/%$/.test(aa.width)&&parseInt(aa.width,10)<310)){aa.width="310"}if(typeof aa.height==D||(!/%$/.test(aa.height)&&parseInt(aa.height,10)<137)){aa.height="137"}j.title=j.title.slice(0,47)+" - Flash Player Installation";var ad=M.ie&&M.win?"ActiveX":"PlugIn",ac="MMredirectURL="+O.location.toString().replace(/&/g,"%26")+"&MMplayerType="+ad+"&MMdoctitle="+j.title;if(typeof ab.flashvars!=D){ab.flashvars+="&"+ac}else{ab.flashvars=ac}if(M.ie&&M.win&&ae.readyState!=4){var Y=C("div");X+="SWFObjectNew";Y.setAttribute("id",X);ae.parentNode.insertBefore(Y,ae);ae.style.display="none";(function(){if(ae.readyState==4){ae.parentNode.removeChild(ae)}else{setTimeout(arguments.callee,10)}})()}u(aa,ab,X)}}function p(Y){if(M.ie&&M.win&&Y.readyState!=4){var X=C("div");Y.parentNode.insertBefore(X,Y);X.parentNode.replaceChild(g(Y),X);Y.style.display="none";(function(){if(Y.readyState==4){Y.parentNode.removeChild(Y)}else{setTimeout(arguments.callee,10)}})()}else{Y.parentNode.replaceChild(g(Y),Y)}}function g(ab){var aa=C("div");if(M.win&&M.ie){aa.innerHTML=ab.innerHTML}else{var Y=ab.getElementsByTagName(r)[0];if(Y){var ad=Y.childNodes;if(ad){var X=ad.length;for(var Z=0;Z<X;Z++){if(!(ad[Z].nodeType==1&&ad[Z].nodeName=="PARAM")&&!(ad[Z].nodeType==8)){aa.appendChild(ad[Z].cloneNode(true))}}}}}return aa}function u(ai,ag,Y){var X,aa=c(Y);if(M.wk&&M.wk<312){return X}if(aa){if(typeof ai.id==D){ai.id=Y}if(M.ie&&M.win){var ah="";for(var ae in ai){if(ai[ae]!=Object.prototype[ae]){if(ae.toLowerCase()=="data"){ag.movie=ai[ae]}else{if(ae.toLowerCase()=="styleclass"){ah+=' class="'+ai[ae]+'"'}else{if(ae.toLowerCase()!="classid"){ah+=" "+ae+'="'+ai[ae]+'"'}}}}}var af="";for(var ad in ag){if(ag[ad]!=Object.prototype[ad]){af+='<param name="'+ad+'" value="'+ag[ad]+'" />'}}aa.outerHTML='<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"'+ah+">"+af+"</object>";N[N.length]=ai.id;X=c(ai.id)}else{var Z=C(r);Z.setAttribute("type",q);for(var ac in ai){if(ai[ac]!=Object.prototype[ac]){if(ac.toLowerCase()=="styleclass"){Z.setAttribute("class",ai[ac])}else{if(ac.toLowerCase()!="classid"){Z.setAttribute(ac,ai[ac])}}}}for(var ab in ag){if(ag[ab]!=Object.prototype[ab]&&ab.toLowerCase()!="movie"){e(Z,ab,ag[ab])}}aa.parentNode.replaceChild(Z,aa);X=Z}}return X}function e(Z,X,Y){var aa=C("param");aa.setAttribute("name",X);aa.setAttribute("value",Y);Z.appendChild(aa)}function y(Y){var X=c(Y);if(X&&X.nodeName=="OBJECT"){if(M.ie&&M.win){X.style.display="none";(function(){if(X.readyState==4){b(Y)}else{setTimeout(arguments.callee,10)}})()}else{X.parentNode.removeChild(X)}}}function b(Z){var Y=c(Z);if(Y){for(var X in Y){if(typeof Y[X]=="function"){Y[X]=null}}Y.parentNode.removeChild(Y)}}function c(Z){var X=null;try{X=j.getElementById(Z)}catch(Y){}return X}function C(X){return j.createElement(X)}function i(Z,X,Y){Z.attachEvent(X,Y);I[I.length]=[Z,X,Y]}function F(Z){var Y=M.pv,X=Z.split(".");X[0]=parseInt(X[0],10);X[1]=parseInt(X[1],10)||0;X[2]=parseInt(X[2],10)||0;return(Y[0]>X[0]||(Y[0]==X[0]&&Y[1]>X[1])||(Y[0]==X[0]&&Y[1]==X[1]&&Y[2]>=X[2]))?true:false}function v(ac,Y,ad,ab){if(M.ie&&M.mac){return}var aa=j.getElementsByTagName("head")[0];if(!aa){return}var X=(ad&&typeof ad=="string")?ad:"screen";if(ab){n=null;G=null}if(!n||G!=X){var Z=C("style");Z.setAttribute("type","text/css");Z.setAttribute("media",X);n=aa.appendChild(Z);if(M.ie&&M.win&&typeof j.styleSheets!=D&&j.styleSheets.length>0){n=j.styleSheets[j.styleSheets.length-1]}G=X}if(M.ie&&M.win){if(n&&typeof n.addRule==r){n.addRule(ac,Y)}}else{if(n&&typeof j.createTextNode!=D){n.appendChild(j.createTextNode(ac+" {"+Y+"}"))}}}function w(Z,X){if(!m){return}var Y=X?"visible":"hidden";if(J&&c(Z)){c(Z).style.visibility=Y}else{v("#"+Z,"visibility:"+Y)}}function L(Y){var Z=/[\\\"<>\.;]/;var X=Z.exec(Y)!=null;return X&&typeof encodeURIComponent!=D?encodeURIComponent(Y):Y}var d=function(){if(M.ie&&M.win){window.attachEvent("onunload",function(){var ac=I.length;for(var ab=0;ab<ac;ab++){I[ab][0].detachEvent(I[ab][1],I[ab][2])}var Z=N.length;for(var aa=0;aa<Z;aa++){y(N[aa])}for(var Y in M){M[Y]=null}M=null;for(var X in swfobject){swfobject[X]=null}swfobject=null})}}();return{registerObject:function(ab,X,aa,Z){if(M.w3&&ab&&X){var Y={};Y.id=ab;Y.swfVersion=X;Y.expressInstall=aa;Y.callbackFn=Z;o[o.length]=Y;w(ab,false)}else{if(Z){Z({success:false,id:ab})}}},getObjectById:function(X){if(M.w3){return z(X)}},embedSWF:function(ab,ah,ae,ag,Y,aa,Z,ad,af,ac){var X={success:false,id:ah};if(M.w3&&!(M.wk&&M.wk<312)&&ab&&ah&&ae&&ag&&Y){w(ah,false);K(function(){ae+="";ag+="";var aj={};if(af&&typeof af===r){for(var al in af){aj[al]=af[al]}}aj.data=ab;aj.width=ae;aj.height=ag;var am={};if(ad&&typeof ad===r){for(var ak in ad){am[ak]=ad[ak]}}if(Z&&typeof Z===r){for(var ai in Z){if(typeof am.flashvars!=D){am.flashvars+="&"+ai+"="+Z[ai]}else{am.flashvars=ai+"="+Z[ai]}}}if(F(Y)){var an=u(aj,am,ah);if(aj.id==ah){w(ah,true)}X.success=true;X.ref=an}else{if(aa&&A()){aj.data=aa;P(aj,am,ah,ac);return}else{w(ah,true)}}if(ac){ac(X)}})}else{if(ac){ac(X)}}},switchOffAutoHideShow:function(){m=false},ua:M,getFlashPlayerVersion:function(){return{major:M.pv[0],minor:M.pv[1],release:M.pv[2]}},hasFlashPlayerVersion:F,createSWF:function(Z,Y,X){if(M.w3){return u(Z,Y,X)}else{return undefined}},showExpressInstall:function(Z,aa,X,Y){if(M.w3&&A()){P(Z,aa,X,Y)}},removeSWF:function(X){if(M.w3){y(X)}},createCSS:function(aa,Z,Y,X){if(M.w3){v(aa,Z,Y,X)}},addDomLoadEvent:K,addLoadEvent:s,getQueryParamValue:function(aa){var Z=j.location.search||j.location.hash;if(Z){if(/\?/.test(Z)){Z=Z.split("?")[1]}if(aa==null){return L(Z)}var Y=Z.split("&");for(var X=0;X<Y.length;X++){if(Y[X].substring(0,Y[X].indexOf("="))==aa){return L(Y[X].substring((Y[X].indexOf("=")+1)))}}}return""},expressInstallCallback:function(){if(a){var X=c(R);if(X&&l){X.parentNode.replaceChild(l,X);if(Q){w(Q,true);if(M.ie&&M.win){l.style.display="block"}}if(E){E(B)}}a=false}}}}(); \ No newline at end of file diff --git a/cb-tools/java-source/java-source/src/main/example/web-socket-js/web_socket.js b/cb-tools/java-source/java-source/src/main/example/web-socket-js/web_socket.js new file mode 100644 index 00000000..0561d10c --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/example/web-socket-js/web_socket.js @@ -0,0 +1,389 @@ +// Copyright: Hiroshi Ichikawa <http://gimite.net/en/> +// License: New BSD License +// Reference: http://dev.w3.org/html5/websockets/ +// Reference: http://tools.ietf.org/html/rfc6455 + +(function() { + + if (window.WEB_SOCKET_FORCE_FLASH) { + // Keeps going. + } else if (window.WebSocket) { + return; + } else if (window.MozWebSocket) { + // Firefox. + window.WebSocket = MozWebSocket; + return; + } + + var logger; + if (window.WEB_SOCKET_LOGGER) { + logger = WEB_SOCKET_LOGGER; + } else if (window.console && window.console.log && window.console.error) { + // In some environment, console is defined but console.log or console.error is missing. + logger = window.console; + } else { + logger = {log: function(){ }, error: function(){ }}; + } + + // swfobject.hasFlashPlayerVersion("10.0.0") doesn't work with Gnash. + if (swfobject.getFlashPlayerVersion().major < 10) { + logger.error("Flash Player >= 10.0.0 is required."); + return; + } + if (location.protocol == "file:") { + logger.error( + "WARNING: web-socket-js doesn't work in file:///... URL " + + "unless you set Flash Security Settings properly. " + + "Open the page via Web server i.e. http://..."); + } + + /** + * Our own implementation of WebSocket class using Flash. + * @param {string} url + * @param {array or string} protocols + * @param {string} proxyHost + * @param {int} proxyPort + * @param {string} headers + */ + window.WebSocket = function(url, protocols, proxyHost, proxyPort, headers) { + var self = this; + self.__id = WebSocket.__nextId++; + WebSocket.__instances[self.__id] = self; + self.readyState = WebSocket.CONNECTING; + self.bufferedAmount = 0; + self.__events = {}; + if (!protocols) { + protocols = []; + } else if (typeof protocols == "string") { + protocols = [protocols]; + } + // Uses setTimeout() to make sure __createFlash() runs after the caller sets ws.onopen etc. + // Otherwise, when onopen fires immediately, onopen is called before it is set. + self.__createTask = setTimeout(function() { + WebSocket.__addTask(function() { + self.__createTask = null; + WebSocket.__flash.create( + self.__id, url, protocols, proxyHost || null, proxyPort || 0, headers || null); + }); + }, 0); + }; + + /** + * Send data to the web socket. + * @param {string} data The data to send to the socket. + * @return {boolean} True for success, false for failure. + */ + WebSocket.prototype.send = function(data) { + if (this.readyState == WebSocket.CONNECTING) { + throw "INVALID_STATE_ERR: Web Socket connection has not been established"; + } + // We use encodeURIComponent() here, because FABridge doesn't work if + // the argument includes some characters. We don't use escape() here + // because of this: + // https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Functions#escape_and_unescape_Functions + // But it looks decodeURIComponent(encodeURIComponent(s)) doesn't + // preserve all Unicode characters either e.g. "\uffff" in Firefox. + // Note by wtritch: Hopefully this will not be necessary using ExternalInterface. Will require + // additional testing. + var result = WebSocket.__flash.send(this.__id, encodeURIComponent(data)); + if (result < 0) { // success + return true; + } else { + this.bufferedAmount += result; + return false; + } + }; + + /** + * Close this web socket gracefully. + */ + WebSocket.prototype.close = function() { + if (this.__createTask) { + clearTimeout(this.__createTask); + this.__createTask = null; + this.readyState = WebSocket.CLOSED; + return; + } + if (this.readyState == WebSocket.CLOSED || this.readyState == WebSocket.CLOSING) { + return; + } + this.readyState = WebSocket.CLOSING; + WebSocket.__flash.close(this.__id); + }; + + /** + * Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>} + * + * @param {string} type + * @param {function} listener + * @param {boolean} useCapture + * @return void + */ + WebSocket.prototype.addEventListener = function(type, listener, useCapture) { + if (!(type in this.__events)) { + this.__events[type] = []; + } + this.__events[type].push(listener); + }; + + /** + * Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>} + * + * @param {string} type + * @param {function} listener + * @param {boolean} useCapture + * @return void + */ + WebSocket.prototype.removeEventListener = function(type, listener, useCapture) { + if (!(type in this.__events)) return; + var events = this.__events[type]; + for (var i = events.length - 1; i >= 0; --i) { + if (events[i] === listener) { + events.splice(i, 1); + break; + } + } + }; + + /** + * Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>} + * + * @param {Event} event + * @return void + */ + WebSocket.prototype.dispatchEvent = function(event) { + var events = this.__events[event.type] || []; + for (var i = 0; i < events.length; ++i) { + events[i](event); + } + var handler = this["on" + event.type]; + if (handler) handler.apply(this, [event]); + }; + + /** + * Handles an event from Flash. + * @param {Object} flashEvent + */ + WebSocket.prototype.__handleEvent = function(flashEvent) { + + if ("readyState" in flashEvent) { + this.readyState = flashEvent.readyState; + } + if ("protocol" in flashEvent) { + this.protocol = flashEvent.protocol; + } + + var jsEvent; + if (flashEvent.type == "open" || flashEvent.type == "error") { + jsEvent = this.__createSimpleEvent(flashEvent.type); + } else if (flashEvent.type == "close") { + jsEvent = this.__createSimpleEvent("close"); + jsEvent.wasClean = flashEvent.wasClean ? true : false; + jsEvent.code = flashEvent.code; + jsEvent.reason = flashEvent.reason; + } else if (flashEvent.type == "message") { + var data = decodeURIComponent(flashEvent.message); + jsEvent = this.__createMessageEvent("message", data); + } else { + throw "unknown event type: " + flashEvent.type; + } + + this.dispatchEvent(jsEvent); + + }; + + WebSocket.prototype.__createSimpleEvent = function(type) { + if (document.createEvent && window.Event) { + var event = document.createEvent("Event"); + event.initEvent(type, false, false); + return event; + } else { + return {type: type, bubbles: false, cancelable: false}; + } + }; + + WebSocket.prototype.__createMessageEvent = function(type, data) { + if (document.createEvent && window.MessageEvent && !window.opera) { + var event = document.createEvent("MessageEvent"); + event.initMessageEvent("message", false, false, data, null, null, window, null); + return event; + } else { + // IE and Opera, the latter one truncates the data parameter after any 0x00 bytes. + return {type: type, data: data, bubbles: false, cancelable: false}; + } + }; + + /** + * Define the WebSocket readyState enumeration. + */ + WebSocket.CONNECTING = 0; + WebSocket.OPEN = 1; + WebSocket.CLOSING = 2; + WebSocket.CLOSED = 3; + + WebSocket.__initialized = false; + WebSocket.__flash = null; + WebSocket.__instances = {}; + WebSocket.__tasks = []; + WebSocket.__nextId = 0; + + /** + * Load a new flash security policy file. + * @param {string} url + */ + WebSocket.loadFlashPolicyFile = function(url){ + WebSocket.__addTask(function() { + WebSocket.__flash.loadManualPolicyFile(url); + }); + }; + + /** + * Loads WebSocketMain.swf and creates WebSocketMain object in Flash. + */ + WebSocket.__initialize = function() { + + if (WebSocket.__initialized) return; + WebSocket.__initialized = true; + + if (WebSocket.__swfLocation) { + // For backword compatibility. + window.WEB_SOCKET_SWF_LOCATION = WebSocket.__swfLocation; + } + if (!window.WEB_SOCKET_SWF_LOCATION) { + logger.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf"); + return; + } + if (!window.WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR && + !WEB_SOCKET_SWF_LOCATION.match(/(^|\/)WebSocketMainInsecure\.swf(\?.*)?$/) && + WEB_SOCKET_SWF_LOCATION.match(/^\w+:\/\/([^\/]+)/)) { + var swfHost = RegExp.$1; + if (location.host != swfHost) { + logger.error( + "[WebSocket] You must host HTML and WebSocketMain.swf in the same host " + + "('" + location.host + "' != '" + swfHost + "'). " + + "See also 'How to host HTML file and SWF file in different domains' section " + + "in README.md. If you use WebSocketMainInsecure.swf, you can suppress this message " + + "by WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR = true;"); + } + } + var container = document.createElement("div"); + container.id = "webSocketContainer"; + // Hides Flash box. We cannot use display: none or visibility: hidden because it prevents + // Flash from loading at least in IE. So we move it out of the screen at (-100, -100). + // But this even doesn't work with Flash Lite (e.g. in Droid Incredible). So with Flash + // Lite, we put it at (0, 0). This shows 1x1 box visible at left-top corner but this is + // the best we can do as far as we know now. + container.style.position = "absolute"; + if (WebSocket.__isFlashLite()) { + container.style.left = "0px"; + container.style.top = "0px"; + } else { + container.style.left = "-100px"; + container.style.top = "-100px"; + } + var holder = document.createElement("div"); + holder.id = "webSocketFlash"; + container.appendChild(holder); + document.body.appendChild(container); + // See this article for hasPriority: + // http://help.adobe.com/en_US/as3/mobile/WS4bebcd66a74275c36cfb8137124318eebc6-7ffd.html + swfobject.embedSWF( + WEB_SOCKET_SWF_LOCATION, + "webSocketFlash", + "1" /* width */, + "1" /* height */, + "10.0.0" /* SWF version */, + null, + null, + {hasPriority: true, swliveconnect : true, allowScriptAccess: "always"}, + null, + function(e) { + if (!e.success) { + logger.error("[WebSocket] swfobject.embedSWF failed"); + } + } + ); + + }; + + /** + * Called by Flash to notify JS that it's fully loaded and ready + * for communication. + */ + WebSocket.__onFlashInitialized = function() { + // We need to set a timeout here to avoid round-trip calls + // to flash during the initialization process. + setTimeout(function() { + WebSocket.__flash = document.getElementById("webSocketFlash"); + WebSocket.__flash.setCallerUrl(location.href); + WebSocket.__flash.setDebug(!!window.WEB_SOCKET_DEBUG); + for (var i = 0; i < WebSocket.__tasks.length; ++i) { + WebSocket.__tasks[i](); + } + WebSocket.__tasks = []; + }, 0); + }; + + /** + * Called by Flash to notify WebSockets events are fired. + */ + WebSocket.__onFlashEvent = function() { + setTimeout(function() { + try { + // Gets events using receiveEvents() instead of getting it from event object + // of Flash event. This is to make sure to keep message order. + // It seems sometimes Flash events don't arrive in the same order as they are sent. + var events = WebSocket.__flash.receiveEvents(); + for (var i = 0; i < events.length; ++i) { + WebSocket.__instances[events[i].webSocketId].__handleEvent(events[i]); + } + } catch (e) { + logger.error(e); + } + }, 0); + return true; + }; + + // Called by Flash. + WebSocket.__log = function(message) { + logger.log(decodeURIComponent(message)); + }; + + // Called by Flash. + WebSocket.__error = function(message) { + logger.error(decodeURIComponent(message)); + }; + + WebSocket.__addTask = function(task) { + if (WebSocket.__flash) { + task(); + } else { + WebSocket.__tasks.push(task); + } + }; + + /** + * Test if the browser is running flash lite. + * @return {boolean} True if flash lite is running, false otherwise. + */ + WebSocket.__isFlashLite = function() { + if (!window.navigator || !window.navigator.mimeTypes) { + return false; + } + var mimeType = window.navigator.mimeTypes["application/x-shockwave-flash"]; + if (!mimeType || !mimeType.enabledPlugin || !mimeType.enabledPlugin.filename) { + return false; + } + return mimeType.enabledPlugin.filename.match(/flashlite/i) ? true : false; + }; + + if (!window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION) { + // NOTE: + // This fires immediately if web_socket.js is dynamically loaded after + // the document is loaded. + swfobject.addDomLoadEvent(function() { + WebSocket.__initialize(); + }); + } + +})(); diff --git a/cb-tools/java-source/java-source/src/main/java/org/java_websocket/AbstractWrappedByteChannel.java b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/AbstractWrappedByteChannel.java new file mode 100644 index 00000000..0481a6d4 --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/AbstractWrappedByteChannel.java @@ -0,0 +1,75 @@ +package org.java_websocket; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.ByteChannel; +import java.nio.channels.SocketChannel; + +import javax.net.ssl.SSLException; + + +public class AbstractWrappedByteChannel implements WrappedByteChannel { + + private final ByteChannel channel; + + public AbstractWrappedByteChannel( ByteChannel towrap ) { + this.channel = towrap; + } + + public AbstractWrappedByteChannel( WrappedByteChannel towrap ) { + this.channel = towrap; + } + + @Override + public int read( ByteBuffer dst ) throws IOException { + return channel.read( dst ); + } + + @Override + public boolean isOpen() { + return channel.isOpen(); + } + + @Override + public void close() throws IOException { + channel.close(); + } + + @Override + public int write( ByteBuffer src ) throws IOException { + return channel.write( src ); + } + + @Override + public boolean isNeedWrite() { + return channel instanceof WrappedByteChannel ? ( (WrappedByteChannel) channel ).isNeedWrite() : false; + } + + @Override + public void writeMore() throws IOException { + if( channel instanceof WrappedByteChannel ) + ( (WrappedByteChannel) channel ).writeMore(); + + } + + @Override + public boolean isNeedRead() { + return channel instanceof WrappedByteChannel ? ( (WrappedByteChannel) channel ).isNeedRead() : false; + + } + + @Override + public int readMore( ByteBuffer dst ) throws SSLException { + return channel instanceof WrappedByteChannel ? ( (WrappedByteChannel) channel ).readMore( dst ) : 0; + } + + @Override + public boolean isBlocking() { + if( channel instanceof SocketChannel ) + return ( (SocketChannel) channel ).isBlocking(); + else if( channel instanceof WrappedByteChannel ) + return ( (WrappedByteChannel) channel ).isBlocking(); + return false; + } + +} diff --git a/cb-tools/java-source/java-source/src/main/java/org/java_websocket/SSLSocketChannel2.java b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/SSLSocketChannel2.java new file mode 100644 index 00000000..ba8d8f88 --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/SSLSocketChannel2.java @@ -0,0 +1,380 @@ +/** + * Copyright (C) 2003 Alexander Kout + * Originally from the jFxp project (http://jfxp.sourceforge.net/). + * Copied with permission June 11, 2012 by Femi Omojola (fomojola@ideasynthesis.com). + */ +package org.java_websocket; + +import java.io.IOException; +import java.net.Socket; +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.ByteChannel; +import java.nio.channels.SelectableChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.SocketChannel; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLEngineResult.HandshakeStatus; +import javax.net.ssl.SSLEngineResult.Status; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLSession; + +/** + * Implements the relevant portions of the SocketChannel interface with the SSLEngine wrapper. + */ +public class SSLSocketChannel2 implements ByteChannel, WrappedByteChannel { + /** + * This object is used to feed the {@link SSLEngine}'s wrap and unwrap methods during the handshake phase. + **/ + protected static ByteBuffer emptybuffer = ByteBuffer.allocate( 0 ); + + protected ExecutorService exec; + + protected List<Future<?>> tasks; + + /** raw payload incomming */ + protected ByteBuffer inData; + /** encrypted data outgoing */ + protected ByteBuffer outCrypt; + /** encrypted data incoming */ + protected ByteBuffer inCrypt; + + /** the underlying channel */ + protected SocketChannel socketChannel; + /** used to set interestOP SelectionKey.OP_WRITE for the underlying channel */ + protected SelectionKey selectionKey; + + protected SSLEngine sslEngine; + protected SSLEngineResult readEngineResult; + protected SSLEngineResult writeEngineResult; + + /** + * Should be used to count the buffer allocations. + * But because of #190 where HandshakeStatus.FINISHED is not properly returned by nio wrap/unwrap this variable is used to check whether {@link #createBuffers(SSLSession)} needs to be called. + **/ + protected int bufferallocations = 0; + + public SSLSocketChannel2( SocketChannel channel , SSLEngine sslEngine , ExecutorService exec , SelectionKey key ) throws IOException { + if( channel == null || sslEngine == null || exec == null ) + throw new IllegalArgumentException( "parameter must not be null" ); + + this.socketChannel = channel; + this.sslEngine = sslEngine; + this.exec = exec; + + readEngineResult = writeEngineResult = new SSLEngineResult( Status.BUFFER_UNDERFLOW, sslEngine.getHandshakeStatus(), 0, 0 ); // init to prevent NPEs + + tasks = new ArrayList<Future<?>>( 3 ); + if( key != null ) { + key.interestOps( key.interestOps() | SelectionKey.OP_WRITE ); + this.selectionKey = key; + } + createBuffers( sslEngine.getSession() ); + // kick off handshake + socketChannel.write( wrap( emptybuffer ) );// initializes res + processHandshake(); + } + + private void consumeFutureUninterruptible( Future<?> f ) { + try { + boolean interrupted = false; + while ( true ) { + try { + f.get(); + break; + } catch ( InterruptedException e ) { + interrupted = true; + } + } + if( interrupted ) + Thread.currentThread().interrupt(); + } catch ( ExecutionException e ) { + throw new RuntimeException( e ); + } + } + + /** + * This method will do whatever necessary to process the sslengine handshake. + * Thats why it's called both from the {@link #read(ByteBuffer)} and {@link #write(ByteBuffer)} + **/ + private synchronized void processHandshake() throws IOException { + if( sslEngine.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING ) + return; // since this may be called either from a reading or a writing thread and because this method is synchronized it is necessary to double check if we are still handshaking. + if( !tasks.isEmpty() ) { + Iterator<Future<?>> it = tasks.iterator(); + while ( it.hasNext() ) { + Future<?> f = it.next(); + if( f.isDone() ) { + it.remove(); + } else { + if( isBlocking() ) + consumeFutureUninterruptible( f ); + return; + } + } + } + + if( sslEngine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP ) { + if( !isBlocking() || readEngineResult.getStatus() == Status.BUFFER_UNDERFLOW ) { + inCrypt.compact(); + int read = socketChannel.read( inCrypt ); + if( read == -1 ) { + throw new IOException( "connection closed unexpectedly by peer" ); + } + inCrypt.flip(); + } + inData.compact(); + unwrap(); + if( readEngineResult.getHandshakeStatus() == HandshakeStatus.FINISHED ) { + createBuffers( sslEngine.getSession() ); + return; + } + } + consumeDelegatedTasks(); + if( tasks.isEmpty() || sslEngine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_WRAP ) { + socketChannel.write( wrap( emptybuffer ) ); + if( writeEngineResult.getHandshakeStatus() == HandshakeStatus.FINISHED ) { + createBuffers( sslEngine.getSession() ); + return; + } + } + assert ( sslEngine.getHandshakeStatus() != HandshakeStatus.NOT_HANDSHAKING );// this function could only leave NOT_HANDSHAKING after createBuffers was called unless #190 occurs which means that nio wrap/unwrap never return HandshakeStatus.FINISHED + + bufferallocations = 1; // look at variable declaration why this line exists and #190. Without this line buffers would not be be recreated when #190 AND a rehandshake occur. + } + private synchronized ByteBuffer wrap( ByteBuffer b ) throws SSLException { + outCrypt.compact(); + writeEngineResult = sslEngine.wrap( b, outCrypt ); + outCrypt.flip(); + return outCrypt; + } + + /** + * performs the unwrap operation by unwrapping from {@link #inCrypt} to {@link #inData} + **/ + private synchronized ByteBuffer unwrap() throws SSLException { + int rem; + do { + rem = inData.remaining(); + readEngineResult = sslEngine.unwrap( inCrypt, inData ); + } while ( readEngineResult.getStatus() == SSLEngineResult.Status.OK && ( rem != inData.remaining() || sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP ) ); + inData.flip(); + return inData; + } + + protected void consumeDelegatedTasks() { + Runnable task; + while ( ( task = sslEngine.getDelegatedTask() ) != null ) { + tasks.add( exec.submit( task ) ); + // task.run(); + } + } + + protected void createBuffers( SSLSession session ) { + int netBufferMax = session.getPacketBufferSize(); + int appBufferMax = Math.max(session.getApplicationBufferSize(), netBufferMax); + + if( inData == null ) { + inData = ByteBuffer.allocate( appBufferMax ); + outCrypt = ByteBuffer.allocate( netBufferMax ); + inCrypt = ByteBuffer.allocate( netBufferMax ); + } else { + if( inData.capacity() != appBufferMax ) + inData = ByteBuffer.allocate( appBufferMax ); + if( outCrypt.capacity() != netBufferMax ) + outCrypt = ByteBuffer.allocate( netBufferMax ); + if( inCrypt.capacity() != netBufferMax ) + inCrypt = ByteBuffer.allocate( netBufferMax ); + } + inData.rewind(); + inData.flip(); + inCrypt.rewind(); + inCrypt.flip(); + outCrypt.rewind(); + outCrypt.flip(); + bufferallocations++; + } + + public int write( ByteBuffer src ) throws IOException { + if( !isHandShakeComplete() ) { + processHandshake(); + return 0; + } + // assert ( bufferallocations > 1 ); //see #190 + //if( bufferallocations <= 1 ) { + // createBuffers( sslEngine.getSession() ); + //} + int num = socketChannel.write( wrap( src ) ); + return num; + + } + + /** + * Blocks when in blocking mode until at least one byte has been decoded.<br> + * When not in blocking mode 0 may be returned. + * + * @return the number of bytes read. + **/ + public int read( ByteBuffer dst ) throws IOException { + if( !dst.hasRemaining() ) + return 0; + if( !isHandShakeComplete() ) { + if( isBlocking() ) { + while ( !isHandShakeComplete() ) { + processHandshake(); + } + } else { + processHandshake(); + if( !isHandShakeComplete() ) { + return 0; + } + } + } + // assert ( bufferallocations > 1 ); //see #190 + //if( bufferallocations <= 1 ) { + // createBuffers( sslEngine.getSession() ); + //} + /* 1. When "dst" is smaller than "inData" readRemaining will fill "dst" with data decoded in a previous read call. + * 2. When "inCrypt" contains more data than "inData" has remaining space, unwrap has to be called on more time(readRemaining) + */ + int purged = readRemaining( dst ); + if( purged != 0 ) + return purged; + + /* We only continue when we really need more data from the network. + * Thats the case if inData is empty or inCrypt holds to less data than necessary for decryption + */ + assert ( inData.position() == 0 ); + inData.clear(); + + if( !inCrypt.hasRemaining() ) + inCrypt.clear(); + else + inCrypt.compact(); + + if( isBlocking() || readEngineResult.getStatus() == Status.BUFFER_UNDERFLOW ) + if( socketChannel.read( inCrypt ) == -1 ) { + return -1; + } + inCrypt.flip(); + unwrap(); + + int transfered = transfereTo( inData, dst ); + if( transfered == 0 && isBlocking() ) { + return read( dst ); // "transfered" may be 0 when not enough bytes were received or during rehandshaking + } + return transfered; + } + /** + * {@link #read(ByteBuffer)} may not be to leave all buffers(inData, inCrypt) + **/ + private int readRemaining( ByteBuffer dst ) throws SSLException { + if( inData.hasRemaining() ) { + return transfereTo( inData, dst ); + } + if( !inData.hasRemaining() ) + inData.clear(); + // test if some bytes left from last read (e.g. BUFFER_UNDERFLOW) + if( inCrypt.hasRemaining() ) { + unwrap(); + int amount = transfereTo( inData, dst ); + if( amount > 0 ) + return amount; + } + return 0; + } + + public boolean isConnected() { + return socketChannel.isConnected(); + } + + public void close() throws IOException { + sslEngine.closeOutbound(); + sslEngine.getSession().invalidate(); + if( socketChannel.isOpen() ) + socketChannel.write( wrap( emptybuffer ) );// FIXME what if not all bytes can be written + socketChannel.close(); + exec.shutdownNow(); + } + + private boolean isHandShakeComplete() { + HandshakeStatus status = sslEngine.getHandshakeStatus(); + return status == SSLEngineResult.HandshakeStatus.FINISHED || status == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING; + } + + public SelectableChannel configureBlocking( boolean b ) throws IOException { + return socketChannel.configureBlocking( b ); + } + + public boolean connect( SocketAddress remote ) throws IOException { + return socketChannel.connect( remote ); + } + + public boolean finishConnect() throws IOException { + return socketChannel.finishConnect(); + } + + public Socket socket() { + return socketChannel.socket(); + } + + public boolean isInboundDone() { + return sslEngine.isInboundDone(); + } + + @Override + public boolean isOpen() { + return socketChannel.isOpen(); + } + + @Override + public boolean isNeedWrite() { + return outCrypt.hasRemaining() || !isHandShakeComplete(); // FIXME this condition can cause high cpu load during handshaking when network is slow + } + + @Override + public void writeMore() throws IOException { + write( outCrypt ); + } + + @Override + public boolean isNeedRead() { + return inData.hasRemaining() || ( inCrypt.hasRemaining() && readEngineResult.getStatus() != Status.BUFFER_UNDERFLOW && readEngineResult.getStatus() != Status.CLOSED ); + } + + @Override + public int readMore( ByteBuffer dst ) throws SSLException { + return readRemaining( dst ); + } + + private int transfereTo( ByteBuffer from, ByteBuffer to ) { + int fremain = from.remaining(); + int toremain = to.remaining(); + if( fremain > toremain ) { + // FIXME there should be a more efficient transfer method + int limit = Math.min( fremain, toremain ); + for( int i = 0 ; i < limit ; i++ ) { + to.put( from.get() ); + } + return limit; + } else { + to.put( from ); + return fremain; + } + + } + + @Override + public boolean isBlocking() { + return socketChannel.isBlocking(); + } + +} \ No newline at end of file diff --git a/cb-tools/java-source/java-source/src/main/java/org/java_websocket/SocketChannelIOHelper.java b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/SocketChannelIOHelper.java new file mode 100644 index 00000000..e0da2bdc --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/SocketChannelIOHelper.java @@ -0,0 +1,71 @@ +package org.java_websocket; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.ByteChannel; +import java.nio.channels.spi.AbstractSelectableChannel; + +import org.java_websocket.WebSocket.Role; + +public class SocketChannelIOHelper { + + public static boolean read( final ByteBuffer buf, WebSocketImpl ws, ByteChannel channel ) throws IOException { + buf.clear(); + int read = channel.read( buf ); + buf.flip(); + + if( read == -1 ) { + ws.eot(); + return false; + } + return read != 0; + } + + /** + * @see WrappedByteChannel#readMore(ByteBuffer) + * @return returns whether there is more data left which can be obtained via {@link #readMore(ByteBuffer, WebSocketImpl, WrappedByteChannel)} + **/ + public static boolean readMore( final ByteBuffer buf, WebSocketImpl ws, WrappedByteChannel channel ) throws IOException { + buf.clear(); + int read = channel.readMore( buf ); + buf.flip(); + + if( read == -1 ) { + ws.eot(); + return false; + } + return channel.isNeedRead(); + } + + /** Returns whether the whole outQueue has been flushed */ + public static boolean batch( WebSocketImpl ws, ByteChannel sockchannel ) throws IOException { + ByteBuffer buffer = ws.outQueue.peek(); + WrappedByteChannel c = null; + + if( buffer == null ) { + if( sockchannel instanceof WrappedByteChannel ) { + c = (WrappedByteChannel) sockchannel; + if( c.isNeedWrite() ) { + c.writeMore(); + } + } + } else { + do {// FIXME writing as much as possible is unfair!! + /*int written = */sockchannel.write( buffer ); + if( buffer.remaining() > 0 ) { + return false; + } else { + ws.outQueue.poll(); // Buffer finished. Remove it. + buffer = ws.outQueue.peek(); + } + } while ( buffer != null ); + } + + if( ws.outQueue.isEmpty() && ws.isFlushAndClose() && ws.getDraft().getRole() == Role.SERVER ) {// + synchronized ( ws ) { + ws.closeConnection(); + } + } + return c != null ? !( (WrappedByteChannel) sockchannel ).isNeedWrite() : true; + } +} diff --git a/cb-tools/java-source/java-source/src/main/java/org/java_websocket/WebSocket.java b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/WebSocket.java new file mode 100644 index 00000000..a661eddb --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/WebSocket.java @@ -0,0 +1,124 @@ +package org.java_websocket; + +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.NotYetConnectedException; + +import org.java_websocket.drafts.Draft; +import org.java_websocket.framing.Framedata; +import org.java_websocket.framing.Framedata.Opcode; + +public interface WebSocket { + public enum Role { + CLIENT, SERVER + } + + public enum READYSTATE { + NOT_YET_CONNECTED, CONNECTING, OPEN, CLOSING, CLOSED; + } + + /** + * The default port of WebSockets, as defined in the spec. If the nullary + * constructor is used, DEFAULT_PORT will be the port the WebSocketServer + * is binded to. Note that ports under 1024 usually require root permissions. + */ + public static final int DEFAULT_PORT = 80; + + public static final int DEFAULT_WSS_PORT = 443; + + /** + * sends the closing handshake. + * may be send in response to an other handshake. + */ + public void close( int code, String message ); + + public void close( int code ); + + /** Convenience function which behaves like close(CloseFrame.NORMAL) */ + public void close(); + + /** + * This will close the connection immediately without a proper close handshake. + * The code and the message therefore won't be transfered over the wire also they will be forwarded to onClose/onWebsocketClose. + **/ + public abstract void closeConnection( int code, String message ); + + /** + * Send Text data to the other end. + * + * @throws IllegalArgumentException + * @throws NotYetConnectedException + */ + public abstract void send( String text ) throws NotYetConnectedException; + + /** + * Send Binary data (plain bytes) to the other end. + * + * @throws IllegalArgumentException + * @throws NotYetConnectedException + */ + public abstract void send( ByteBuffer bytes ) throws IllegalArgumentException , NotYetConnectedException; + + public abstract void send( byte[] bytes ) throws IllegalArgumentException , NotYetConnectedException; + + public abstract void sendFrame( Framedata framedata ); + + /** + * Allows to send continuous/fragmented frames conveniently. <br> + * For more into on this frame type see http://tools.ietf.org/html/rfc6455#section-5.4<br> + * + * If the first frame you send is also the last then it is not a fragmented frame and will received via onMessage instead of onFragmented even though it was send by this method. + * + * @param op + * This is only important for the first frame in the sequence. Opcode.TEXT, Opcode.BINARY are allowed. + * @param buffer + * The buffer which contains the payload. It may have no bytes remaining. + * @param fin + * true means the current frame is the last in the sequence. + **/ + public abstract void sendFragmentedFrame( Opcode op, ByteBuffer buffer, boolean fin ); + + public abstract boolean hasBufferedData(); + + /** + * @returns never returns null + */ + public abstract InetSocketAddress getRemoteSocketAddress(); + + /** + * @returns never returns null + */ + public abstract InetSocketAddress getLocalSocketAddress(); + + public abstract boolean isConnecting(); + + public abstract boolean isOpen(); + + public abstract boolean isClosing(); + + /** + * Returns true when no further frames may be submitted<br> + * This happens before the socket connection is closed. + */ + public abstract boolean isFlushAndClose(); + + /** Returns whether the close handshake has been completed and the socket is closed. */ + public abstract boolean isClosed(); + + public abstract Draft getDraft(); + + /** + * Retrieve the WebSocket 'readyState'. + * This represents the state of the connection. + * It returns a numerical value, as per W3C WebSockets specs. + * + * @return Returns '0 = CONNECTING', '1 = OPEN', '2 = CLOSING' or '3 = CLOSED' + */ + public abstract READYSTATE getReadyState(); + + /** + * Returns the HTTP Request-URI as defined by http://tools.ietf.org/html/rfc2616#section-5.1.2<br> + * If the opening handshake has not yet happened it will return null. + **/ + public abstract String getResourceDescriptor(); +} \ No newline at end of file diff --git a/cb-tools/java-source/java-source/src/main/java/org/java_websocket/WebSocketAdapter.java b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/WebSocketAdapter.java new file mode 100644 index 00000000..290e1049 --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/WebSocketAdapter.java @@ -0,0 +1,104 @@ +package org.java_websocket; + +import java.net.InetSocketAddress; + +import org.java_websocket.drafts.Draft; +import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.exceptions.InvalidHandshakeException; +import org.java_websocket.framing.Framedata; +import org.java_websocket.framing.Framedata.Opcode; +import org.java_websocket.framing.FramedataImpl1; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.HandshakeImpl1Server; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.handshake.ServerHandshakeBuilder; + +/** + * This class default implements all methods of the WebSocketListener that can be overridden optionally when advances functionalities is needed.<br> + **/ +public abstract class WebSocketAdapter implements WebSocketListener { + + /** + * This default implementation does not do anything. Go ahead and overwrite it. + * + * @see org.java_websocket.WebSocketListener#onWebsocketHandshakeReceivedAsServer(WebSocket, Draft, ClientHandshake) + */ + @Override + public ServerHandshakeBuilder onWebsocketHandshakeReceivedAsServer( WebSocket conn, Draft draft, ClientHandshake request ) throws InvalidDataException { + return new HandshakeImpl1Server(); + } + + @Override + public void onWebsocketHandshakeReceivedAsClient( WebSocket conn, ClientHandshake request, ServerHandshake response ) throws InvalidDataException { + } + + /** + * This default implementation does not do anything which will cause the connections to always progress. + * + * @see org.java_websocket.WebSocketListener#onWebsocketHandshakeSentAsClient(WebSocket, ClientHandshake) + */ + @Override + public void onWebsocketHandshakeSentAsClient( WebSocket conn, ClientHandshake request ) throws InvalidDataException { + } + + /** + * This default implementation does not do anything. Go ahead and overwrite it + * + * @see org.java_websocket.WebSocketListener#onWebsocketMessageFragment(WebSocket, Framedata) + */ + @Override + public void onWebsocketMessageFragment( WebSocket conn, Framedata frame ) { + } + + /** + * This default implementation will send a pong in response to the received ping. + * The pong frame will have the same payload as the ping frame. + * + * @see org.java_websocket.WebSocketListener#onWebsocketPing(WebSocket, Framedata) + */ + @Override + public void onWebsocketPing( WebSocket conn, Framedata f ) { + FramedataImpl1 resp = new FramedataImpl1( f ); + resp.setOptcode( Opcode.PONG ); + conn.sendFrame( resp ); + } + + /** + * This default implementation does not do anything. Go ahead and overwrite it. + * + * @see @see org.java_websocket.WebSocketListener#onWebsocketPong(WebSocket, Framedata) + */ + @Override + public void onWebsocketPong( WebSocket conn, Framedata f ) { + } + + /** + * Gets the XML string that should be returned if a client requests a Flash + * security policy. + * + * The default implementation allows access from all remote domains, but + * only on the port that this WebSocketServer is listening on. + * + * This is specifically implemented for gitime's WebSocket client for Flash: + * http://github.com/gimite/web-socket-js + * + * @return An XML String that comforts to Flash's security policy. You MUST + * not include the null char at the end, it is appended automatically. + * @throws InvalidDataException thrown when some data that is required to generate the flash-policy like the websocket local port could not be obtained e.g because the websocket is not connected. + */ + @Override + public String getFlashPolicy( WebSocket conn ) throws InvalidDataException { + InetSocketAddress adr = conn.getLocalSocketAddress(); + if(null == adr){ + throw new InvalidHandshakeException( "socket not bound" ); + } + + StringBuffer sb = new StringBuffer( 90 ); + sb.append( "<cross-domain-policy><allow-access-from domain=\"*\" to-ports=\"" ); + sb.append(adr.getPort()); + sb.append( "\" /></cross-domain-policy>\0" ); + + return sb.toString(); + } + +} diff --git a/cb-tools/java-source/java-source/src/main/java/org/java_websocket/WebSocketFactory.java b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/WebSocketFactory.java new file mode 100644 index 00000000..651e9743 --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/WebSocketFactory.java @@ -0,0 +1,12 @@ +package org.java_websocket; + +import java.net.Socket; +import java.util.List; + +import org.java_websocket.drafts.Draft; + +public interface WebSocketFactory { + public WebSocket createWebSocket( WebSocketAdapter a, Draft d, Socket s ); + public WebSocket createWebSocket( WebSocketAdapter a, List<Draft> drafts, Socket s ); + +} diff --git a/cb-tools/java-source/java-source/src/main/java/org/java_websocket/WebSocketImpl.java b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/WebSocketImpl.java new file mode 100644 index 00000000..669bee14 --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/WebSocketImpl.java @@ -0,0 +1,737 @@ +package org.java_websocket; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.nio.ByteBuffer; +import java.nio.channels.ByteChannel; +import java.nio.channels.NotYetConnectedException; +import java.nio.channels.SelectionKey; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + +import org.java_websocket.drafts.Draft; +import org.java_websocket.drafts.Draft.CloseHandshakeType; +import org.java_websocket.drafts.Draft.HandshakeState; +import org.java_websocket.drafts.Draft_10; +import org.java_websocket.drafts.Draft_17; +import org.java_websocket.drafts.Draft_75; +import org.java_websocket.drafts.Draft_76; +import org.java_websocket.exceptions.IncompleteHandshakeException; +import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.exceptions.InvalidHandshakeException; +import org.java_websocket.exceptions.WebsocketNotConnectedException; +import org.java_websocket.framing.CloseFrame; +import org.java_websocket.framing.CloseFrameBuilder; +import org.java_websocket.framing.Framedata; +import org.java_websocket.framing.Framedata.Opcode; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.ClientHandshakeBuilder; +import org.java_websocket.handshake.Handshakedata; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.handshake.ServerHandshakeBuilder; +import org.java_websocket.server.WebSocketServer.WebSocketWorker; +import org.java_websocket.util.Charsetfunctions; + +/** + * Represents one end (client or server) of a single WebSocketImpl connection. + * Takes care of the "handshake" phase, then allows for easy sending of + * text frames, and receiving frames through an event-based model. + * + */ +public class WebSocketImpl implements WebSocket { + + public static int RCVBUF = 16384; + + public static/*final*/boolean DEBUG = false; // must be final in the future in order to take advantage of VM optimization + + public static final List<Draft> defaultdraftlist = new ArrayList<Draft>( 4 ); + static { + defaultdraftlist.add( new Draft_17() ); + defaultdraftlist.add( new Draft_10() ); + defaultdraftlist.add( new Draft_76() ); + defaultdraftlist.add( new Draft_75() ); + } + + public SelectionKey key; + + /** the possibly wrapped channel object whose selection is controlled by {@link #key} */ + public ByteChannel channel; + /** + * Queue of buffers that need to be sent to the client. + */ + public final BlockingQueue<ByteBuffer> outQueue; + /** + * Queue of buffers that need to be processed + */ + public final BlockingQueue<ByteBuffer> inQueue; + + /** + * Helper variable meant to store the thread which ( exclusively ) triggers this objects decode method. + **/ + public volatile WebSocketWorker workerThread; // TODO reset worker? + + /** When true no further frames may be submitted to be sent */ + private volatile boolean flushandclosestate = false; + + private READYSTATE readystate = READYSTATE.NOT_YET_CONNECTED; + + /** + * The listener to notify of WebSocket events. + */ + private final WebSocketListener wsl; + + private List<Draft> knownDrafts; + + private Draft draft = null; + + private Role role; + + private Opcode current_continuous_frame_opcode = null; + + /** the bytes of an incomplete received handshake */ + private ByteBuffer tmpHandshakeBytes = ByteBuffer.allocate( 0 ); + + /** stores the handshake sent by this websocket ( Role.CLIENT only ) */ + private ClientHandshake handshakerequest = null; + + private String closemessage = null; + private Integer closecode = null; + private Boolean closedremotely = null; + + private String resourceDescriptor = null; + + /** + * crates a websocket with server role + */ + public WebSocketImpl( WebSocketListener listener , List<Draft> drafts ) { + this( listener, (Draft) null ); + this.role = Role.SERVER; + // draft.copyInstance will be called when the draft is first needed + if( drafts == null || drafts.isEmpty() ) { + knownDrafts = defaultdraftlist; + } else { + knownDrafts = drafts; + } + } + + /** + * crates a websocket with client role + * + * @param socket + * may be unbound + */ + public WebSocketImpl( WebSocketListener listener , Draft draft ) { + if( listener == null || ( draft == null && role == Role.SERVER ) )// socket can be null because we want do be able to create the object without already having a bound channel + throw new IllegalArgumentException( "parameters must not be null" ); + this.outQueue = new LinkedBlockingQueue<ByteBuffer>(); + inQueue = new LinkedBlockingQueue<ByteBuffer>(); + this.wsl = listener; + this.role = Role.CLIENT; + if( draft != null ) + this.draft = draft.copyInstance(); + } + + @Deprecated + public WebSocketImpl( WebSocketListener listener , Draft draft , Socket socket ) { + this( listener, draft ); + } + + @Deprecated + public WebSocketImpl( WebSocketListener listener , List<Draft> drafts , Socket socket ) { + this( listener, drafts ); + } + + /** + * + */ + public void decode( ByteBuffer socketBuffer ) { + assert ( socketBuffer.hasRemaining() ); + + if( DEBUG ) + System.out.println( "process(" + socketBuffer.remaining() + "): {" + ( socketBuffer.remaining() > 1000 ? "too big to display" : new String( socketBuffer.array(), socketBuffer.position(), socketBuffer.remaining() ) ) + "}" ); + + if( readystate != READYSTATE.NOT_YET_CONNECTED ) { + decodeFrames( socketBuffer );; + } else { + if( decodeHandshake( socketBuffer ) ) { + assert ( tmpHandshakeBytes.hasRemaining() != socketBuffer.hasRemaining() || !socketBuffer.hasRemaining() ); // the buffers will never have remaining bytes at the same time + + if( socketBuffer.hasRemaining() ) { + decodeFrames( socketBuffer ); + } else if( tmpHandshakeBytes.hasRemaining() ) { + decodeFrames( tmpHandshakeBytes ); + } + } + } + assert ( isClosing() || isFlushAndClose() || !socketBuffer.hasRemaining() ); + } + /** + * Returns whether the handshake phase has is completed. + * In case of a broken handshake this will be never the case. + **/ + private boolean decodeHandshake( ByteBuffer socketBufferNew ) { + ByteBuffer socketBuffer; + if( tmpHandshakeBytes.capacity() == 0 ) { + socketBuffer = socketBufferNew; + } else { + if( tmpHandshakeBytes.remaining() < socketBufferNew.remaining() ) { + ByteBuffer buf = ByteBuffer.allocate( tmpHandshakeBytes.capacity() + socketBufferNew.remaining() ); + tmpHandshakeBytes.flip(); + buf.put( tmpHandshakeBytes ); + tmpHandshakeBytes = buf; + } + + tmpHandshakeBytes.put( socketBufferNew ); + tmpHandshakeBytes.flip(); + socketBuffer = tmpHandshakeBytes; + } + socketBuffer.mark(); + try { + if( draft == null ) { + HandshakeState isflashedgecase = isFlashEdgeCase( socketBuffer ); + if( isflashedgecase == HandshakeState.MATCHED ) { + try { + write( ByteBuffer.wrap( Charsetfunctions.utf8Bytes( wsl.getFlashPolicy( this ) ) ) ); + close( CloseFrame.FLASHPOLICY, "" ); + } catch ( InvalidDataException e ) { + close( CloseFrame.ABNORMAL_CLOSE, "remote peer closed connection before flashpolicy could be transmitted", true ); + } + return false; + } + } + HandshakeState handshakestate = null; + + try { + if( role == Role.SERVER ) { + if( draft == null ) { + for( Draft d : knownDrafts ) { + d = d.copyInstance(); + try { + d.setParseMode( role ); + socketBuffer.reset(); + Handshakedata tmphandshake = d.translateHandshake( socketBuffer ); + if( tmphandshake instanceof ClientHandshake == false ) { + flushAndClose( CloseFrame.PROTOCOL_ERROR, "wrong http function", false ); + return false; + } + ClientHandshake handshake = (ClientHandshake) tmphandshake; + handshakestate = d.acceptHandshakeAsServer( handshake ); + if( handshakestate == HandshakeState.MATCHED ) { + resourceDescriptor = handshake.getResourceDescriptor(); + ServerHandshakeBuilder response; + try { + response = wsl.onWebsocketHandshakeReceivedAsServer( this, d, handshake ); + } catch ( InvalidDataException e ) { + flushAndClose( e.getCloseCode(), e.getMessage(), false ); + return false; + } catch ( RuntimeException e ) { + wsl.onWebsocketError( this, e ); + flushAndClose( CloseFrame.NEVER_CONNECTED, e.getMessage(), false ); + return false; + } + write( d.createHandshake( d.postProcessHandshakeResponseAsServer( handshake, response ), role ) ); + draft = d; + open( handshake ); + return true; + } + } catch ( InvalidHandshakeException e ) { + // go on with an other draft + } + } + if( draft == null ) { + close( CloseFrame.PROTOCOL_ERROR, "no draft matches" ); + } + return false; + } else { + // special case for multiple step handshakes + Handshakedata tmphandshake = draft.translateHandshake( socketBuffer ); + if( tmphandshake instanceof ClientHandshake == false ) { + flushAndClose( CloseFrame.PROTOCOL_ERROR, "wrong http function", false ); + return false; + } + ClientHandshake handshake = (ClientHandshake) tmphandshake; + handshakestate = draft.acceptHandshakeAsServer( handshake ); + + if( handshakestate == HandshakeState.MATCHED ) { + open( handshake ); + return true; + } else { + close( CloseFrame.PROTOCOL_ERROR, "the handshake did finaly not match" ); + } + return false; + } + } else if( role == Role.CLIENT ) { + draft.setParseMode( role ); + Handshakedata tmphandshake = draft.translateHandshake( socketBuffer ); + if( tmphandshake instanceof ServerHandshake == false ) { + flushAndClose( CloseFrame.PROTOCOL_ERROR, "wrong http function", false ); + return false; + } + ServerHandshake handshake = (ServerHandshake) tmphandshake; + handshakestate = draft.acceptHandshakeAsClient( handshakerequest, handshake ); + if( handshakestate == HandshakeState.MATCHED ) { + try { + wsl.onWebsocketHandshakeReceivedAsClient( this, handshakerequest, handshake ); + } catch ( InvalidDataException e ) { + flushAndClose( e.getCloseCode(), e.getMessage(), false ); + return false; + } catch ( RuntimeException e ) { + wsl.onWebsocketError( this, e ); + flushAndClose( CloseFrame.NEVER_CONNECTED, e.getMessage(), false ); + return false; + } + open( handshake ); + return true; + } else { + close( CloseFrame.PROTOCOL_ERROR, "draft " + draft + " refuses handshake" ); + } + } + } catch ( InvalidHandshakeException e ) { + close( e ); + } + } catch ( IncompleteHandshakeException e ) { + if( tmpHandshakeBytes.capacity() == 0 ) { + socketBuffer.reset(); + int newsize = e.getPreferedSize(); + if( newsize == 0 ) { + newsize = socketBuffer.capacity() + 16; + } else { + assert ( e.getPreferedSize() >= socketBuffer.remaining() ); + } + tmpHandshakeBytes = ByteBuffer.allocate( newsize ); + + tmpHandshakeBytes.put( socketBufferNew ); + // tmpHandshakeBytes.flip(); + } else { + tmpHandshakeBytes.position( tmpHandshakeBytes.limit() ); + tmpHandshakeBytes.limit( tmpHandshakeBytes.capacity() ); + } + } + return false; + } + + private void decodeFrames( ByteBuffer socketBuffer ) { + + List<Framedata> frames; + try { + frames = draft.translateFrame( socketBuffer ); + for( Framedata f : frames ) { + if( DEBUG ) + System.out.println( "matched frame: " + f ); + Opcode curop = f.getOpcode(); + boolean fin = f.isFin(); + + if( curop == Opcode.CLOSING ) { + int code = CloseFrame.NOCODE; + String reason = ""; + if( f instanceof CloseFrame ) { + CloseFrame cf = (CloseFrame) f; + code = cf.getCloseCode(); + reason = cf.getMessage(); + } + if( readystate == READYSTATE.CLOSING ) { + // complete the close handshake by disconnecting + closeConnection( code, reason, true ); + } else { + // echo close handshake + if( draft.getCloseHandshakeType() == CloseHandshakeType.TWOWAY ) + close( code, reason, true ); + else + flushAndClose( code, reason, false ); + } + continue; + } else if( curop == Opcode.PING ) { + wsl.onWebsocketPing( this, f ); + continue; + } else if( curop == Opcode.PONG ) { + wsl.onWebsocketPong( this, f ); + continue; + } else if( !fin || curop == Opcode.CONTINUOUS ) { + if( curop != Opcode.CONTINUOUS ) { + if( current_continuous_frame_opcode != null ) + throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "Previous continuous frame sequence not completed." ); + current_continuous_frame_opcode = curop; + } else if( fin ) { + if( current_continuous_frame_opcode == null ) + throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "Continuous frame sequence was not started." ); + current_continuous_frame_opcode = null; + } else if( current_continuous_frame_opcode == null ) { + throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "Continuous frame sequence was not started." ); + } + try { + wsl.onWebsocketMessageFragment( this, f ); + } catch ( RuntimeException e ) { + wsl.onWebsocketError( this, e ); + } + + } else if( current_continuous_frame_opcode != null ) { + throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "Continuous frame sequence not completed." ); + } else if( curop == Opcode.TEXT ) { + try { + wsl.onWebsocketMessage( this, Charsetfunctions.stringUtf8( f.getPayloadData() ) ); + } catch ( RuntimeException e ) { + wsl.onWebsocketError( this, e ); + } + } else if( curop == Opcode.BINARY ) { + try { + wsl.onWebsocketMessage( this, f.getPayloadData() ); + } catch ( RuntimeException e ) { + wsl.onWebsocketError( this, e ); + } + } else { + throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "non control or continious frame expected" ); + } + } + } catch ( InvalidDataException e1 ) { + wsl.onWebsocketError( this, e1 ); + close( e1 ); + return; + } + } + + private void close( int code, String message, boolean remote ) { + if( readystate != READYSTATE.CLOSING && readystate != READYSTATE.CLOSED ) { + if( readystate == READYSTATE.OPEN ) { + if( code == CloseFrame.ABNORMAL_CLOSE ) { + assert ( remote == false ); + readystate = READYSTATE.CLOSING; + flushAndClose( code, message, false ); + return; + } + if( draft.getCloseHandshakeType() != CloseHandshakeType.NONE ) { + try { + if( !remote ) { + try { + wsl.onWebsocketCloseInitiated( this, code, message ); + } catch ( RuntimeException e ) { + wsl.onWebsocketError( this, e ); + } + } + sendFrame( new CloseFrameBuilder( code, message ) ); + } catch ( InvalidDataException e ) { + wsl.onWebsocketError( this, e ); + flushAndClose( CloseFrame.ABNORMAL_CLOSE, "generated frame is invalid", false ); + } + } + flushAndClose( code, message, remote ); + } else if( code == CloseFrame.FLASHPOLICY ) { + assert ( remote ); + flushAndClose( CloseFrame.FLASHPOLICY, message, true ); + } else { + flushAndClose( CloseFrame.NEVER_CONNECTED, message, false ); + } + if( code == CloseFrame.PROTOCOL_ERROR )// this endpoint found a PROTOCOL_ERROR + flushAndClose( code, message, remote ); + readystate = READYSTATE.CLOSING; + tmpHandshakeBytes = null; + return; + } + } + + @Override + public void close( int code, String message ) { + close( code, message, false ); + } + + /** + * + * @param remote + * Indicates who "generated" <code>code</code>.<br> + * <code>true</code> means that this endpoint received the <code>code</code> from the other endpoint.<br> + * false means this endpoint decided to send the given code,<br> + * <code>remote</code> may also be true if this endpoint started the closing handshake since the other endpoint may not simply echo the <code>code</code> but close the connection the same time this endpoint does do but with an other <code>code</code>. <br> + **/ + + protected synchronized void closeConnection( int code, String message, boolean remote ) { + if( readystate == READYSTATE.CLOSED ) { + return; + } + + if( key != null ) { + // key.attach( null ); //see issue #114 + key.cancel(); + } + if( channel != null ) { + try { + channel.close(); + } catch ( IOException e ) { + wsl.onWebsocketError( this, e ); + } + } + try { + this.wsl.onWebsocketClose( this, code, message, remote ); + } catch ( RuntimeException e ) { + wsl.onWebsocketError( this, e ); + } + if( draft != null ) + draft.reset(); + handshakerequest = null; + + readystate = READYSTATE.CLOSED; + this.outQueue.clear(); + } + + protected void closeConnection( int code, boolean remote ) { + closeConnection( code, "", remote ); + } + + public void closeConnection() { + if( closedremotely == null ) { + throw new IllegalStateException( "this method must be used in conjuction with flushAndClose" ); + } + closeConnection( closecode, closemessage, closedremotely ); + } + + public void closeConnection( int code, String message ) { + closeConnection( code, message, false ); + } + + protected synchronized void flushAndClose( int code, String message, boolean remote ) { + if( flushandclosestate ) { + return; + } + closecode = code; + closemessage = message; + closedremotely = remote; + + flushandclosestate = true; + + wsl.onWriteDemand( this ); // ensures that all outgoing frames are flushed before closing the connection + try { + wsl.onWebsocketClosing( this, code, message, remote ); + } catch ( RuntimeException e ) { + wsl.onWebsocketError( this, e ); + } + if( draft != null ) + draft.reset(); + handshakerequest = null; + } + + public void eot() { + if( getReadyState() == READYSTATE.NOT_YET_CONNECTED ) { + closeConnection( CloseFrame.NEVER_CONNECTED, true ); + } else if( flushandclosestate ) { + closeConnection( closecode, closemessage, closedremotely ); + } else if( draft.getCloseHandshakeType() == CloseHandshakeType.NONE ) { + closeConnection( CloseFrame.NORMAL, true ); + } else if( draft.getCloseHandshakeType() == CloseHandshakeType.ONEWAY ) { + if( role == Role.SERVER ) + closeConnection( CloseFrame.ABNORMAL_CLOSE, true ); + else + closeConnection( CloseFrame.NORMAL, true ); + } else { + closeConnection( CloseFrame.ABNORMAL_CLOSE, true ); + } + } + + @Override + public void close( int code ) { + close( code, "", false ); + } + + public void close( InvalidDataException e ) { + close( e.getCloseCode(), e.getMessage(), false ); + } + + /** + * Send Text data to the other end. + * + * @throws IllegalArgumentException + * @throws NotYetConnectedException + */ + @Override + public void send( String text ) throws WebsocketNotConnectedException { + if( text == null ) + throw new IllegalArgumentException( "Cannot send 'null' data to a WebSocketImpl." ); + send( draft.createFrames( text, role == Role.CLIENT ) ); + } + + /** + * Send Binary data (plain bytes) to the other end. + * + * @throws IllegalArgumentException + * @throws NotYetConnectedException + */ + @Override + public void send( ByteBuffer bytes ) throws IllegalArgumentException , WebsocketNotConnectedException { + if( bytes == null ) + throw new IllegalArgumentException( "Cannot send 'null' data to a WebSocketImpl." ); + send( draft.createFrames( bytes, role == Role.CLIENT ) ); + } + + @Override + public void send( byte[] bytes ) throws IllegalArgumentException , WebsocketNotConnectedException { + send( ByteBuffer.wrap( bytes ) ); + } + + private void send( Collection<Framedata> frames ) { + if( !isOpen() ) + throw new WebsocketNotConnectedException(); + for( Framedata f : frames ) { + sendFrame( f ); + } + } + + @Override + public void sendFragmentedFrame( Opcode op, ByteBuffer buffer, boolean fin ) { + send( draft.continuousFrame( op, buffer, fin ) ); + } + + @Override + public void sendFrame( Framedata framedata ) { + if( DEBUG ) + System.out.println( "send frame: " + framedata ); + write( draft.createBinaryFrame( framedata ) ); + } + + @Override + public boolean hasBufferedData() { + return !this.outQueue.isEmpty(); + } + + private HandshakeState isFlashEdgeCase( ByteBuffer request ) throws IncompleteHandshakeException { + request.mark(); + if( request.limit() > Draft.FLASH_POLICY_REQUEST.length ) { + return HandshakeState.NOT_MATCHED; + } else if( request.limit() < Draft.FLASH_POLICY_REQUEST.length ) { + throw new IncompleteHandshakeException( Draft.FLASH_POLICY_REQUEST.length ); + } else { + + for( int flash_policy_index = 0 ; request.hasRemaining() ; flash_policy_index++ ) { + if( Draft.FLASH_POLICY_REQUEST[ flash_policy_index ] != request.get() ) { + request.reset(); + return HandshakeState.NOT_MATCHED; + } + } + return HandshakeState.MATCHED; + } + } + + public void startHandshake( ClientHandshakeBuilder handshakedata ) throws InvalidHandshakeException { + assert ( readystate != READYSTATE.CONNECTING ) : "shall only be called once"; + + // Store the Handshake Request we are about to send + this.handshakerequest = draft.postProcessHandshakeRequestAsClient( handshakedata ); + + resourceDescriptor = handshakedata.getResourceDescriptor(); + assert( resourceDescriptor != null ); + + // Notify Listener + try { + wsl.onWebsocketHandshakeSentAsClient( this, this.handshakerequest ); + } catch ( InvalidDataException e ) { + // Stop if the client code throws an exception + throw new InvalidHandshakeException( "Handshake data rejected by client." ); + } catch ( RuntimeException e ) { + wsl.onWebsocketError( this, e ); + throw new InvalidHandshakeException( "rejected because of" + e ); + } + + // Send + write( draft.createHandshake( this.handshakerequest, role ) ); + } + + private void write( ByteBuffer buf ) { + if( DEBUG ) + System.out.println( "write(" + buf.remaining() + "): {" + ( buf.remaining() > 1000 ? "too big to display" : new String( buf.array() ) ) + "}" ); + + outQueue.add( buf ); + /*try { + outQueue.put( buf ); + } catch ( InterruptedException e ) { + write( buf ); + Thread.currentThread().interrupt(); // keep the interrupted status + e.printStackTrace(); + }*/ + wsl.onWriteDemand( this ); + } + + private void write( List<ByteBuffer> bufs ) { + for( ByteBuffer b : bufs ) { + write( b ); + } + } + + private void open( Handshakedata d ) { + if( DEBUG ) + System.out.println( "open using draft: " + draft.getClass().getSimpleName() ); + readystate = READYSTATE.OPEN; + try { + wsl.onWebsocketOpen( this, d ); + } catch ( RuntimeException e ) { + wsl.onWebsocketError( this, e ); + } + } + + @Override + public boolean isConnecting() { + assert ( flushandclosestate ? readystate == READYSTATE.CONNECTING : true ); + return readystate == READYSTATE.CONNECTING; // ifflushandclosestate + } + + @Override + public boolean isOpen() { + assert ( readystate == READYSTATE.OPEN ? !flushandclosestate : true ); + return readystate == READYSTATE.OPEN; + } + + @Override + public boolean isClosing() { + return readystate == READYSTATE.CLOSING; + } + + @Override + public boolean isFlushAndClose() { + return flushandclosestate; + } + + @Override + public boolean isClosed() { + return readystate == READYSTATE.CLOSED; + } + + @Override + public READYSTATE getReadyState() { + return readystate; + } + + @Override + public int hashCode() { + return super.hashCode(); + } + + @Override + public String toString() { + return super.toString(); // its nice to be able to set breakpoints here + } + + @Override + public InetSocketAddress getRemoteSocketAddress() { + return wsl.getRemoteSocketAddress( this ); + } + + @Override + public InetSocketAddress getLocalSocketAddress() { + return wsl.getLocalSocketAddress( this ); + } + + @Override + public Draft getDraft() { + return draft; + } + + @Override + public void close() { + close( CloseFrame.NORMAL ); + } + + @Override + public String getResourceDescriptor() { + return resourceDescriptor; + } + +} diff --git a/cb-tools/java-source/java-source/src/main/java/org/java_websocket/WebSocketListener.java b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/WebSocketListener.java new file mode 100644 index 00000000..93478d94 --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/WebSocketListener.java @@ -0,0 +1,151 @@ +package org.java_websocket; + +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; + +import org.java_websocket.drafts.Draft; +import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.framing.Framedata; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.Handshakedata; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.handshake.ServerHandshakeBuilder; + +/** + * Implemented by <tt>WebSocketClient</tt> and <tt>WebSocketServer</tt>. + * The methods within are called by <tt>WebSocket</tt>. + * Almost every method takes a first parameter conn which represents the source of the respective event. + */ +public interface WebSocketListener { + + /** + * Called on the server side when the socket connection is first established, and the WebSocket + * handshake has been received. This method allows to deny connections based on the received handshake.<br> + * By default this method only requires protocol compliance. + * + * @param conn + * The WebSocket related to this event + * @param draft + * The protocol draft the client uses to connect + * @param request + * The opening http message send by the client. Can be used to access additional fields like cookies. + * @return Returns an incomplete handshake containing all optional fields + * @throws InvalidDataException + * Throwing this exception will cause this handshake to be rejected + */ + public ServerHandshakeBuilder onWebsocketHandshakeReceivedAsServer( WebSocket conn, Draft draft, ClientHandshake request ) throws InvalidDataException; + + /** + * Called on the client side when the socket connection is first established, and the WebSocketImpl + * handshake response has been received. + * + * @param conn + * The WebSocket related to this event + * @param request + * The handshake initially send out to the server by this websocket. + * @param response + * The handshake the server sent in response to the request. + * @throws InvalidDataException + * Allows the client to reject the connection with the server in respect of its handshake response. + */ + public void onWebsocketHandshakeReceivedAsClient( WebSocket conn, ClientHandshake request, ServerHandshake response ) throws InvalidDataException; + + /** + * Called on the client side when the socket connection is first established, and the WebSocketImpl + * handshake has just been sent. + * + * @param conn + * The WebSocket related to this event + * @param request + * The handshake sent to the server by this websocket + * @throws InvalidDataException + * Allows the client to stop the connection from progressing + */ + public void onWebsocketHandshakeSentAsClient( WebSocket conn, ClientHandshake request ) throws InvalidDataException; + + /** + * Called when an entire text frame has been received. Do whatever you want + * here... + * + * @param conn + * The <tt>WebSocket</tt> instance this event is occurring on. + * @param message + * The UTF-8 decoded message that was received. + */ + public void onWebsocketMessage( WebSocket conn, String message ); + + /** + * Called when an entire binary frame has been received. Do whatever you want + * here... + * + * @param conn + * The <tt>WebSocket</tt> instance this event is occurring on. + * @param blob + * The binary message that was received. + */ + public void onWebsocketMessage( WebSocket conn, ByteBuffer blob ); + + public void onWebsocketMessageFragment( WebSocket conn, Framedata frame ); + + /** + * Called after <var>onHandshakeReceived</var> returns <var>true</var>. + * Indicates that a complete WebSocket connection has been established, + * and we are ready to send/receive data. + * + * @param conn + * The <tt>WebSocket</tt> instance this event is occuring on. + */ + public void onWebsocketOpen( WebSocket conn, Handshakedata d ); + + /** + * Called after <tt>WebSocket#close</tt> is explicity called, or when the + * other end of the WebSocket connection is closed. + * + * @param conn + * The <tt>WebSocket</tt> instance this event is occuring on. + */ + public void onWebsocketClose( WebSocket ws, int code, String reason, boolean remote ); + + /** called as soon as no further frames are accepted */ + public void onWebsocketClosing( WebSocket ws, int code, String reason, boolean remote ); + + /** send when this peer sends a close handshake */ + public void onWebsocketCloseInitiated( WebSocket ws, int code, String reason ); + + /** + * Called if an exception worth noting occurred. + * If an error causes the connection to fail onClose will be called additionally afterwards. + * + * @param ex + * The exception that occurred. <br> + * Might be null if the exception is not related to any specific connection. For example if the server port could not be bound. + */ + public void onWebsocketError( WebSocket conn, Exception ex ); + + /** + * Called a ping frame has been received. + * This method must send a corresponding pong by itself. + * + * @param f + * The ping frame. Control frames may contain payload. + */ + public void onWebsocketPing( WebSocket conn, Framedata f ); + + /** + * Called when a pong frame is received. + **/ + public void onWebsocketPong( WebSocket conn, Framedata f ); + + /** + * Gets the XML string that should be returned if a client requests a Flash + * security policy. + * @throws InvalidDataException thrown when some data that is required to generate the flash-policy like the websocket local port could not be obtained. + */ + public String getFlashPolicy( WebSocket conn ) throws InvalidDataException; + + /** This method is used to inform the selector thread that there is data queued to be written to the socket. */ + public void onWriteDemand( WebSocket conn ); + + public InetSocketAddress getLocalSocketAddress( WebSocket conn ); + public InetSocketAddress getRemoteSocketAddress( WebSocket conn ); +} diff --git a/cb-tools/java-source/java-source/src/main/java/org/java_websocket/WrappedByteChannel.java b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/WrappedByteChannel.java new file mode 100644 index 00000000..83a3290b --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/WrappedByteChannel.java @@ -0,0 +1,26 @@ +package org.java_websocket; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.ByteChannel; + +import javax.net.ssl.SSLException; + +public interface WrappedByteChannel extends ByteChannel { + public boolean isNeedWrite(); + public void writeMore() throws IOException; + + /** + * returns whether readMore should be called to fetch data which has been decoded but not yet been returned. + * + * @see #read(ByteBuffer) + * @see #readMore(ByteBuffer) + **/ + public boolean isNeedRead(); + /** + * This function does not read data from the underlying channel at all. It is just a way to fetch data which has already be received or decoded but was but was not yet returned to the user. + * This could be the case when the decoded data did not fit into the buffer the user passed to {@link #read(ByteBuffer)}. + **/ + public int readMore( ByteBuffer dst ) throws SSLException; + public boolean isBlocking(); +} diff --git a/cb-tools/java-source/java-source/src/main/java/org/java_websocket/client/AbstractClientProxyChannel.java b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/client/AbstractClientProxyChannel.java new file mode 100644 index 00000000..bbac6725 --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/client/AbstractClientProxyChannel.java @@ -0,0 +1,38 @@ +package org.java_websocket.client; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; +import java.nio.channels.ByteChannel; + +import org.java_websocket.AbstractWrappedByteChannel; + +public abstract class AbstractClientProxyChannel extends AbstractWrappedByteChannel { + protected final ByteBuffer proxyHandshake; + + + /** + * @param towrap + * The channel to the proxy server + **/ + public AbstractClientProxyChannel( ByteChannel towrap ) { + super( towrap ); + try { + proxyHandshake = ByteBuffer.wrap( buildHandShake().getBytes( "ASCII" ) ); + } catch ( UnsupportedEncodingException e ) { + throw new RuntimeException( e ); + } + } + + @Override + public int write( ByteBuffer src ) throws IOException { + if( !proxyHandshake.hasRemaining() ) { + return super.write( src ); + } else { + return super.write( proxyHandshake ); + } + } + + public abstract String buildHandShake(); + +} diff --git a/cb-tools/java-source/java-source/src/main/java/org/java_websocket/client/WebSocketClient.java b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/client/WebSocketClient.java new file mode 100644 index 00000000..86eff794 --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/client/WebSocketClient.java @@ -0,0 +1,454 @@ +package org.java_websocket.client; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.Socket; +import java.net.URI; +import java.nio.ByteBuffer; +import java.nio.channels.NotYetConnectedException; +import java.util.Map; +import java.util.concurrent.CountDownLatch; + +import org.java_websocket.WebSocket; +import org.java_websocket.WebSocketAdapter; +import org.java_websocket.WebSocketImpl; +import org.java_websocket.drafts.Draft; +import org.java_websocket.drafts.Draft_17; +import org.java_websocket.exceptions.InvalidHandshakeException; +import org.java_websocket.framing.CloseFrame; +import org.java_websocket.framing.Framedata; +import org.java_websocket.framing.Framedata.Opcode; +import org.java_websocket.handshake.HandshakeImpl1Client; +import org.java_websocket.handshake.Handshakedata; +import org.java_websocket.handshake.ServerHandshake; + +/** + * A subclass must implement at least <var>onOpen</var>, <var>onClose</var>, and <var>onMessage</var> to be + * useful. At runtime the user is expected to establish a connection via {@link #connect()}, then receive events like {@link #onMessage(String)} via the overloaded methods and to {@link #send(String)} data to the server. + */ +public abstract class WebSocketClient extends WebSocketAdapter implements Runnable, WebSocket { + + /** + * The URI this channel is supposed to connect to. + */ + protected URI uri = null; + + private WebSocketImpl engine = null; + + private Socket socket = null; + + private InputStream istream; + + private OutputStream ostream; + + private Proxy proxy = Proxy.NO_PROXY; + + private Thread writeThread; + + private Draft draft; + + private Map<String,String> headers; + + private CountDownLatch connectLatch = new CountDownLatch( 1 ); + + private CountDownLatch closeLatch = new CountDownLatch( 1 ); + + private int connectTimeout = 0; + + /** This open a websocket connection as specified by rfc6455 */ + public WebSocketClient( URI serverURI ) { + this( serverURI, new Draft_17() ); + } + + /** + * Constructs a WebSocketClient instance and sets it to the connect to the + * specified URI. The channel does not attampt to connect automatically. The connection + * will be established once you call <var>connect</var>. + */ + public WebSocketClient( URI serverUri , Draft draft ) { + this( serverUri, draft, null, 0 ); + } + + public WebSocketClient( URI serverUri , Draft protocolDraft , Map<String,String> httpHeaders , int connectTimeout ) { + if( serverUri == null ) { + throw new IllegalArgumentException(); + } else if( protocolDraft == null ) { + throw new IllegalArgumentException( "null as draft is permitted for `WebSocketServer` only!" ); + } + this.uri = serverUri; + this.draft = protocolDraft; + this.headers = httpHeaders; + this.connectTimeout = connectTimeout; + this.engine = new WebSocketImpl( this, protocolDraft ); + } + + /** + * Returns the URI that this WebSocketClient is connected to. + */ + public URI getURI() { + return uri; + } + + /** + * Returns the protocol version this channel uses.<br> + * For more infos see https://github.com/TooTallNate/Java-WebSocket/wiki/Drafts + */ + public Draft getDraft() { + return draft; + } + + /** + * Initiates the websocket connection. This method does not block. + */ + public void connect() { + if( writeThread != null ) + throw new IllegalStateException( "WebSocketClient objects are not reuseable" ); + writeThread = new Thread( this ); + writeThread.start(); + } + + /** + * Same as <code>connect</code> but blocks until the websocket connected or failed to do so.<br> + * Returns whether it succeeded or not. + **/ + public boolean connectBlocking() throws InterruptedException { + connect(); + connectLatch.await(); + return engine.isOpen(); + } + + /** + * Initiates the websocket close handshake. This method does not block<br> + * In oder to make sure the connection is closed use <code>closeBlocking</code> + */ + public void close() { + if( writeThread != null ) { + engine.close( CloseFrame.NORMAL ); + } + } + + public void closeBlocking() throws InterruptedException { + close(); + closeLatch.await(); + } + + /** + * Sends <var>text</var> to the connected websocket server. + * + * @param text + * The string which will be transmitted. + */ + public void send( String text ) throws NotYetConnectedException { + engine.send( text ); + } + + /** + * Sends binary <var> data</var> to the connected webSocket server. + * + * @param data + * The byte-Array of data to send to the WebSocket server. + */ + public void send( byte[] data ) throws NotYetConnectedException { + engine.send( data ); + } + + public void run() { + try { + if( socket == null ) { + socket = new Socket( proxy ); + } else if( socket.isClosed() ) { + throw new IOException(); + } + if( !socket.isBound() ) + socket.connect( new InetSocketAddress( uri.getHost(), getPort() ), connectTimeout ); + istream = socket.getInputStream(); + ostream = socket.getOutputStream(); + + sendHandshake(); + } catch ( /*IOException | SecurityException | UnresolvedAddressException | InvalidHandshakeException | ClosedByInterruptException | SocketTimeoutException */Exception e ) { + onWebsocketError( engine, e ); + engine.closeConnection( CloseFrame.NEVER_CONNECTED, e.getMessage() ); + return; + } + + writeThread = new Thread( new WebsocketWriteThread() ); + writeThread.start(); + + byte[] rawbuffer = new byte[ WebSocketImpl.RCVBUF ]; + int readBytes; + + try { + while ( !isClosed() && ( readBytes = istream.read( rawbuffer ) ) != -1 ) { + engine.decode( ByteBuffer.wrap( rawbuffer, 0, readBytes ) ); + } + engine.eot(); + } catch ( IOException e ) { + engine.eot(); + } catch ( RuntimeException e ) { + // this catch case covers internal errors only and indicates a bug in this websocket implementation + onError( e ); + engine.closeConnection( CloseFrame.ABNORMAL_CLOSE, e.getMessage() ); + } + assert ( socket.isClosed() ); + } + private int getPort() { + int port = uri.getPort(); + if( port == -1 ) { + String scheme = uri.getScheme(); + if( scheme.equals( "wss" ) ) { + return WebSocket.DEFAULT_WSS_PORT; + } else if( scheme.equals( "ws" ) ) { + return WebSocket.DEFAULT_PORT; + } else { + throw new RuntimeException( "unkonow scheme" + scheme ); + } + } + return port; + } + + private void sendHandshake() throws InvalidHandshakeException { + String path; + String part1 = uri.getPath(); + String part2 = uri.getQuery(); + if( part1 == null || part1.length() == 0 ) + path = "/"; + else + path = part1; + if( part2 != null ) + path += "?" + part2; + int port = getPort(); + String host = uri.getHost() + ( port != WebSocket.DEFAULT_PORT ? ":" + port : "" ); + + HandshakeImpl1Client handshake = new HandshakeImpl1Client(); + handshake.setResourceDescriptor( path ); + handshake.put( "Host", host ); + if( headers != null ) { + for( Map.Entry<String,String> kv : headers.entrySet() ) { + handshake.put( kv.getKey(), kv.getValue() ); + } + } + engine.startHandshake( handshake ); + } + + /** + * This represents the state of the connection. + */ + public READYSTATE getReadyState() { + return engine.getReadyState(); + } + + /** + * Calls subclass' implementation of <var>onMessage</var>. + */ + @Override + public final void onWebsocketMessage( WebSocket conn, String message ) { + onMessage( message ); + } + + @Override + public final void onWebsocketMessage( WebSocket conn, ByteBuffer blob ) { + onMessage( blob ); + } + + @Override + public void onWebsocketMessageFragment( WebSocket conn, Framedata frame ) { + onFragment( frame ); + } + + /** + * Calls subclass' implementation of <var>onOpen</var>. + */ + @Override + public final void onWebsocketOpen( WebSocket conn, Handshakedata handshake ) { + connectLatch.countDown(); + onOpen( (ServerHandshake) handshake ); + } + + /** + * Calls subclass' implementation of <var>onClose</var>. + */ + @Override + public final void onWebsocketClose( WebSocket conn, int code, String reason, boolean remote ) { + connectLatch.countDown(); + closeLatch.countDown(); + if( writeThread != null ) + writeThread.interrupt(); + try { + if( socket != null ) + socket.close(); + } catch ( IOException e ) { + onWebsocketError( this, e ); + } + onClose( code, reason, remote ); + } + + /** + * Calls subclass' implementation of <var>onIOError</var>. + */ + @Override + public final void onWebsocketError( WebSocket conn, Exception ex ) { + onError( ex ); + } + + @Override + public final void onWriteDemand( WebSocket conn ) { + // nothing to do + } + + @Override + public void onWebsocketCloseInitiated( WebSocket conn, int code, String reason ) { + onCloseInitiated( code, reason ); + } + + @Override + public void onWebsocketClosing( WebSocket conn, int code, String reason, boolean remote ) { + onClosing( code, reason, remote ); + } + + public void onCloseInitiated( int code, String reason ) { + } + + public void onClosing( int code, String reason, boolean remote ) { + } + + public WebSocket getConnection() { + return engine; + } + + @Override + public InetSocketAddress getLocalSocketAddress( WebSocket conn ) { + if( socket != null ) + return (InetSocketAddress) socket.getLocalSocketAddress(); + return null; + } + + @Override + public InetSocketAddress getRemoteSocketAddress( WebSocket conn ) { + if( socket != null ) + return (InetSocketAddress) socket.getRemoteSocketAddress(); + return null; + } + + // ABTRACT METHODS ///////////////////////////////////////////////////////// + public abstract void onOpen( ServerHandshake handshakedata ); + public abstract void onMessage( String message ); + public abstract void onClose( int code, String reason, boolean remote ); + public abstract void onError( Exception ex ); + public void onMessage( ByteBuffer bytes ) { + } + public void onFragment( Framedata frame ) { + } + + private class WebsocketWriteThread implements Runnable { + @Override + public void run() { + Thread.currentThread().setName( "WebsocketWriteThread" ); + try { + while ( !Thread.interrupted() ) { + ByteBuffer buffer = engine.outQueue.take(); + ostream.write( buffer.array(), 0, buffer.limit() ); + ostream.flush(); + } + } catch ( IOException e ) { + engine.eot(); + } catch ( InterruptedException e ) { + // this thread is regularly terminated via an interrupt + } + } + } + + public void setProxy( Proxy proxy ) { + if( proxy == null ) + throw new IllegalArgumentException(); + this.proxy = proxy; + } + + /** + * Accepts bound and unbound sockets.<br> + * This method must be called before <code>connect</code>. + * If the given socket is not yet bound it will be bound to the uri specified in the constructor. + **/ + public void setSocket( Socket socket ) { + if( this.socket != null ) { + throw new IllegalStateException( "socket has already been set" ); + } + this.socket = socket; + } + + @Override + public void sendFragmentedFrame( Opcode op, ByteBuffer buffer, boolean fin ) { + engine.sendFragmentedFrame( op, buffer, fin ); + } + + @Override + public boolean isOpen() { + return engine.isOpen(); + } + + @Override + public boolean isFlushAndClose() { + return engine.isFlushAndClose(); + } + + @Override + public boolean isClosed() { + return engine.isClosed(); + } + + @Override + public boolean isClosing() { + return engine.isClosing(); + } + + @Override + public boolean isConnecting() { + return engine.isConnecting(); + } + + @Override + public boolean hasBufferedData() { + return engine.hasBufferedData(); + } + + @Override + public void close( int code ) { + engine.close(); + } + + @Override + public void close( int code, String message ) { + engine.close( code, message ); + } + + @Override + public void closeConnection( int code, String message ) { + engine.closeConnection( code, message ); + } + + @Override + public void send( ByteBuffer bytes ) throws IllegalArgumentException , NotYetConnectedException { + engine.send( bytes ); + } + + @Override + public void sendFrame( Framedata framedata ) { + engine.sendFrame( framedata ); + } + + @Override + public InetSocketAddress getLocalSocketAddress() { + return engine.getLocalSocketAddress(); + } + @Override + public InetSocketAddress getRemoteSocketAddress() { + return engine.getRemoteSocketAddress(); + } + + @Override + public String getResourceDescriptor() { + return uri.getPath(); + } +} diff --git a/cb-tools/java-source/java-source/src/main/java/org/java_websocket/drafts/Draft.java b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/drafts/Draft.java new file mode 100644 index 00000000..65b34de8 --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/drafts/Draft.java @@ -0,0 +1,228 @@ +package org.java_websocket.drafts; + +import java.nio.ByteBuffer; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; + +import org.java_websocket.WebSocket.Role; +import org.java_websocket.exceptions.IncompleteHandshakeException; +import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.exceptions.InvalidHandshakeException; +import org.java_websocket.exceptions.LimitExedeedException; +import org.java_websocket.framing.CloseFrame; +import org.java_websocket.framing.FrameBuilder; +import org.java_websocket.framing.Framedata; +import org.java_websocket.framing.Framedata.Opcode; +import org.java_websocket.framing.FramedataImpl1; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.ClientHandshakeBuilder; +import org.java_websocket.handshake.HandshakeBuilder; +import org.java_websocket.handshake.HandshakeImpl1Client; +import org.java_websocket.handshake.HandshakeImpl1Server; +import org.java_websocket.handshake.Handshakedata; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.handshake.ServerHandshakeBuilder; +import org.java_websocket.util.Charsetfunctions; + +/** + * Base class for everything of a websocket specification which is not common such as the way the handshake is read or frames are transfered. + **/ +public abstract class Draft { + + public enum HandshakeState { + /** Handshake matched this Draft successfully */ + MATCHED, + /** Handshake is does not match this Draft */ + NOT_MATCHED + } + public enum CloseHandshakeType { + NONE, ONEWAY, TWOWAY + } + + public static int MAX_FAME_SIZE = 1000 * 1; + public static int INITIAL_FAMESIZE = 64; + + public static final byte[] FLASH_POLICY_REQUEST = Charsetfunctions.utf8Bytes( "<policy-file-request/>\0" ); + + /** In some cases the handshake will be parsed different depending on whether */ + protected Role role = null; + + protected Opcode continuousFrameType = null; + + public static ByteBuffer readLine( ByteBuffer buf ) { + ByteBuffer sbuf = ByteBuffer.allocate( buf.remaining() ); + byte prev = '0'; + byte cur = '0'; + while ( buf.hasRemaining() ) { + prev = cur; + cur = buf.get(); + sbuf.put( cur ); + if( prev == (byte) '\r' && cur == (byte) '\n' ) { + sbuf.limit( sbuf.position() - 2 ); + sbuf.position( 0 ); + return sbuf; + + } + } + // ensure that there wont be any bytes skipped + buf.position( buf.position() - sbuf.position() ); + return null; + } + + public static String readStringLine( ByteBuffer buf ) { + ByteBuffer b = readLine( buf ); + return b == null ? null : Charsetfunctions.stringAscii( b.array(), 0, b.limit() ); + } + + public static HandshakeBuilder translateHandshakeHttp( ByteBuffer buf, Role role ) throws InvalidHandshakeException , IncompleteHandshakeException { + HandshakeBuilder handshake; + + String line = readStringLine( buf ); + if( line == null ) + throw new IncompleteHandshakeException( buf.capacity() + 128 ); + + String[] firstLineTokens = line.split( " ", 3 );// eg. HTTP/1.1 101 Switching the Protocols + if( firstLineTokens.length != 3 ) { + throw new InvalidHandshakeException(); + } + + if( role == Role.CLIENT ) { + // translating/parsing the response from the SERVER + handshake = new HandshakeImpl1Server(); + ServerHandshakeBuilder serverhandshake = (ServerHandshakeBuilder) handshake; + serverhandshake.setHttpStatus( Short.parseShort( firstLineTokens[ 1 ] ) ); + serverhandshake.setHttpStatusMessage( firstLineTokens[ 2 ] ); + } else { + // translating/parsing the request from the CLIENT + ClientHandshakeBuilder clienthandshake = new HandshakeImpl1Client(); + clienthandshake.setResourceDescriptor( firstLineTokens[ 1 ] ); + handshake = clienthandshake; + } + + line = readStringLine( buf ); + while ( line != null && line.length() > 0 ) { + String[] pair = line.split( ":", 2 ); + if( pair.length != 2 ) + throw new InvalidHandshakeException( "not an http header" ); + handshake.put( pair[ 0 ], pair[ 1 ].replaceFirst( "^ +", "" ) ); + line = readStringLine( buf ); + } + if( line == null ) + throw new IncompleteHandshakeException(); + return handshake; + } + + public abstract HandshakeState acceptHandshakeAsClient( ClientHandshake request, ServerHandshake response ) throws InvalidHandshakeException; + + public abstract HandshakeState acceptHandshakeAsServer( ClientHandshake handshakedata ) throws InvalidHandshakeException; + + protected boolean basicAccept( Handshakedata handshakedata ) { + return handshakedata.getFieldValue( "Upgrade" ).equalsIgnoreCase( "websocket" ) && handshakedata.getFieldValue( "Connection" ).toLowerCase( Locale.ENGLISH ).contains( "upgrade" ); + } + + public abstract ByteBuffer createBinaryFrame( Framedata framedata ); // TODO Allow to send data on the base of an Iterator or InputStream + + public abstract List<Framedata> createFrames( ByteBuffer binary, boolean mask ); + + public abstract List<Framedata> createFrames( String text, boolean mask ); + + public List<Framedata> continuousFrame( Opcode op, ByteBuffer buffer, boolean fin ) { + if( op != Opcode.BINARY && op != Opcode.TEXT && op != Opcode.TEXT ) { + throw new IllegalArgumentException( "Only Opcode.BINARY or Opcode.TEXT are allowed" ); + } + + if( continuousFrameType != null ) { + continuousFrameType = Opcode.CONTINUOUS; + } else { + continuousFrameType = op; + } + + FrameBuilder bui = new FramedataImpl1( continuousFrameType ); + try { + bui.setPayload( buffer ); + } catch ( InvalidDataException e ) { + throw new RuntimeException( e ); // can only happen when one builds close frames(Opcode.Close) + } + bui.setFin( fin ); + if( fin ) { + continuousFrameType = null; + } else { + continuousFrameType = op; + } + return Collections.singletonList( (Framedata) bui ); + } + + public abstract void reset(); + + public List<ByteBuffer> createHandshake( Handshakedata handshakedata, Role ownrole ) { + return createHandshake( handshakedata, ownrole, true ); + } + + public List<ByteBuffer> createHandshake( Handshakedata handshakedata, Role ownrole, boolean withcontent ) { + StringBuilder bui = new StringBuilder( 100 ); + if( handshakedata instanceof ClientHandshake ) { + bui.append( "GET " ); + bui.append( ( (ClientHandshake) handshakedata ).getResourceDescriptor() ); + bui.append( " HTTP/1.1" ); + } else if( handshakedata instanceof ServerHandshake ) { + bui.append( "HTTP/1.1 101 " + ( (ServerHandshake) handshakedata ).getHttpStatusMessage() ); + } else { + throw new RuntimeException( "unknow role" ); + } + bui.append( "\r\n" ); + Iterator<String> it = handshakedata.iterateHttpFields(); + while ( it.hasNext() ) { + String fieldname = it.next(); + String fieldvalue = handshakedata.getFieldValue( fieldname ); + bui.append( fieldname ); + bui.append( ": " ); + bui.append( fieldvalue ); + bui.append( "\r\n" ); + } + bui.append( "\r\n" ); + byte[] httpheader = Charsetfunctions.asciiBytes( bui.toString() ); + + byte[] content = withcontent ? handshakedata.getContent() : null; + ByteBuffer bytebuffer = ByteBuffer.allocate( ( content == null ? 0 : content.length ) + httpheader.length ); + bytebuffer.put( httpheader ); + if( content != null ) + bytebuffer.put( content ); + bytebuffer.flip(); + return Collections.singletonList( bytebuffer ); + } + + public abstract ClientHandshakeBuilder postProcessHandshakeRequestAsClient( ClientHandshakeBuilder request ) throws InvalidHandshakeException; + + public abstract HandshakeBuilder postProcessHandshakeResponseAsServer( ClientHandshake request, ServerHandshakeBuilder response ) throws InvalidHandshakeException; + + public abstract List<Framedata> translateFrame( ByteBuffer buffer ) throws InvalidDataException; + + public abstract CloseHandshakeType getCloseHandshakeType(); + + /** + * Drafts must only be by one websocket at all. To prevent drafts to be used more than once the Websocket implementation should call this method in order to create a new usable version of a given draft instance.<br> + * The copy can be safely used in conjunction with a new websocket connection. + * */ + public abstract Draft copyInstance(); + + public Handshakedata translateHandshake( ByteBuffer buf ) throws InvalidHandshakeException { + return translateHandshakeHttp( buf, role ); + } + + public int checkAlloc( int bytecount ) throws LimitExedeedException , InvalidDataException { + if( bytecount < 0 ) + throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "Negative count" ); + return bytecount; + } + + public void setParseMode( Role role ) { + this.role = role; + } + + public Role getRole() { + return role; + } + +} diff --git a/cb-tools/java-source/java-source/src/main/java/org/java_websocket/drafts/Draft_10.java b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/drafts/Draft_10.java new file mode 100644 index 00000000..305460a5 --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/drafts/Draft_10.java @@ -0,0 +1,397 @@ +package org.java_websocket.drafts; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Random; + +import org.java_websocket.WebSocket.Role; +import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.exceptions.InvalidFrameException; +import org.java_websocket.exceptions.InvalidHandshakeException; +import org.java_websocket.exceptions.LimitExedeedException; +import org.java_websocket.exceptions.NotSendableException; +import org.java_websocket.framing.CloseFrameBuilder; +import org.java_websocket.framing.FrameBuilder; +import org.java_websocket.framing.Framedata; +import org.java_websocket.framing.Framedata.Opcode; +import org.java_websocket.framing.FramedataImpl1; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.ClientHandshakeBuilder; +import org.java_websocket.handshake.HandshakeBuilder; +import org.java_websocket.handshake.Handshakedata; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.handshake.ServerHandshakeBuilder; +import org.java_websocket.util.Base64; +import org.java_websocket.util.Charsetfunctions; + +public class Draft_10 extends Draft { + + private class IncompleteException extends Throwable { + + /** + * It's Serializable. + */ + private static final long serialVersionUID = 7330519489840500997L; + + private int preferedsize; + public IncompleteException( int preferedsize ) { + this.preferedsize = preferedsize; + } + public int getPreferedSize() { + return preferedsize; + } + } + + public static int readVersion( Handshakedata handshakedata ) { + String vers = handshakedata.getFieldValue( "Sec-WebSocket-Version" ); + if( vers.length() > 0 ) { + int v; + try { + v = new Integer( vers.trim() ); + return v; + } catch ( NumberFormatException e ) { + return -1; + } + } + return -1; + } + + private ByteBuffer incompleteframe; + private Framedata fragmentedframe = null; + + private final Random reuseableRandom = new Random(); + + @Override + public HandshakeState acceptHandshakeAsClient( ClientHandshake request, ServerHandshake response ) throws InvalidHandshakeException { + if( !request.hasFieldValue( "Sec-WebSocket-Key" ) || !response.hasFieldValue( "Sec-WebSocket-Accept" ) ) + return HandshakeState.NOT_MATCHED; + + String seckey_answere = response.getFieldValue( "Sec-WebSocket-Accept" ); + String seckey_challenge = request.getFieldValue( "Sec-WebSocket-Key" ); + seckey_challenge = generateFinalKey( seckey_challenge ); + + if( seckey_challenge.equals( seckey_answere ) ) + return HandshakeState.MATCHED; + return HandshakeState.NOT_MATCHED; + } + + @Override + public HandshakeState acceptHandshakeAsServer( ClientHandshake handshakedata ) throws InvalidHandshakeException { + // Sec-WebSocket-Origin is only required for browser clients + int v = readVersion( handshakedata ); + if( v == 7 || v == 8 )// g + return basicAccept( handshakedata ) ? HandshakeState.MATCHED : HandshakeState.NOT_MATCHED; + return HandshakeState.NOT_MATCHED; + } + + @Override + public ByteBuffer createBinaryFrame( Framedata framedata ) { + ByteBuffer mes = framedata.getPayloadData(); + boolean mask = role == Role.CLIENT; // framedata.getTransfereMasked(); + int sizebytes = mes.remaining() <= 125 ? 1 : mes.remaining() <= 65535 ? 2 : 8; + ByteBuffer buf = ByteBuffer.allocate( 1 + ( sizebytes > 1 ? sizebytes + 1 : sizebytes ) + ( mask ? 4 : 0 ) + mes.remaining() ); + byte optcode = fromOpcode( framedata.getOpcode() ); + byte one = (byte) ( framedata.isFin() ? -128 : 0 ); + one |= optcode; + buf.put( one ); + byte[] payloadlengthbytes = toByteArray( mes.remaining(), sizebytes ); + assert ( payloadlengthbytes.length == sizebytes ); + + if( sizebytes == 1 ) { + buf.put( (byte) ( (byte) payloadlengthbytes[ 0 ] | ( mask ? (byte) -128 : 0 ) ) ); + } else if( sizebytes == 2 ) { + buf.put( (byte) ( (byte) 126 | ( mask ? (byte) -128 : 0 ) ) ); + buf.put( payloadlengthbytes ); + } else if( sizebytes == 8 ) { + buf.put( (byte) ( (byte) 127 | ( mask ? (byte) -128 : 0 ) ) ); + buf.put( payloadlengthbytes ); + } else + throw new RuntimeException( "Size representation not supported/specified" ); + + if( mask ) { + ByteBuffer maskkey = ByteBuffer.allocate( 4 ); + maskkey.putInt( reuseableRandom.nextInt() ); + buf.put( maskkey.array() ); + for( int i = 0 ; mes.hasRemaining() ; i++ ) { + buf.put( (byte) ( mes.get() ^ maskkey.get( i % 4 ) ) ); + } + } else + buf.put( mes ); + // translateFrame ( buf.array () , buf.array ().length ); + assert ( buf.remaining() == 0 ) : buf.remaining(); + buf.flip(); + + return buf; + } + + @Override + public List<Framedata> createFrames( ByteBuffer binary, boolean mask ) { + FrameBuilder curframe = new FramedataImpl1(); + try { + curframe.setPayload( binary ); + } catch ( InvalidDataException e ) { + throw new NotSendableException( e ); + } + curframe.setFin( true ); + curframe.setOptcode( Opcode.BINARY ); + curframe.setTransferemasked( mask ); + return Collections.singletonList( (Framedata) curframe ); + } + + @Override + public List<Framedata> createFrames( String text, boolean mask ) { + FrameBuilder curframe = new FramedataImpl1(); + try { + curframe.setPayload( ByteBuffer.wrap( Charsetfunctions.utf8Bytes( text ) ) ); + } catch ( InvalidDataException e ) { + throw new NotSendableException( e ); + } + curframe.setFin( true ); + curframe.setOptcode( Opcode.TEXT ); + curframe.setTransferemasked( mask ); + return Collections.singletonList( (Framedata) curframe ); + } + + private byte fromOpcode( Opcode opcode ) { + if( opcode == Opcode.CONTINUOUS ) + return 0; + else if( opcode == Opcode.TEXT ) + return 1; + else if( opcode == Opcode.BINARY ) + return 2; + else if( opcode == Opcode.CLOSING ) + return 8; + else if( opcode == Opcode.PING ) + return 9; + else if( opcode == Opcode.PONG ) + return 10; + throw new RuntimeException( "Don't know how to handle " + opcode.toString() ); + } + + private String generateFinalKey( String in ) { + String seckey = in.trim(); + String acc = seckey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + MessageDigest sh1; + try { + sh1 = MessageDigest.getInstance( "SHA1" ); + } catch ( NoSuchAlgorithmException e ) { + throw new RuntimeException( e ); + } + return Base64.encodeBytes( sh1.digest( acc.getBytes() ) ); + } + + @Override + public ClientHandshakeBuilder postProcessHandshakeRequestAsClient( ClientHandshakeBuilder request ) { + request.put( "Upgrade", "websocket" ); + request.put( "Connection", "Upgrade" ); // to respond to a Connection keep alives + request.put( "Sec-WebSocket-Version", "8" ); + + byte[] random = new byte[ 16 ]; + reuseableRandom.nextBytes( random ); + request.put( "Sec-WebSocket-Key", Base64.encodeBytes( random ) ); + + return request; + } + + @Override + public HandshakeBuilder postProcessHandshakeResponseAsServer( ClientHandshake request, ServerHandshakeBuilder response ) throws InvalidHandshakeException { + response.put( "Upgrade", "websocket" ); + response.put( "Connection", request.getFieldValue( "Connection" ) ); // to respond to a Connection keep alives + response.setHttpStatusMessage( "Switching Protocols" ); + String seckey = request.getFieldValue( "Sec-WebSocket-Key" ); + if( seckey == null ) + throw new InvalidHandshakeException( "missing Sec-WebSocket-Key" ); + response.put( "Sec-WebSocket-Accept", generateFinalKey( seckey ) ); + return response; + } + + private byte[] toByteArray( long val, int bytecount ) { + byte[] buffer = new byte[ bytecount ]; + int highest = 8 * bytecount - 8; + for( int i = 0 ; i < bytecount ; i++ ) { + buffer[ i ] = (byte) ( val >>> ( highest - 8 * i ) ); + } + return buffer; + } + + private Opcode toOpcode( byte opcode ) throws InvalidFrameException { + switch ( opcode ) { + case 0: + return Opcode.CONTINUOUS; + case 1: + return Opcode.TEXT; + case 2: + return Opcode.BINARY; + // 3-7 are not yet defined + case 8: + return Opcode.CLOSING; + case 9: + return Opcode.PING; + case 10: + return Opcode.PONG; + // 11-15 are not yet defined + default : + throw new InvalidFrameException( "unknow optcode " + (short) opcode ); + } + } + + @Override + public List<Framedata> translateFrame( ByteBuffer buffer ) throws LimitExedeedException , InvalidDataException { + List<Framedata> frames = new LinkedList<Framedata>(); + Framedata cur; + + if( incompleteframe != null ) { + // complete an incomplete frame + while ( true ) { + try { + buffer.mark(); + int available_next_byte_count = buffer.remaining();// The number of bytes received + int expected_next_byte_count = incompleteframe.remaining();// The number of bytes to complete the incomplete frame + + if( expected_next_byte_count > available_next_byte_count ) { + // did not receive enough bytes to complete the frame + incompleteframe.put( buffer.array(), buffer.position(), available_next_byte_count ); + buffer.position( buffer.position() + available_next_byte_count ); + return Collections.emptyList(); + } + incompleteframe.put( buffer.array(), buffer.position(), expected_next_byte_count ); + buffer.position( buffer.position() + expected_next_byte_count ); + + cur = translateSingleFrame( (ByteBuffer) incompleteframe.duplicate().position( 0 ) ); + frames.add( cur ); + incompleteframe = null; + break; // go on with the normal frame receival + } catch ( IncompleteException e ) { + // extending as much as suggested + int oldsize = incompleteframe.limit(); + ByteBuffer extendedframe = ByteBuffer.allocate( checkAlloc( e.getPreferedSize() ) ); + assert ( extendedframe.limit() > incompleteframe.limit() ); + incompleteframe.rewind(); + extendedframe.put( incompleteframe ); + incompleteframe = extendedframe; + + return translateFrame( buffer ); + } + } + } + + while ( buffer.hasRemaining() ) {// Read as much as possible full frames + buffer.mark(); + try { + cur = translateSingleFrame( buffer ); + frames.add( cur ); + } catch ( IncompleteException e ) { + // remember the incomplete data + buffer.reset(); + int pref = e.getPreferedSize(); + incompleteframe = ByteBuffer.allocate( checkAlloc( pref ) ); + incompleteframe.put( buffer ); + break; + } + } + return frames; + } + + public Framedata translateSingleFrame( ByteBuffer buffer ) throws IncompleteException , InvalidDataException { + int maxpacketsize = buffer.remaining(); + int realpacketsize = 2; + if( maxpacketsize < realpacketsize ) + throw new IncompleteException( realpacketsize ); + byte b1 = buffer.get( /*0*/); + boolean FIN = b1 >> 8 != 0; + byte rsv = (byte) ( ( b1 & ~(byte) 128 ) >> 4 ); + if( rsv != 0 ) + throw new InvalidFrameException( "bad rsv " + rsv ); + byte b2 = buffer.get( /*1*/); + boolean MASK = ( b2 & -128 ) != 0; + int payloadlength = (byte) ( b2 & ~(byte) 128 ); + Opcode optcode = toOpcode( (byte) ( b1 & 15 ) ); + + if( !FIN ) { + if( optcode == Opcode.PING || optcode == Opcode.PONG || optcode == Opcode.CLOSING ) { + throw new InvalidFrameException( "control frames may no be fragmented" ); + } + } + + if( payloadlength >= 0 && payloadlength <= 125 ) { + } else { + if( optcode == Opcode.PING || optcode == Opcode.PONG || optcode == Opcode.CLOSING ) { + throw new InvalidFrameException( "more than 125 octets" ); + } + if( payloadlength == 126 ) { + realpacketsize += 2; // additional length bytes + if( maxpacketsize < realpacketsize ) + throw new IncompleteException( realpacketsize ); + byte[] sizebytes = new byte[ 3 ]; + sizebytes[ 1 ] = buffer.get( /*1 + 1*/); + sizebytes[ 2 ] = buffer.get( /*1 + 2*/); + payloadlength = new BigInteger( sizebytes ).intValue(); + } else { + realpacketsize += 8; // additional length bytes + if( maxpacketsize < realpacketsize ) + throw new IncompleteException( realpacketsize ); + byte[] bytes = new byte[ 8 ]; + for( int i = 0 ; i < 8 ; i++ ) { + bytes[ i ] = buffer.get( /*1 + i*/); + } + long length = new BigInteger( bytes ).longValue(); + if( length > Integer.MAX_VALUE ) { + throw new LimitExedeedException( "Payloadsize is to big..." ); + } else { + payloadlength = (int) length; + } + } + } + + // int maskskeystart = foff + realpacketsize; + realpacketsize += ( MASK ? 4 : 0 ); + // int payloadstart = foff + realpacketsize; + realpacketsize += payloadlength; + + if( maxpacketsize < realpacketsize ) + throw new IncompleteException( realpacketsize ); + + ByteBuffer payload = ByteBuffer.allocate( checkAlloc( payloadlength ) ); + if( MASK ) { + byte[] maskskey = new byte[ 4 ]; + buffer.get( maskskey ); + for( int i = 0 ; i < payloadlength ; i++ ) { + payload.put( (byte) ( (byte) buffer.get( /*payloadstart + i*/) ^ (byte) maskskey[ i % 4 ] ) ); + } + } else { + payload.put( buffer.array(), buffer.position(), payload.limit() ); + buffer.position( buffer.position() + payload.limit() ); + } + + FrameBuilder frame; + if( optcode == Opcode.CLOSING ) { + frame = new CloseFrameBuilder(); + } else { + frame = new FramedataImpl1(); + frame.setFin( FIN ); + frame.setOptcode( optcode ); + } + payload.flip(); + frame.setPayload( payload ); + return frame; + } + + @Override + public void reset() { + incompleteframe = null; + } + + @Override + public Draft copyInstance() { + return new Draft_10(); + } + + @Override + public CloseHandshakeType getCloseHandshakeType() { + return CloseHandshakeType.TWOWAY; + } +} diff --git a/cb-tools/java-source/java-source/src/main/java/org/java_websocket/drafts/Draft_17.java b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/drafts/Draft_17.java new file mode 100644 index 00000000..5c4088f7 --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/drafts/Draft_17.java @@ -0,0 +1,28 @@ +package org.java_websocket.drafts; + +import org.java_websocket.exceptions.InvalidHandshakeException; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.ClientHandshakeBuilder; + +public class Draft_17 extends Draft_10 { + @Override + public HandshakeState acceptHandshakeAsServer( ClientHandshake handshakedata ) throws InvalidHandshakeException { + int v = readVersion( handshakedata ); + if( v == 13 ) + return HandshakeState.MATCHED; + return HandshakeState.NOT_MATCHED; + } + + @Override + public ClientHandshakeBuilder postProcessHandshakeRequestAsClient( ClientHandshakeBuilder request ) { + super.postProcessHandshakeRequestAsClient( request ); + request.put( "Sec-WebSocket-Version", "13" );// overwriting the previous + return request; + } + + @Override + public Draft copyInstance() { + return new Draft_17(); + } + +} diff --git a/cb-tools/java-source/java-source/src/main/java/org/java_websocket/drafts/Draft_75.java b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/drafts/Draft_75.java new file mode 100644 index 00000000..947a35ec --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/drafts/Draft_75.java @@ -0,0 +1,206 @@ +package org.java_websocket.drafts; + +import java.nio.ByteBuffer; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Random; + +import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.exceptions.InvalidFrameException; +import org.java_websocket.exceptions.InvalidHandshakeException; +import org.java_websocket.exceptions.LimitExedeedException; +import org.java_websocket.exceptions.NotSendableException; +import org.java_websocket.framing.CloseFrame; +import org.java_websocket.framing.FrameBuilder; +import org.java_websocket.framing.Framedata; +import org.java_websocket.framing.Framedata.Opcode; +import org.java_websocket.framing.FramedataImpl1; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.ClientHandshakeBuilder; +import org.java_websocket.handshake.HandshakeBuilder; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.handshake.ServerHandshakeBuilder; +import org.java_websocket.util.Charsetfunctions; + +public class Draft_75 extends Draft { + + /** + * The byte representing CR, or Carriage Return, or \r + */ + public static final byte CR = (byte) 0x0D; + /** + * The byte representing LF, or Line Feed, or \n + */ + public static final byte LF = (byte) 0x0A; + /** + * The byte representing the beginning of a WebSocket text frame. + */ + public static final byte START_OF_FRAME = (byte) 0x00; + /** + * The byte representing the end of a WebSocket text frame. + */ + public static final byte END_OF_FRAME = (byte) 0xFF; + + /** Is only used to detect protocol violations */ + protected boolean readingState = false; + + protected List<Framedata> readyframes = new LinkedList<Framedata>(); + protected ByteBuffer currentFrame; + + private final Random reuseableRandom = new Random(); + + @Override + public HandshakeState acceptHandshakeAsClient( ClientHandshake request, ServerHandshake response ) { + return request.getFieldValue( "WebSocket-Origin" ).equals( response.getFieldValue( "Origin" ) ) && basicAccept( response ) ? HandshakeState.MATCHED : HandshakeState.NOT_MATCHED; + } + + @Override + public HandshakeState acceptHandshakeAsServer( ClientHandshake handshakedata ) { + if( handshakedata.hasFieldValue( "Origin" ) && basicAccept( handshakedata ) ) { + return HandshakeState.MATCHED; + } + return HandshakeState.NOT_MATCHED; + } + + @Override + public ByteBuffer createBinaryFrame( Framedata framedata ) { + if( framedata.getOpcode() != Opcode.TEXT ) { + throw new RuntimeException( "only text frames supported" ); + } + + ByteBuffer pay = framedata.getPayloadData(); + ByteBuffer b = ByteBuffer.allocate( pay.remaining() + 2 ); + b.put( START_OF_FRAME ); + pay.mark(); + b.put( pay ); + pay.reset(); + b.put( END_OF_FRAME ); + b.flip(); + return b; + } + + @Override + public List<Framedata> createFrames( ByteBuffer binary, boolean mask ) { + throw new RuntimeException( "not yet implemented" ); + } + + @Override + public List<Framedata> createFrames( String text, boolean mask ) { + FrameBuilder frame = new FramedataImpl1(); + try { + frame.setPayload( ByteBuffer.wrap( Charsetfunctions.utf8Bytes( text ) ) ); + } catch ( InvalidDataException e ) { + throw new NotSendableException( e ); + } + frame.setFin( true ); + frame.setOptcode( Opcode.TEXT ); + frame.setTransferemasked( mask ); + return Collections.singletonList( (Framedata) frame ); + } + + @Override + public ClientHandshakeBuilder postProcessHandshakeRequestAsClient( ClientHandshakeBuilder request ) throws InvalidHandshakeException { + request.put( "Upgrade", "WebSocket" ); + request.put( "Connection", "Upgrade" ); + if( !request.hasFieldValue( "Origin" ) ) { + request.put( "Origin", "random" + reuseableRandom.nextInt() ); + } + + return request; + } + + @Override + public HandshakeBuilder postProcessHandshakeResponseAsServer( ClientHandshake request, ServerHandshakeBuilder response ) throws InvalidHandshakeException { + response.setHttpStatusMessage( "Web Socket Protocol Handshake" ); + response.put( "Upgrade", "WebSocket" ); + response.put( "Connection", request.getFieldValue( "Connection" ) ); // to respond to a Connection keep alive + response.put( "WebSocket-Origin", request.getFieldValue( "Origin" ) ); + String location = "ws://" + request.getFieldValue( "Host" ) + request.getResourceDescriptor(); + response.put( "WebSocket-Location", location ); + // TODO handle Sec-WebSocket-Protocol and Set-Cookie + return response; + } + + protected List<Framedata> translateRegularFrame( ByteBuffer buffer ) throws InvalidDataException { + + while ( buffer.hasRemaining() ) { + byte newestByte = buffer.get(); + if( newestByte == START_OF_FRAME ) { // Beginning of Frame + if( readingState ) + throw new InvalidFrameException( "unexpected START_OF_FRAME" ); + readingState = true; + } else if( newestByte == END_OF_FRAME ) { // End of Frame + if( !readingState ) + throw new InvalidFrameException( "unexpected END_OF_FRAME" ); + // currentFrame will be null if END_OF_FRAME was send directly after + // START_OF_FRAME, thus we will send 'null' as the sent message. + if( this.currentFrame != null ) { + currentFrame.flip(); + FramedataImpl1 curframe = new FramedataImpl1(); + curframe.setPayload( currentFrame ); + curframe.setFin( true ); + curframe.setOptcode( Opcode.TEXT ); + readyframes.add( curframe ); + this.currentFrame = null; + buffer.mark(); + } + readingState = false; + } else if( readingState ) { // Regular frame data, add to current frame buffer //TODO This code is very expensive and slow + if( currentFrame == null ) { + currentFrame = createBuffer(); + } else if( !currentFrame.hasRemaining() ) { + currentFrame = increaseBuffer( currentFrame ); + } + currentFrame.put( newestByte ); + } else { + return null; + } + } + + // if no error occurred this block will be reached + /*if( readingState ) { + checkAlloc(currentFrame.position()+1); + }*/ + + List<Framedata> frames = readyframes; + readyframes = new LinkedList<Framedata>(); + return frames; + } + + @Override + public List<Framedata> translateFrame( ByteBuffer buffer ) throws InvalidDataException { + List<Framedata> frames = translateRegularFrame( buffer ); + if( frames == null ) { + throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR ); + } + return frames; + } + + @Override + public void reset() { + readingState = false; + this.currentFrame = null; + } + + @Override + public CloseHandshakeType getCloseHandshakeType() { + return CloseHandshakeType.NONE; + } + + public ByteBuffer createBuffer() { + return ByteBuffer.allocate( INITIAL_FAMESIZE ); + } + + public ByteBuffer increaseBuffer( ByteBuffer full ) throws LimitExedeedException , InvalidDataException { + full.flip(); + ByteBuffer newbuffer = ByteBuffer.allocate( checkAlloc( full.capacity() * 2 ) ); + newbuffer.put( full ); + return newbuffer; + } + + @Override + public Draft copyInstance() { + return new Draft_75(); + } +} diff --git a/cb-tools/java-source/java-source/src/main/java/org/java_websocket/drafts/Draft_76.java b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/drafts/Draft_76.java new file mode 100644 index 00000000..26f23531 --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/drafts/Draft_76.java @@ -0,0 +1,242 @@ +package org.java_websocket.drafts; + +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.Random; + +import org.java_websocket.WebSocket.Role; +import org.java_websocket.exceptions.IncompleteHandshakeException; +import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.exceptions.InvalidFrameException; +import org.java_websocket.exceptions.InvalidHandshakeException; +import org.java_websocket.framing.CloseFrame; +import org.java_websocket.framing.CloseFrameBuilder; +import org.java_websocket.framing.Framedata; +import org.java_websocket.framing.Framedata.Opcode; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.ClientHandshakeBuilder; +import org.java_websocket.handshake.HandshakeBuilder; +import org.java_websocket.handshake.Handshakedata; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.handshake.ServerHandshakeBuilder; + +public class Draft_76 extends Draft_75 { + private boolean failed = false; + private static final byte[] closehandshake = { -1, 0 }; + + private final Random reuseableRandom = new Random(); + + + public static byte[] createChallenge( String key1, String key2, byte[] key3 ) throws InvalidHandshakeException { + byte[] part1 = getPart( key1 ); + byte[] part2 = getPart( key2 ); + byte[] challenge = new byte[ 16 ]; + challenge[ 0 ] = part1[ 0 ]; + challenge[ 1 ] = part1[ 1 ]; + challenge[ 2 ] = part1[ 2 ]; + challenge[ 3 ] = part1[ 3 ]; + challenge[ 4 ] = part2[ 0 ]; + challenge[ 5 ] = part2[ 1 ]; + challenge[ 6 ] = part2[ 2 ]; + challenge[ 7 ] = part2[ 3 ]; + challenge[ 8 ] = key3[ 0 ]; + challenge[ 9 ] = key3[ 1 ]; + challenge[ 10 ] = key3[ 2 ]; + challenge[ 11 ] = key3[ 3 ]; + challenge[ 12 ] = key3[ 4 ]; + challenge[ 13 ] = key3[ 5 ]; + challenge[ 14 ] = key3[ 6 ]; + challenge[ 15 ] = key3[ 7 ]; + MessageDigest md5; + try { + md5 = MessageDigest.getInstance( "MD5" ); + } catch ( NoSuchAlgorithmException e ) { + throw new RuntimeException( e ); + } + return md5.digest( challenge ); + } + + private static String generateKey() { + Random r = new Random(); + long maxNumber = 4294967295L; + long spaces = r.nextInt( 12 ) + 1; + int max = new Long( maxNumber / spaces ).intValue(); + max = Math.abs( max ); + int number = r.nextInt( max ) + 1; + long product = number * spaces; + String key = Long.toString( product ); + // always insert atleast one random character + int numChars = r.nextInt( 12 ) + 1; + for( int i = 0 ; i < numChars ; i++ ) { + int position = r.nextInt( key.length() ); + position = Math.abs( position ); + char randChar = (char) ( r.nextInt( 95 ) + 33 ); + // exclude numbers here + if( randChar >= 48 && randChar <= 57 ) { + randChar -= 15; + } + key = new StringBuilder( key ).insert( position, randChar ).toString(); + } + for( int i = 0 ; i < spaces ; i++ ) { + int position = r.nextInt( key.length() - 1 ) + 1; + position = Math.abs( position ); + key = new StringBuilder( key ).insert( position, "\u0020" ).toString(); + } + return key; + } + + private static byte[] getPart( String key ) throws InvalidHandshakeException { + try { + long keyNumber = Long.parseLong( key.replaceAll( "[^0-9]", "" ) ); + long keySpace = key.split( "\u0020" ).length - 1; + if( keySpace == 0 ) { + throw new InvalidHandshakeException( "invalid Sec-WebSocket-Key (/key2/)" ); + } + long part = new Long( keyNumber / keySpace ); + return new byte[]{ (byte) ( part >> 24 ), (byte) ( ( part << 8 ) >> 24 ), (byte) ( ( part << 16 ) >> 24 ), (byte) ( ( part << 24 ) >> 24 ) }; + } catch ( NumberFormatException e ) { + throw new InvalidHandshakeException( "invalid Sec-WebSocket-Key (/key1/ or /key2/)" ); + } + } + + @Override + public HandshakeState acceptHandshakeAsClient( ClientHandshake request, ServerHandshake response ) { + if( failed ) { + return HandshakeState.NOT_MATCHED; + } + + try { + if( !response.getFieldValue( "Sec-WebSocket-Origin" ).equals( request.getFieldValue( "Origin" ) ) || !basicAccept( response ) ) { + return HandshakeState.NOT_MATCHED; + } + byte[] content = response.getContent(); + if( content == null || content.length == 0 ) { + throw new IncompleteHandshakeException(); + } + if( Arrays.equals( content, createChallenge( request.getFieldValue( "Sec-WebSocket-Key1" ), request.getFieldValue( "Sec-WebSocket-Key2" ), request.getContent() ) ) ) { + return HandshakeState.MATCHED; + } else { + return HandshakeState.NOT_MATCHED; + } + } catch ( InvalidHandshakeException e ) { + throw new RuntimeException( "bad handshakerequest", e ); + } + } + + @Override + public HandshakeState acceptHandshakeAsServer( ClientHandshake handshakedata ) { + if( handshakedata.getFieldValue( "Upgrade" ).equals( "WebSocket" ) && handshakedata.getFieldValue( "Connection" ).contains( "Upgrade" ) && handshakedata.getFieldValue( "Sec-WebSocket-Key1" ).length() > 0 && !handshakedata.getFieldValue( "Sec-WebSocket-Key2" ).isEmpty() && handshakedata.hasFieldValue( "Origin" ) ) + return HandshakeState.MATCHED; + return HandshakeState.NOT_MATCHED; + } + + @Override + public ClientHandshakeBuilder postProcessHandshakeRequestAsClient( ClientHandshakeBuilder request ) { + request.put( "Upgrade", "WebSocket" ); + request.put( "Connection", "Upgrade" ); + request.put( "Sec-WebSocket-Key1", generateKey() ); + request.put( "Sec-WebSocket-Key2", generateKey() ); + + if( !request.hasFieldValue( "Origin" ) ) { + request.put( "Origin", "random" + reuseableRandom.nextInt() ); + } + + byte[] key3 = new byte[ 8 ]; + reuseableRandom.nextBytes( key3 ); + request.setContent( key3 ); + return request; + + } + + @Override + public HandshakeBuilder postProcessHandshakeResponseAsServer( ClientHandshake request, ServerHandshakeBuilder response ) throws InvalidHandshakeException { + response.setHttpStatusMessage( "WebSocket Protocol Handshake" ); + response.put( "Upgrade", "WebSocket" ); + response.put( "Connection", request.getFieldValue( "Connection" ) ); // to respond to a Connection keep alive + response.put( "Sec-WebSocket-Origin", request.getFieldValue( "Origin" ) ); + String location = "ws://" + request.getFieldValue( "Host" ) + request.getResourceDescriptor(); + response.put( "Sec-WebSocket-Location", location ); + String key1 = request.getFieldValue( "Sec-WebSocket-Key1" ); + String key2 = request.getFieldValue( "Sec-WebSocket-Key2" ); + byte[] key3 = request.getContent(); + if( key1 == null || key2 == null || key3 == null || key3.length != 8 ) { + throw new InvalidHandshakeException( "Bad keys" ); + } + response.setContent( createChallenge( key1, key2, key3 ) ); + return response; + } + + @Override + public Handshakedata translateHandshake( ByteBuffer buf ) throws InvalidHandshakeException { + + HandshakeBuilder bui = translateHandshakeHttp( buf, role ); + // the first drafts are lacking a protocol number which makes them difficult to distinguish. Sec-WebSocket-Key1 is typical for draft76 + if( ( bui.hasFieldValue( "Sec-WebSocket-Key1" ) || role == Role.CLIENT ) && !bui.hasFieldValue( "Sec-WebSocket-Version" ) ) { + byte[] key3 = new byte[ role == Role.SERVER ? 8 : 16 ]; + try { + buf.get( key3 ); + } catch ( BufferUnderflowException e ) { + throw new IncompleteHandshakeException( buf.capacity() + 16 ); + } + bui.setContent( key3 ); + + } + return bui; + } + + @Override + public List<Framedata> translateFrame( ByteBuffer buffer ) throws InvalidDataException { + buffer.mark(); + List<Framedata> frames = super.translateRegularFrame( buffer ); + if( frames == null ) { + buffer.reset(); + frames = readyframes; + readingState = true; + if( currentFrame == null ) + currentFrame = ByteBuffer.allocate( 2 ); + else { + throw new InvalidFrameException(); + } + if( buffer.remaining() > currentFrame.remaining() ) { + throw new InvalidFrameException(); + } else { + currentFrame.put( buffer ); + } + if( !currentFrame.hasRemaining() ) { + if( Arrays.equals( currentFrame.array(), closehandshake ) ) { + frames.add( new CloseFrameBuilder( CloseFrame.NORMAL ) ); + return frames; + } + else{ + throw new InvalidFrameException(); + } + } else { + readyframes = new LinkedList<Framedata>(); + return frames; + } + } else { + return frames; + } + } + @Override + public ByteBuffer createBinaryFrame( Framedata framedata ) { + if( framedata.getOpcode() == Opcode.CLOSING ) + return ByteBuffer.wrap( closehandshake ); + return super.createBinaryFrame( framedata ); + } + + @Override + public CloseHandshakeType getCloseHandshakeType() { + return CloseHandshakeType.ONEWAY; + } + + @Override + public Draft copyInstance() { + return new Draft_76(); + } +} diff --git a/cb-tools/java-source/java-source/src/main/java/org/java_websocket/exceptions/IncompleteHandshakeException.java b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/exceptions/IncompleteHandshakeException.java new file mode 100644 index 00000000..2fdb5eae --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/exceptions/IncompleteHandshakeException.java @@ -0,0 +1,20 @@ +package org.java_websocket.exceptions; + +public class IncompleteHandshakeException extends RuntimeException { + + private static final long serialVersionUID = 7906596804233893092L; + private int newsize; + + public IncompleteHandshakeException( int newsize ) { + this.newsize = newsize; + } + + public IncompleteHandshakeException() { + this.newsize = 0; + } + + public int getPreferedSize() { + return newsize; + } + +} diff --git a/cb-tools/java-source/java-source/src/main/java/org/java_websocket/exceptions/InvalidDataException.java b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/exceptions/InvalidDataException.java new file mode 100644 index 00000000..2ab9d328 --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/exceptions/InvalidDataException.java @@ -0,0 +1,34 @@ +package org.java_websocket.exceptions; + +public class InvalidDataException extends Exception { + /** + * Serializable + */ + private static final long serialVersionUID = 3731842424390998726L; + + private int closecode; + + public InvalidDataException( int closecode ) { + this.closecode = closecode; + } + + public InvalidDataException( int closecode , String s ) { + super( s ); + this.closecode = closecode; + } + + public InvalidDataException( int closecode , Throwable t ) { + super( t ); + this.closecode = closecode; + } + + public InvalidDataException( int closecode , String s , Throwable t ) { + super( s, t ); + this.closecode = closecode; + } + + public int getCloseCode() { + return closecode; + } + +} diff --git a/cb-tools/java-source/java-source/src/main/java/org/java_websocket/exceptions/InvalidFrameException.java b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/exceptions/InvalidFrameException.java new file mode 100644 index 00000000..c7fe4101 --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/exceptions/InvalidFrameException.java @@ -0,0 +1,27 @@ +package org.java_websocket.exceptions; + +import org.java_websocket.framing.CloseFrame; + +public class InvalidFrameException extends InvalidDataException { + + /** + * Serializable + */ + private static final long serialVersionUID = -9016496369828887591L; + + public InvalidFrameException() { + super( CloseFrame.PROTOCOL_ERROR ); + } + + public InvalidFrameException( String arg0 ) { + super( CloseFrame.PROTOCOL_ERROR, arg0 ); + } + + public InvalidFrameException( Throwable arg0 ) { + super( CloseFrame.PROTOCOL_ERROR, arg0 ); + } + + public InvalidFrameException( String arg0 , Throwable arg1 ) { + super( CloseFrame.PROTOCOL_ERROR, arg0, arg1 ); + } +} diff --git a/cb-tools/java-source/java-source/src/main/java/org/java_websocket/exceptions/InvalidHandshakeException.java b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/exceptions/InvalidHandshakeException.java new file mode 100644 index 00000000..4d0baec8 --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/exceptions/InvalidHandshakeException.java @@ -0,0 +1,28 @@ +package org.java_websocket.exceptions; + +import org.java_websocket.framing.CloseFrame; + +public class InvalidHandshakeException extends InvalidDataException { + + /** + * Serializable + */ + private static final long serialVersionUID = -1426533877490484964L; + + public InvalidHandshakeException() { + super( CloseFrame.PROTOCOL_ERROR ); + } + + public InvalidHandshakeException( String arg0 , Throwable arg1 ) { + super( CloseFrame.PROTOCOL_ERROR, arg0, arg1 ); + } + + public InvalidHandshakeException( String arg0 ) { + super( CloseFrame.PROTOCOL_ERROR, arg0 ); + } + + public InvalidHandshakeException( Throwable arg0 ) { + super( CloseFrame.PROTOCOL_ERROR, arg0 ); + } + +} diff --git a/cb-tools/java-source/java-source/src/main/java/org/java_websocket/exceptions/LimitExedeedException.java b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/exceptions/LimitExedeedException.java new file mode 100644 index 00000000..1ac7f8c5 --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/exceptions/LimitExedeedException.java @@ -0,0 +1,20 @@ +package org.java_websocket.exceptions; + +import org.java_websocket.framing.CloseFrame; + +public class LimitExedeedException extends InvalidDataException { + + /** + * Serializable + */ + private static final long serialVersionUID = 6908339749836826785L; + + public LimitExedeedException() { + super( CloseFrame.TOOBIG ); + } + + public LimitExedeedException( String s ) { + super( CloseFrame.TOOBIG, s ); + } + +} diff --git a/cb-tools/java-source/java-source/src/main/java/org/java_websocket/exceptions/NotSendableException.java b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/exceptions/NotSendableException.java new file mode 100644 index 00000000..2b2e2293 --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/exceptions/NotSendableException.java @@ -0,0 +1,25 @@ +package org.java_websocket.exceptions; + +public class NotSendableException extends RuntimeException { + + /** + * Serializable + */ + private static final long serialVersionUID = -6468967874576651628L; + + public NotSendableException() { + } + + public NotSendableException( String message ) { + super( message ); + } + + public NotSendableException( Throwable cause ) { + super( cause ); + } + + public NotSendableException( String message , Throwable cause ) { + super( message, cause ); + } + +} diff --git a/cb-tools/java-source/java-source/src/main/java/org/java_websocket/exceptions/WebsocketNotConnectedException.java b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/exceptions/WebsocketNotConnectedException.java new file mode 100644 index 00000000..45c5f4df --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/exceptions/WebsocketNotConnectedException.java @@ -0,0 +1,5 @@ +package org.java_websocket.exceptions; + +public class WebsocketNotConnectedException extends RuntimeException { + +} diff --git a/cb-tools/java-source/java-source/src/main/java/org/java_websocket/framing/CloseFrame.java b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/framing/CloseFrame.java new file mode 100644 index 00000000..f253b8dd --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/framing/CloseFrame.java @@ -0,0 +1,98 @@ +package org.java_websocket.framing; + +import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.exceptions.InvalidFrameException; + +public interface CloseFrame extends Framedata { + /** + * indicates a normal closure, meaning whatever purpose the + * connection was established for has been fulfilled. + */ + public static final int NORMAL = 1000; + /** + * 1001 indicates that an endpoint is "going away", such as a server + * going down, or a browser having navigated away from a page. + */ + public static final int GOING_AWAY = 1001; + /** + * 1002 indicates that an endpoint is terminating the connection due + * to a protocol error. + */ + public static final int PROTOCOL_ERROR = 1002; + /** + * 1003 indicates that an endpoint is terminating the connection + * because it has received a type of data it cannot accept (e.g. an + * endpoint that understands only text data MAY send this if it + * receives a binary message). + */ + public static final int REFUSE = 1003; + /*1004: Reserved. The specific meaning might be defined in the future.*/ + /** + * 1005 is a reserved value and MUST NOT be set as a status code in a + * Close control frame by an endpoint. It is designated for use in + * applications expecting a status code to indicate that no status + * code was actually present. + */ + public static final int NOCODE = 1005; + /** + * 1006 is a reserved value and MUST NOT be set as a status code in a + * Close control frame by an endpoint. It is designated for use in + * applications expecting a status code to indicate that the + * connection was closed abnormally, e.g. without sending or + * receiving a Close control frame. + */ + public static final int ABNORMAL_CLOSE = 1006; + /** + * 1007 indicates that an endpoint is terminating the connection + * because it has received data within a message that was not + * consistent with the type of the message (e.g., non-UTF-8 [RFC3629] + * data within a text message). + */ + public static final int NO_UTF8 = 1007; + /** + * 1008 indicates that an endpoint is terminating the connection + * because it has received a message that violates its policy. This + * is a generic status code that can be returned when there is no + * other more suitable status code (e.g. 1003 or 1009), or if there + * is a need to hide specific details about the policy. + */ + public static final int POLICY_VALIDATION = 1008; + /** + * 1009 indicates that an endpoint is terminating the connection + * because it has received a message which is too big for it to + * process. + */ + public static final int TOOBIG = 1009; + /** + * 1010 indicates that an endpoint (client) is terminating the + * connection because it has expected the server to negotiate one or + * more extension, but the server didn't return them in the response + * message of the WebSocket handshake. The list of extensions which + * are needed SHOULD appear in the /reason/ part of the Close frame. + * Note that this status code is not used by the server, because it + * can fail the WebSocket handshake instead. + */ + public static final int EXTENSION = 1010; + /** + * 1011 indicates that a server is terminating the connection because + * it encountered an unexpected condition that prevented it from + * fulfilling the request. + **/ + public static final int UNEXPECTED_CONDITION = 1011; + /** + * 1015 is a reserved value and MUST NOT be set as a status code in a + * Close control frame by an endpoint. It is designated for use in + * applications expecting a status code to indicate that the + * connection was closed due to a failure to perform a TLS handshake + * (e.g., the server certificate can't be verified). + **/ + public static final int TLS_ERROR = 1015; + + /** The connection had never been established */ + public static final int NEVER_CONNECTED = -1; + public static final int BUGGYCLOSE = -2; + public static final int FLASHPOLICY = -3; + + public int getCloseCode() throws InvalidFrameException; + public String getMessage() throws InvalidDataException; +} diff --git a/cb-tools/java-source/java-source/src/main/java/org/java_websocket/framing/CloseFrameBuilder.java b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/framing/CloseFrameBuilder.java new file mode 100644 index 00000000..fee1b540 --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/framing/CloseFrameBuilder.java @@ -0,0 +1,123 @@ +package org.java_websocket.framing; + +import java.nio.ByteBuffer; + +import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.exceptions.InvalidFrameException; +import org.java_websocket.util.Charsetfunctions; + +public class CloseFrameBuilder extends FramedataImpl1 implements CloseFrame { + + static final ByteBuffer emptybytebuffer = ByteBuffer.allocate( 0 ); + + private int code; + private String reason; + + public CloseFrameBuilder() { + super( Opcode.CLOSING ); + setFin( true ); + } + + public CloseFrameBuilder( int code ) throws InvalidDataException { + super( Opcode.CLOSING ); + setFin( true ); + setCodeAndMessage( code, "" ); + } + + public CloseFrameBuilder( int code , String m ) throws InvalidDataException { + super( Opcode.CLOSING ); + setFin( true ); + setCodeAndMessage( code, m ); + } + + private void setCodeAndMessage( int code, String m ) throws InvalidDataException { + if( m == null ) { + m = ""; + } + // CloseFrame.TLS_ERROR is not allowed to be transfered over the wire + if( code == CloseFrame.TLS_ERROR ) { + code = CloseFrame.NOCODE; + m = ""; + } + if( code == CloseFrame.NOCODE ) { + if( 0 < m.length() ) { + throw new InvalidDataException( PROTOCOL_ERROR, "A close frame must have a closecode if it has a reason" ); + } + return;// empty payload + } + + byte[] by = Charsetfunctions.utf8Bytes( m ); + ByteBuffer buf = ByteBuffer.allocate( 4 ); + buf.putInt( code ); + buf.position( 2 ); + ByteBuffer pay = ByteBuffer.allocate( 2 + by.length ); + pay.put( buf ); + pay.put( by ); + pay.rewind(); + setPayload( pay ); + } + + private void initCloseCode() throws InvalidFrameException { + code = CloseFrame.NOCODE; + ByteBuffer payload = super.getPayloadData(); + payload.mark(); + if( payload.remaining() >= 2 ) { + ByteBuffer bb = ByteBuffer.allocate( 4 ); + bb.position( 2 ); + bb.putShort( payload.getShort() ); + bb.position( 0 ); + code = bb.getInt(); + + if( code == CloseFrame.ABNORMAL_CLOSE || code == CloseFrame.TLS_ERROR || code == CloseFrame.NOCODE || code > 4999 || code < 1000 || code == 1004 ) { + throw new InvalidFrameException( "closecode must not be sent over the wire: " + code ); + } + } + payload.reset(); + } + + @Override + public int getCloseCode() { + return code; + } + + private void initMessage() throws InvalidDataException { + if( code == CloseFrame.NOCODE ) { + reason = Charsetfunctions.stringUtf8( super.getPayloadData() ); + } else { + ByteBuffer b = super.getPayloadData(); + int mark = b.position();// because stringUtf8 also creates a mark + try { + b.position( b.position() + 2 ); + reason = Charsetfunctions.stringUtf8( b ); + } catch ( IllegalArgumentException e ) { + throw new InvalidFrameException( e ); + } finally { + b.position( mark ); + } + } + } + + @Override + public String getMessage() { + return reason; + } + + @Override + public String toString() { + return super.toString() + "code: " + code; + } + + @Override + public void setPayload( ByteBuffer payload ) throws InvalidDataException { + super.setPayload( payload ); + initCloseCode(); + initMessage(); + } + @Override + public ByteBuffer getPayloadData() { + if( code == NOCODE ) + return emptybytebuffer; + return super.getPayloadData(); + } + +} diff --git a/cb-tools/java-source/java-source/src/main/java/org/java_websocket/framing/FrameBuilder.java b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/framing/FrameBuilder.java new file mode 100644 index 00000000..25a20de4 --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/framing/FrameBuilder.java @@ -0,0 +1,17 @@ +package org.java_websocket.framing; + +import java.nio.ByteBuffer; + +import org.java_websocket.exceptions.InvalidDataException; + +public interface FrameBuilder extends Framedata { + + public abstract void setFin( boolean fin ); + + public abstract void setOptcode( Opcode optcode ); + + public abstract void setPayload( ByteBuffer payload ) throws InvalidDataException; + + public abstract void setTransferemasked( boolean transferemasked ); + +} \ No newline at end of file diff --git a/cb-tools/java-source/java-source/src/main/java/org/java_websocket/framing/Framedata.java b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/framing/Framedata.java new file mode 100644 index 00000000..3dfa8c08 --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/framing/Framedata.java @@ -0,0 +1,17 @@ +package org.java_websocket.framing; + +import java.nio.ByteBuffer; + +import org.java_websocket.exceptions.InvalidFrameException; + +public interface Framedata { + public enum Opcode { + CONTINUOUS, TEXT, BINARY, PING, PONG, CLOSING + // more to come + } + public boolean isFin(); + public boolean getTransfereMasked(); + public Opcode getOpcode(); + public ByteBuffer getPayloadData();// TODO the separation of the application data and the extension data is yet to be done + public abstract void append( Framedata nextframe ) throws InvalidFrameException; +} diff --git a/cb-tools/java-source/java-source/src/main/java/org/java_websocket/framing/FramedataImpl1.java b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/framing/FramedataImpl1.java new file mode 100644 index 00000000..5fba075b --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/framing/FramedataImpl1.java @@ -0,0 +1,110 @@ +package org.java_websocket.framing; + +import java.nio.ByteBuffer; +import java.util.Arrays; + +import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.exceptions.InvalidFrameException; +import org.java_websocket.util.Charsetfunctions; + +public class FramedataImpl1 implements FrameBuilder { + protected static byte[] emptyarray = {}; + protected boolean fin; + protected Opcode optcode; + private ByteBuffer unmaskedpayload; + protected boolean transferemasked; + + public FramedataImpl1() { + } + + public FramedataImpl1( Opcode op ) { + this.optcode = op; + unmaskedpayload = ByteBuffer.wrap( emptyarray ); + } + + /** + * Helper constructor which helps to create "echo" frames. + * The new object will use the same underlying payload data. + **/ + public FramedataImpl1( Framedata f ) { + fin = f.isFin(); + optcode = f.getOpcode(); + unmaskedpayload = f.getPayloadData(); + transferemasked = f.getTransfereMasked(); + } + + @Override + public boolean isFin() { + return fin; + } + + @Override + public Opcode getOpcode() { + return optcode; + } + + @Override + public boolean getTransfereMasked() { + return transferemasked; + } + + @Override + public ByteBuffer getPayloadData() { + return unmaskedpayload; + } + + @Override + public void setFin( boolean fin ) { + this.fin = fin; + } + + @Override + public void setOptcode( Opcode optcode ) { + this.optcode = optcode; + } + + @Override + public void setPayload( ByteBuffer payload ) throws InvalidDataException { + unmaskedpayload = payload; + } + + @Override + public void setTransferemasked( boolean transferemasked ) { + this.transferemasked = transferemasked; + } + + @Override + public void append( Framedata nextframe ) throws InvalidFrameException { + ByteBuffer b = nextframe.getPayloadData(); + if( unmaskedpayload == null ) { + unmaskedpayload = ByteBuffer.allocate( b.remaining() ); + b.mark(); + unmaskedpayload.put( b ); + b.reset(); + } else { + b.mark(); + unmaskedpayload.position( unmaskedpayload.limit() ); + unmaskedpayload.limit( unmaskedpayload.capacity() ); + + if( b.remaining() > unmaskedpayload.remaining() ) { + ByteBuffer tmp = ByteBuffer.allocate( b.remaining() + unmaskedpayload.capacity() ); + unmaskedpayload.flip(); + tmp.put( unmaskedpayload ); + tmp.put( b ); + unmaskedpayload = tmp; + + } else { + unmaskedpayload.put( b ); + } + unmaskedpayload.rewind(); + b.reset(); + } + fin = nextframe.isFin(); + } + + @Override + public String toString() { + return "Framedata{ optcode:" + getOpcode() + ", fin:" + isFin() + ", payloadlength:[pos:" + unmaskedpayload.position() + ", len:" + unmaskedpayload.remaining() + "], payload:" + Arrays.toString( Charsetfunctions.utf8Bytes( new String( unmaskedpayload.array() ) ) ) + "}"; + } + +} diff --git a/cb-tools/java-source/java-source/src/main/java/org/java_websocket/handshake/ClientHandshake.java b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/handshake/ClientHandshake.java new file mode 100644 index 00000000..918d2218 --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/handshake/ClientHandshake.java @@ -0,0 +1,6 @@ +package org.java_websocket.handshake; + +public interface ClientHandshake extends Handshakedata { + /**returns the HTTP Request-URI as defined by http://tools.ietf.org/html/rfc2616#section-5.1.2*/ + public String getResourceDescriptor(); +} diff --git a/cb-tools/java-source/java-source/src/main/java/org/java_websocket/handshake/ClientHandshakeBuilder.java b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/handshake/ClientHandshakeBuilder.java new file mode 100644 index 00000000..88ac4f27 --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/handshake/ClientHandshakeBuilder.java @@ -0,0 +1,5 @@ +package org.java_websocket.handshake; + +public interface ClientHandshakeBuilder extends HandshakeBuilder, ClientHandshake { + public void setResourceDescriptor( String resourceDescriptor ); +} diff --git a/cb-tools/java-source/java-source/src/main/java/org/java_websocket/handshake/HandshakeBuilder.java b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/handshake/HandshakeBuilder.java new file mode 100644 index 00000000..8a6236ca --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/handshake/HandshakeBuilder.java @@ -0,0 +1,6 @@ +package org.java_websocket.handshake; + +public interface HandshakeBuilder extends Handshakedata { + public abstract void setContent( byte[] content ); + public abstract void put( String name, String value ); +} diff --git a/cb-tools/java-source/java-source/src/main/java/org/java_websocket/handshake/HandshakeImpl1Client.java b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/handshake/HandshakeImpl1Client.java new file mode 100644 index 00000000..15715e37 --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/handshake/HandshakeImpl1Client.java @@ -0,0 +1,18 @@ +package org.java_websocket.handshake; + +public class HandshakeImpl1Client extends HandshakedataImpl1 implements ClientHandshakeBuilder { + private String resourceDescriptor = "*"; + + public HandshakeImpl1Client() { + } + + public void setResourceDescriptor( String resourceDescriptor ) throws IllegalArgumentException { + if(resourceDescriptor==null) + throw new IllegalArgumentException( "http resource descriptor must not be null" ); + this.resourceDescriptor = resourceDescriptor; + } + + public String getResourceDescriptor() { + return resourceDescriptor; + } +} diff --git a/cb-tools/java-source/java-source/src/main/java/org/java_websocket/handshake/HandshakeImpl1Server.java b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/handshake/HandshakeImpl1Server.java new file mode 100644 index 00000000..7063b892 --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/handshake/HandshakeImpl1Server.java @@ -0,0 +1,29 @@ +package org.java_websocket.handshake; + +public class HandshakeImpl1Server extends HandshakedataImpl1 implements ServerHandshakeBuilder { + private short httpstatus; + private String httpstatusmessage; + + public HandshakeImpl1Server() { + } + + @Override + public String getHttpStatusMessage() { + return httpstatusmessage; + } + + @Override + public short getHttpStatus() { + return httpstatus; + } + + public void setHttpStatusMessage( String message ) { + this.httpstatusmessage = message; + } + + public void setHttpStatus( short status ) { + httpstatus = status; + } + + +} diff --git a/cb-tools/java-source/java-source/src/main/java/org/java_websocket/handshake/Handshakedata.java b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/handshake/Handshakedata.java new file mode 100644 index 00000000..577d6ce1 --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/handshake/Handshakedata.java @@ -0,0 +1,10 @@ +package org.java_websocket.handshake; + +import java.util.Iterator; + +public interface Handshakedata { + public Iterator<String> iterateHttpFields(); + public String getFieldValue( String name ); + public boolean hasFieldValue( String name ); + public byte[] getContent(); +} diff --git a/cb-tools/java-source/java-source/src/main/java/org/java_websocket/handshake/HandshakedataImpl1.java b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/handshake/HandshakedataImpl1.java new file mode 100644 index 00000000..d4d9555c --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/handshake/HandshakedataImpl1.java @@ -0,0 +1,60 @@ +package org.java_websocket.handshake; + +import java.util.Collections; +import java.util.Iterator; +import java.util.TreeMap; + +public class HandshakedataImpl1 implements HandshakeBuilder { + private byte[] content; + private TreeMap<String,String> map; + + public HandshakedataImpl1() { + map = new TreeMap<String,String>( String.CASE_INSENSITIVE_ORDER ); + } + + /*public HandshakedataImpl1( Handshakedata h ) { + httpstatusmessage = h.getHttpStatusMessage(); + resourcedescriptor = h.getResourceDescriptor(); + content = h.getContent(); + map = new LinkedHashMap<String,String>(); + Iterator<String> it = h.iterateHttpFields(); + while ( it.hasNext() ) { + String key = (String) it.next(); + map.put( key, h.getFieldValue( key ) ); + } + }*/ + + @Override + public Iterator<String> iterateHttpFields() { + return Collections.unmodifiableSet( map.keySet() ).iterator();// Safety first + } + + @Override + public String getFieldValue( String name ) { + String s = map.get( name ); + if ( s == null ) { + return ""; + } + return s; + } + + @Override + public byte[] getContent() { + return content; + } + + @Override + public void setContent( byte[] content ) { + this.content = content; + } + + @Override + public void put( String name, String value ) { + map.put( name, value ); + } + + @Override + public boolean hasFieldValue( String name ) { + return map.containsKey( name ); + } +} diff --git a/cb-tools/java-source/java-source/src/main/java/org/java_websocket/handshake/ServerHandshake.java b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/handshake/ServerHandshake.java new file mode 100644 index 00000000..880e9b2d --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/handshake/ServerHandshake.java @@ -0,0 +1,6 @@ +package org.java_websocket.handshake; + +public interface ServerHandshake extends Handshakedata { + public short getHttpStatus(); + public String getHttpStatusMessage(); +} diff --git a/cb-tools/java-source/java-source/src/main/java/org/java_websocket/handshake/ServerHandshakeBuilder.java b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/handshake/ServerHandshakeBuilder.java new file mode 100644 index 00000000..d518dfb1 --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/handshake/ServerHandshakeBuilder.java @@ -0,0 +1,6 @@ +package org.java_websocket.handshake; + +public interface ServerHandshakeBuilder extends HandshakeBuilder, ServerHandshake { + public void setHttpStatus( short status ); + public void setHttpStatusMessage( String message ); +} diff --git a/cb-tools/java-source/java-source/src/main/java/org/java_websocket/server/DefaultSSLWebSocketServerFactory.java b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/server/DefaultSSLWebSocketServerFactory.java new file mode 100644 index 00000000..b871260f --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/server/DefaultSSLWebSocketServerFactory.java @@ -0,0 +1,51 @@ +package org.java_websocket.server; +import java.io.IOException; +import java.net.Socket; +import java.nio.channels.ByteChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.SocketChannel; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; + +import org.java_websocket.SSLSocketChannel2; +import org.java_websocket.WebSocketAdapter; +import org.java_websocket.WebSocketImpl; +import org.java_websocket.drafts.Draft; + + +public class DefaultSSLWebSocketServerFactory implements WebSocketServer.WebSocketServerFactory { + protected SSLContext sslcontext; + protected ExecutorService exec; + + public DefaultSSLWebSocketServerFactory( SSLContext sslContext ) { + this( sslContext, Executors.newSingleThreadScheduledExecutor() ); + } + + public DefaultSSLWebSocketServerFactory( SSLContext sslContext , ExecutorService exec ) { + if( sslContext == null || exec == null ) + throw new IllegalArgumentException(); + this.sslcontext = sslContext; + this.exec = exec; + } + + @Override + public ByteChannel wrapChannel( SocketChannel channel, SelectionKey key ) throws IOException { + SSLEngine e = sslcontext.createSSLEngine(); + e.setUseClientMode( false ); + return new SSLSocketChannel2( channel, e, exec, key ); + } + + @Override + public WebSocketImpl createWebSocket( WebSocketAdapter a, Draft d, Socket c ) { + return new WebSocketImpl( a, d ); + } + + @Override + public WebSocketImpl createWebSocket( WebSocketAdapter a, List<Draft> d, Socket s ) { + return new WebSocketImpl( a, d ); + } +} \ No newline at end of file diff --git a/cb-tools/java-source/java-source/src/main/java/org/java_websocket/server/DefaultWebSocketServerFactory.java b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/server/DefaultWebSocketServerFactory.java new file mode 100644 index 00000000..3b89cdc2 --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/server/DefaultWebSocketServerFactory.java @@ -0,0 +1,26 @@ +package org.java_websocket.server; + +import java.net.Socket; +import java.nio.channels.SelectionKey; +import java.nio.channels.SocketChannel; +import java.util.List; + +import org.java_websocket.WebSocketAdapter; +import org.java_websocket.WebSocketImpl; +import org.java_websocket.drafts.Draft; +import org.java_websocket.server.WebSocketServer.WebSocketServerFactory; + +public class DefaultWebSocketServerFactory implements WebSocketServerFactory { + @Override + public WebSocketImpl createWebSocket( WebSocketAdapter a, Draft d, Socket s ) { + return new WebSocketImpl( a, d ); + } + @Override + public WebSocketImpl createWebSocket( WebSocketAdapter a, List<Draft> d, Socket s ) { + return new WebSocketImpl( a, d ); + } + @Override + public SocketChannel wrapChannel( SocketChannel channel, SelectionKey key ) { + return (SocketChannel) channel; + } +} \ No newline at end of file diff --git a/cb-tools/java-source/java-source/src/main/java/org/java_websocket/server/WebSocketServer.java b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/server/WebSocketServer.java new file mode 100644 index 00000000..4444ca61 --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/server/WebSocketServer.java @@ -0,0 +1,743 @@ +package org.java_websocket.server; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.nio.channels.ByteChannel; +import java.nio.channels.CancelledKeyException; +import java.nio.channels.ClosedByInterruptException; +import java.nio.channels.SelectableChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import org.java_websocket.SocketChannelIOHelper; +import org.java_websocket.WebSocket; +import org.java_websocket.WebSocketAdapter; +import org.java_websocket.WebSocketFactory; +import org.java_websocket.WebSocketImpl; +import org.java_websocket.WrappedByteChannel; +import org.java_websocket.drafts.Draft; +import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.framing.CloseFrame; +import org.java_websocket.framing.Framedata; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.Handshakedata; +import org.java_websocket.handshake.ServerHandshakeBuilder; + +/** + * <tt>WebSocketServer</tt> is an abstract class that only takes care of the + * HTTP handshake portion of WebSockets. It's up to a subclass to add + * functionality/purpose to the server. + * + */ +public abstract class WebSocketServer extends WebSocketAdapter implements Runnable { + + public static int DECODERS = Runtime.getRuntime().availableProcessors(); + + /** + * Holds the list of active WebSocket connections. "Active" means WebSocket + * handshake is complete and socket can be written to, or read from. + */ + private final Collection<WebSocket> connections; + /** + * The port number that this WebSocket server should listen on. Default is + * WebSocket.DEFAULT_PORT. + */ + private final InetSocketAddress address; + /** + * The socket channel for this WebSocket server. + */ + private ServerSocketChannel server; + /** + * The 'Selector' used to get event keys from the underlying socket. + */ + private Selector selector; + /** + * The Draft of the WebSocket protocol the Server is adhering to. + */ + private List<Draft> drafts; + + private Thread selectorthread; + + private volatile AtomicBoolean isclosed = new AtomicBoolean( false ); + + private List<WebSocketWorker> decoders; + + private List<WebSocketImpl> iqueue; + private BlockingQueue<ByteBuffer> buffers; + private int queueinvokes = 0; + private AtomicInteger queuesize = new AtomicInteger( 0 ); + + private WebSocketServerFactory wsf = new DefaultWebSocketServerFactory(); + + /** + * Creates a WebSocketServer that will attempt to + * listen on port <var>WebSocket.DEFAULT_PORT</var>. + * + * @see #WebSocketServer(InetSocketAddress, int, List, Collection) more details here + */ + public WebSocketServer() throws UnknownHostException { + this( new InetSocketAddress( WebSocket.DEFAULT_PORT ), DECODERS, null ); + } + + /** + * Creates a WebSocketServer that will attempt to bind/listen on the given <var>address</var>. + * + * @see #WebSocketServer(InetSocketAddress, int, List, Collection) more details here + */ + public WebSocketServer( InetSocketAddress address ) { + this( address, DECODERS, null ); + } + + /** + * @see #WebSocketServer(InetSocketAddress, int, List, Collection) more details here + */ + public WebSocketServer( InetSocketAddress address , int decoders ) { + this( address, decoders, null ); + } + + /** + * @see #WebSocketServer(InetSocketAddress, int, List, Collection) more details here + */ + public WebSocketServer( InetSocketAddress address , List<Draft> drafts ) { + this( address, DECODERS, drafts ); + } + + /** + * @see #WebSocketServer(InetSocketAddress, int, List, Collection) more details here + */ + public WebSocketServer( InetSocketAddress address , int decodercount , List<Draft> drafts ) { + this( address, decodercount, drafts, new HashSet<WebSocket>() ); + } + + /** + * Creates a WebSocketServer that will attempt to bind/listen on the given <var>address</var>, + * and comply with <tt>Draft</tt> version <var>draft</var>. + * + * @param address + * The address (host:port) this server should listen on. + * @param decodercount + * The number of {@link WebSocketWorker}s that will be used to process the incoming network data. By default this will be <code>Runtime.getRuntime().availableProcessors()</code> + * @param drafts + * The versions of the WebSocket protocol that this server + * instance should comply to. Clients that use an other protocol version will be rejected. + * + * @param connectionscontainer + * Allows to specify a collection that will be used to store the websockets in. <br> + * If you plan to often iterate through the currently connected websockets you may want to use a collection that does not require synchronization like a {@link CopyOnWriteArraySet}. In that case make sure that you overload {@link #removeConnection(WebSocket)} and {@link #addConnection(WebSocket)}.<br> + * By default a {@link HashSet} will be used. + * + * @see #removeConnection(WebSocket) for more control over syncronized operation + * @see <a href="https://github.com/TooTallNate/Java-WebSocket/wiki/Drafts" > more about drafts + */ + public WebSocketServer( InetSocketAddress address , int decodercount , List<Draft> drafts , Collection<WebSocket> connectionscontainer ) { + if( address == null || decodercount < 1 || connectionscontainer == null ) { + throw new IllegalArgumentException( "address and connectionscontainer must not be null and you need at least 1 decoder" ); + } + + if( drafts == null ) + this.drafts = Collections.emptyList(); + else + this.drafts = drafts; + + this.address = address; + this.connections = connectionscontainer; + + iqueue = new LinkedList<WebSocketImpl>(); + + decoders = new ArrayList<WebSocketWorker>( decodercount ); + buffers = new LinkedBlockingQueue<ByteBuffer>(); + for( int i = 0 ; i < decodercount ; i++ ) { + WebSocketWorker ex = new WebSocketWorker(); + decoders.add( ex ); + ex.start(); + } + } + + /** + * Starts the server selectorthread that binds to the currently set port number and + * listeners for WebSocket connection requests. Creates a fixed thread pool with the size {@link WebSocketServer#DECODERS}<br> + * May only be called once. + * + * Alternatively you can call {@link WebSocketServer#run()} directly. + * + * @throws IllegalStateException + */ + public void start() { + if( selectorthread != null ) + throw new IllegalStateException( getClass().getName() + " can only be started once." ); + new Thread( this ).start();; + } + + /** + * Closes all connected clients sockets, then closes the underlying + * ServerSocketChannel, effectively killing the server socket selectorthread, + * freeing the port the server was bound to and stops all internal workerthreads. + * + * If this method is called before the server is started it will never start. + * + * @param timeout + * Specifies how many milliseconds the overall close handshaking may take altogether before the connections are closed without proper close handshaking.<br> + * + * @throws IOException + * When {@link ServerSocketChannel}.close throws an IOException + * @throws InterruptedException + */ + public void stop( int timeout ) throws InterruptedException { + if( !isclosed.compareAndSet( false, true ) ) { // this also makes sure that no further connections will be added to this.connections + return; + } + + List<WebSocket> socketsToClose = null; + + // copy the connections in a list (prevent callback deadlocks) + synchronized ( connections ) { + socketsToClose = new ArrayList<WebSocket>( connections ); + } + + for( WebSocket ws : socketsToClose ) { + ws.close( CloseFrame.GOING_AWAY ); + } + + synchronized ( this ) { + if( selectorthread != null ) { + if( Thread.currentThread() != selectorthread ) { + + } + if( selectorthread != Thread.currentThread() ) { + if( socketsToClose.size() > 0 ) + selectorthread.join( timeout );// isclosed will tell the selectorthread to go down after the last connection was closed + selectorthread.interrupt();// in case the selectorthread did not terminate in time we send the interrupt + selectorthread.join(); + } + } + } + } + public void stop() throws IOException , InterruptedException { + stop( 0 ); + } + + /** + * Returns a WebSocket[] of currently connected clients. + * Its iterators will be failfast and its not judicious + * to modify it. + * + * @return The currently connected clients. + */ + public Collection<WebSocket> connections() { + return this.connections; + } + + public InetSocketAddress getAddress() { + return this.address; + } + + /** + * Gets the port number that this server listens on. + * + * @return The port number. + */ + public int getPort() { + int port = getAddress().getPort(); + if( port == 0 && server != null ) { + port = server.socket().getLocalPort(); + } + return port; + } + + public List<Draft> getDraft() { + return Collections.unmodifiableList( drafts ); + } + + // Runnable IMPLEMENTATION ///////////////////////////////////////////////// + public void run() { + synchronized ( this ) { + if( selectorthread != null ) + throw new IllegalStateException( getClass().getName() + " can only be started once." ); + selectorthread = Thread.currentThread(); + if( isclosed.get() ) { + return; + } + } + selectorthread.setName( "WebsocketSelector" + selectorthread.getId() ); + try { + server = ServerSocketChannel.open(); + server.configureBlocking( false ); + ServerSocket socket = server.socket(); + socket.setReceiveBufferSize( WebSocketImpl.RCVBUF ); + socket.bind( address ); + selector = Selector.open(); + server.register( selector, server.validOps() ); + } catch ( IOException ex ) { + handleFatal( null, ex ); + return; + } + try { + while ( !selectorthread.isInterrupted() ) { + SelectionKey key = null; + WebSocketImpl conn = null; + try { + selector.select(); + Set<SelectionKey> keys = selector.selectedKeys(); + Iterator<SelectionKey> i = keys.iterator(); + + while ( i.hasNext() ) { + key = i.next(); + + if( !key.isValid() ) { + // Object o = key.attachment(); + continue; + } + + if( key.isAcceptable() ) { + if( !onConnect( key ) ) { + key.cancel(); + continue; + } + + SocketChannel channel = server.accept(); + channel.configureBlocking( false ); + WebSocketImpl w = wsf.createWebSocket( this, drafts, channel.socket() ); + w.key = channel.register( selector, SelectionKey.OP_READ, w ); + w.channel = wsf.wrapChannel( channel, w.key ); + i.remove(); + allocateBuffers( w ); + continue; + } + + if( key.isReadable() ) { + conn = (WebSocketImpl) key.attachment(); + ByteBuffer buf = takeBuffer(); + try { + if( SocketChannelIOHelper.read( buf, conn, conn.channel ) ) { + if( buf.hasRemaining() ) { + conn.inQueue.put( buf ); + queue( conn ); + i.remove(); + if( conn.channel instanceof WrappedByteChannel ) { + if( ( (WrappedByteChannel) conn.channel ).isNeedRead() ) { + iqueue.add( conn ); + } + } + } else + pushBuffer( buf ); + } else { + pushBuffer( buf ); + } + } catch ( IOException e ) { + pushBuffer( buf ); + throw e; + } + } + if( key.isWritable() ) { + conn = (WebSocketImpl) key.attachment(); + if( SocketChannelIOHelper.batch( conn, conn.channel ) ) { + if( key.isValid() ) + key.interestOps( SelectionKey.OP_READ ); + } + } + } + while ( !iqueue.isEmpty() ) { + conn = iqueue.remove( 0 ); + WrappedByteChannel c = ( (WrappedByteChannel) conn.channel ); + ByteBuffer buf = takeBuffer(); + try { + if( SocketChannelIOHelper.readMore( buf, conn, c ) ) + iqueue.add( conn ); + if( buf.hasRemaining() ) { + conn.inQueue.put( buf ); + queue( conn ); + } else { + pushBuffer( buf ); + } + } catch ( IOException e ) { + pushBuffer( buf ); + throw e; + } + + } + } catch ( CancelledKeyException e ) { + // an other thread may cancel the key + } catch ( ClosedByInterruptException e ) { + return; // do the same stuff as when InterruptedException is thrown + } catch ( IOException ex ) { + if( key != null ) + key.cancel(); + handleIOException( key, conn, ex ); + } catch ( InterruptedException e ) { + return;// FIXME controlled shutdown (e.g. take care of buffermanagement) + } + } + + } catch ( RuntimeException e ) { + // should hopefully never occur + handleFatal( null, e ); + } finally { + if( decoders != null ) { + for( WebSocketWorker w : decoders ) { + w.interrupt(); + } + } + if( selector != null ) { + try { + selector.close(); + } catch ( IOException e ) { + onError( null, e ); + } + } + if( server != null ) { + try { + server.close(); + } catch ( IOException e ) { + onError( null, e ); + } + } + } + } + protected void allocateBuffers( WebSocket c ) throws InterruptedException { + if( queuesize.get() >= 2 * decoders.size() + 1 ) { + return; + } + queuesize.incrementAndGet(); + buffers.put( createBuffer() ); + } + + protected void releaseBuffers( WebSocket c ) throws InterruptedException { + // queuesize.decrementAndGet(); + // takeBuffer(); + } + + public ByteBuffer createBuffer() { + return ByteBuffer.allocate( WebSocketImpl.RCVBUF ); + } + + private void queue( WebSocketImpl ws ) throws InterruptedException { + if( ws.workerThread == null ) { + ws.workerThread = decoders.get( queueinvokes % decoders.size() ); + queueinvokes++; + } + ws.workerThread.put( ws ); + } + + private ByteBuffer takeBuffer() throws InterruptedException { + return buffers.take(); + } + + private void pushBuffer( ByteBuffer buf ) throws InterruptedException { + if( buffers.size() > queuesize.intValue() ) + return; + buffers.put( buf ); + } + + private void handleIOException( SelectionKey key, WebSocket conn, IOException ex ) { + // onWebsocketError( conn, ex );// conn may be null here + if( conn != null ) { + conn.closeConnection( CloseFrame.ABNORMAL_CLOSE, ex.getMessage() ); + } else if( key != null ) { + SelectableChannel channel = key.channel(); + if( channel != null && channel.isOpen() ) { // this could be the case if the IOException ex is a SSLException + try { + channel.close(); + } catch ( IOException e ) { + // there is nothing that must be done here + } + if( WebSocketImpl.DEBUG ) + System.out.println( "Connection closed because of" + ex ); + } + } + } + + private void handleFatal( WebSocket conn, Exception e ) { + onError( conn, e ); + try { + stop(); + } catch ( IOException e1 ) { + onError( null, e1 ); + } catch ( InterruptedException e1 ) { + Thread.currentThread().interrupt(); + onError( null, e1 ); + } + } + + /** + * Gets the XML string that should be returned if a client requests a Flash + * security policy. + * + * The default implementation allows access from all remote domains, but + * only on the port that this WebSocketServer is listening on. + * + * This is specifically implemented for gitime's WebSocket client for Flash: + * http://github.com/gimite/web-socket-js + * + * @return An XML String that comforms to Flash's security policy. You MUST + * not include the null char at the end, it is appended automatically. + */ + protected String getFlashSecurityPolicy() { + return "<cross-domain-policy><allow-access-from domain=\"*\" to-ports=\"" + getPort() + "\" /></cross-domain-policy>"; + } + + @Override + public final void onWebsocketMessage( WebSocket conn, String message ) { + onMessage( conn, message ); + } + + @Override + @Deprecated + public/*final*/void onWebsocketMessageFragment( WebSocket conn, Framedata frame ) {// onFragment should be overloaded instead + onFragment( conn, frame ); + } + + @Override + public final void onWebsocketMessage( WebSocket conn, ByteBuffer blob ) { + onMessage( conn, blob ); + } + + @Override + public final void onWebsocketOpen( WebSocket conn, Handshakedata handshake ) { + if( addConnection( conn ) ) { + onOpen( conn, (ClientHandshake) handshake ); + } + } + + @Override + public final void onWebsocketClose( WebSocket conn, int code, String reason, boolean remote ) { + selector.wakeup(); + try { + if( removeConnection( conn ) ) { + onClose( conn, code, reason, remote ); + } + } finally { + try { + releaseBuffers( conn ); + } catch ( InterruptedException e ) { + Thread.currentThread().interrupt(); + } + } + + } + + /** + * This method performs remove operations on the connection and therefore also gives control over whether the operation shall be synchronized + * <p> + * {@link #WebSocketServer(InetSocketAddress, int, List, Collection)} allows to specify a collection which will be used to store current connections in.<br> + * Depending on the type on the connection, modifications of that collection may have to be synchronized. + **/ + protected boolean removeConnection( WebSocket ws ) { + boolean removed; + synchronized ( connections ) { + removed = this.connections.remove( ws ); + assert ( removed ); + } + if( isclosed.get() && connections.size() == 0 ) { + selectorthread.interrupt(); + } + return removed; + } + @Override + public ServerHandshakeBuilder onWebsocketHandshakeReceivedAsServer( WebSocket conn, Draft draft, ClientHandshake request ) throws InvalidDataException { + return super.onWebsocketHandshakeReceivedAsServer( conn, draft, request ); + } + + /** @see #removeConnection(WebSocket) */ + protected boolean addConnection( WebSocket ws ) { + if( !isclosed.get() ) { + synchronized ( connections ) { + boolean succ = this.connections.add( ws ); + assert ( succ ); + return succ; + } + } else { + // This case will happen when a new connection gets ready while the server is already stopping. + ws.close( CloseFrame.GOING_AWAY ); + return true;// for consistency sake we will make sure that both onOpen will be called + } + } + /** + * @param conn + * may be null if the error does not belong to a single connection + */ + @Override + public final void onWebsocketError( WebSocket conn, Exception ex ) { + onError( conn, ex ); + } + + @Override + public final void onWriteDemand( WebSocket w ) { + WebSocketImpl conn = (WebSocketImpl) w; + try { + conn.key.interestOps( SelectionKey.OP_READ | SelectionKey.OP_WRITE ); + } catch ( CancelledKeyException e ) { + // the thread which cancels key is responsible for possible cleanup + conn.outQueue.clear(); + } + selector.wakeup(); + } + + @Override + public void onWebsocketCloseInitiated( WebSocket conn, int code, String reason ) { + onCloseInitiated( conn, code, reason ); + } + + @Override + public void onWebsocketClosing( WebSocket conn, int code, String reason, boolean remote ) { + onClosing( conn, code, reason, remote ); + + } + + public void onCloseInitiated( WebSocket conn, int code, String reason ) { + } + + public void onClosing( WebSocket conn, int code, String reason, boolean remote ) { + + } + + public final void setWebSocketFactory( WebSocketServerFactory wsf ) { + this.wsf = wsf; + } + + public final WebSocketFactory getWebSocketFactory() { + return wsf; + } + + /** + * Returns whether a new connection shall be accepted or not.<br> + * Therefore method is well suited to implement some kind of connection limitation.<br> + * + * @see {@link #onOpen(WebSocket, ClientHandshake)}, {@link #onWebsocketHandshakeReceivedAsServer(WebSocket, Draft, ClientHandshake)} + **/ + protected boolean onConnect( SelectionKey key ) { + return true; + } + + private Socket getSocket( WebSocket conn ) { + WebSocketImpl impl = (WebSocketImpl) conn; + return ( (SocketChannel) impl.key.channel() ).socket(); + } + + @Override + public InetSocketAddress getLocalSocketAddress( WebSocket conn ) { + return (InetSocketAddress) getSocket( conn ).getLocalSocketAddress(); + } + + @Override + public InetSocketAddress getRemoteSocketAddress( WebSocket conn ) { + return (InetSocketAddress) getSocket( conn ).getRemoteSocketAddress(); + } + + /** Called after an opening handshake has been performed and the given websocket is ready to be written on. */ + public abstract void onOpen( WebSocket conn, ClientHandshake handshake ); + /** + * Called after the websocket connection has been closed. + * + * @param code + * The codes can be looked up here: {@link CloseFrame} + * @param reason + * Additional information string + * @param remote + * Returns whether or not the closing of the connection was initiated by the remote host. + **/ + public abstract void onClose( WebSocket conn, int code, String reason, boolean remote ); + /** + * Callback for string messages received from the remote host + * + * @see #onMessage(WebSocket, ByteBuffer) + **/ + public abstract void onMessage( WebSocket conn, String message ); + /** + * Called when errors occurs. If an error causes the websocket connection to fail {@link #onClose(WebSocket, int, String, boolean)} will be called additionally.<br> + * This method will be called primarily because of IO or protocol errors.<br> + * If the given exception is an RuntimeException that probably means that you encountered a bug.<br> + * + * @param con + * Can be null if there error does not belong to one specific websocket. For example if the servers port could not be bound. + **/ + public abstract void onError( WebSocket conn, Exception ex ); + /** + * Callback for binary messages received from the remote host + * + * @see #onMessage(WebSocket, String) + **/ + public void onMessage( WebSocket conn, ByteBuffer message ) { + } + + /** + * @see WebSocket#sendFragmentedFrame(org.java_websocket.framing.Framedata.Opcode, ByteBuffer, boolean) + */ + public void onFragment( WebSocket conn, Framedata fragment ) { + } + + public class WebSocketWorker extends Thread { + + private BlockingQueue<WebSocketImpl> iqueue; + + public WebSocketWorker() { + iqueue = new LinkedBlockingQueue<WebSocketImpl>(); + setName( "WebSocketWorker-" + getId() ); + setUncaughtExceptionHandler( new UncaughtExceptionHandler() { + @Override + public void uncaughtException( Thread t, Throwable e ) { + getDefaultUncaughtExceptionHandler().uncaughtException( t, e ); + } + } ); + } + + public void put( WebSocketImpl ws ) throws InterruptedException { + iqueue.put( ws ); + } + + @Override + public void run() { + WebSocketImpl ws = null; + try { + while ( true ) { + ByteBuffer buf = null; + ws = iqueue.take(); + buf = ws.inQueue.poll(); + assert ( buf != null ); + try { + ws.decode( buf ); + } finally { + pushBuffer( buf ); + } + } + } catch ( InterruptedException e ) { + } catch ( RuntimeException e ) { + handleFatal( ws, e ); + } + } + } + + public interface WebSocketServerFactory extends WebSocketFactory { + @Override + public WebSocketImpl createWebSocket( WebSocketAdapter a, Draft d, Socket s ); + + public WebSocketImpl createWebSocket( WebSocketAdapter a, List<Draft> drafts, Socket s ); + + /** + * Allows to wrap the Socketchannel( key.channel() ) to insert a protocol layer( like ssl or proxy authentication) beyond the ws layer. + * + * @param key + * a SelectionKey of an open SocketChannel. + * @return The channel on which the read and write operations will be performed.<br> + */ + public ByteChannel wrapChannel( SocketChannel channel, SelectionKey key ) throws IOException; + } +} diff --git a/cb-tools/java-source/java-source/src/main/java/org/java_websocket/util/Base64.java b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/util/Base64.java new file mode 100644 index 00000000..38d06ae2 --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/util/Base64.java @@ -0,0 +1,2065 @@ +package org.java_websocket.util; + +/** + * <p>Encodes and decodes to and from Base64 notation.</p> + * <p>Homepage: <a href="http://iharder.net/base64">http://iharder.net/base64</a>.</p> + * + * <p>Example:</p> + * + * <code>String encoded = Base64.encode( myByteArray );</code> + * <br /> + * <code>byte[] myByteArray = Base64.decode( encoded );</code> + * + * <p>The <tt>options</tt> parameter, which appears in a few places, is used to pass + * several pieces of information to the encoder. In the "higher level" methods such as + * encodeBytes( bytes, options ) the options parameter can be used to indicate such + * things as first gzipping the bytes before encoding them, not inserting linefeeds, + * and encoding using the URL-safe and Ordered dialects.</p> + * + * <p>Note, according to <a href="http://www.faqs.org/rfcs/rfc3548.html">RFC3548</a>, + * Section 2.1, implementations should not add line feeds unless explicitly told + * to do so. I've got Base64 set to this behavior now, although earlier versions + * broke lines by default.</p> + * + * <p>The constants defined in Base64 can be OR-ed together to combine options, so you + * might make a call like this:</p> + * + * <code>String encoded = Base64.encodeBytes( mybytes, Base64.GZIP | Base64.DO_BREAK_LINES );</code> + * <p>to compress the data before encoding it and then making the output have newline characters.</p> + * <p>Also...</p> + * <code>String encoded = Base64.encodeBytes( crazyString.getBytes() );</code> + * + * + * + * <p> + * Change Log: + * </p> + * <ul> + * <li>v2.3.7 - Fixed subtle bug when base 64 input stream contained the + * value 01111111, which is an invalid base 64 character but should not + * throw an ArrayIndexOutOfBoundsException either. Led to discovery of + * mishandling (or potential for better handling) of other bad input + * characters. You should now get an IOException if you try decoding + * something that has bad characters in it.</li> + * <li>v2.3.6 - Fixed bug when breaking lines and the final byte of the encoded + * string ended in the last column; the buffer was not properly shrunk and + * contained an extra (null) byte that made it into the string.</li> + * <li>v2.3.5 - Fixed bug in {@link #encodeFromFile} where estimated buffer size + * was wrong for files of size 31, 34, and 37 bytes.</li> + * <li>v2.3.4 - Fixed bug when working with gzipped streams whereby flushing + * the Base64.OutputStream closed the Base64 encoding (by padding with equals + * signs) too soon. Also added an option to suppress the automatic decoding + * of gzipped streams. Also added experimental support for specifying a + * class loader when using the + * {@link #decodeToObject(java.lang.String, int, java.lang.ClassLoader)} + * method.</li> + * <li>v2.3.3 - Changed default char encoding to US-ASCII which reduces the internal Java + * footprint with its CharEncoders and so forth. Fixed some javadocs that were + * inconsistent. Removed imports and specified things like java.io.IOException + * explicitly inline.</li> + * <li>v2.3.2 - Reduced memory footprint! Finally refined the "guessing" of how big the + * final encoded data will be so that the code doesn't have to create two output + * arrays: an oversized initial one and then a final, exact-sized one. Big win + * when using the {@link #encodeBytesToBytes(byte[])} family of methods (and not + * using the gzip options which uses a different mechanism with streams and stuff).</li> + * <li>v2.3.1 - Added {@link #encodeBytesToBytes(byte[], int, int, int)} and some + * similar helper methods to be more efficient with memory by not returning a + * String but just a byte array.</li> + * <li>v2.3 - <strong>This is not a drop-in replacement!</strong> This is two years of comments + * and bug fixes queued up and finally executed. Thanks to everyone who sent + * me stuff, and I'm sorry I wasn't able to distribute your fixes to everyone else. + * Much bad coding was cleaned up including throwing exceptions where necessary + * instead of returning null values or something similar. Here are some changes + * that may affect you: + * <ul> + * <li><em>Does not break lines, by default.</em> This is to keep in compliance with + * <a href="http://www.faqs.org/rfcs/rfc3548.html">RFC3548</a>.</li> + * <li><em>Throws exceptions instead of returning null values.</em> Because some operations + * (especially those that may permit the GZIP option) use IO streams, there + * is a possiblity of an java.io.IOException being thrown. After some discussion and + * thought, I've changed the behavior of the methods to throw java.io.IOExceptions + * rather than return null if ever there's an error. I think this is more + * appropriate, though it will require some changes to your code. Sorry, + * it should have been done this way to begin with.</li> + * <li><em>Removed all references to System.out, System.err, and the like.</em> + * Shame on me. All I can say is sorry they were ever there.</li> + * <li><em>Throws NullPointerExceptions and IllegalArgumentExceptions</em> as needed + * such as when passed arrays are null or offsets are invalid.</li> + * <li>Cleaned up as much javadoc as I could to avoid any javadoc warnings. + * This was especially annoying before for people who were thorough in their + * own projects and then had gobs of javadoc warnings on this file.</li> + * </ul> + * <li>v2.2.1 - Fixed bug using URL_SAFE and ORDERED encodings. Fixed bug + * when using very small files (~< 40 bytes).</li> + * <li>v2.2 - Added some helper methods for encoding/decoding directly from + * one file to the next. Also added a main() method to support command line + * encoding/decoding from one file to the next. Also added these Base64 dialects: + * <ol> + * <li>The default is RFC3548 format.</li> + * <li>Calling Base64.setFormat(Base64.BASE64_FORMAT.URLSAFE_FORMAT) generates + * URL and file name friendly format as described in Section 4 of RFC3548. + * http://www.faqs.org/rfcs/rfc3548.html</li> + * <li>Calling Base64.setFormat(Base64.BASE64_FORMAT.ORDERED_FORMAT) generates + * URL and file name friendly format that preserves lexical ordering as described + * in http://www.faqs.org/qa/rfcc-1940.html</li> + * </ol> + * Special thanks to Jim Kellerman at <a href="http://www.powerset.com/">http://www.powerset.com/</a> + * for contributing the new Base64 dialects. + * </li> + * + * <li>v2.1 - Cleaned up javadoc comments and unused variables and methods. Added + * some convenience methods for reading and writing to and from files.</li> + * <li>v2.0.2 - Now specifies UTF-8 encoding in places where the code fails on systems + * with other encodings (like EBCDIC).</li> + * <li>v2.0.1 - Fixed an error when decoding a single byte, that is, when the + * encoded data was a single byte.</li> + * <li>v2.0 - I got rid of methods that used booleans to set options. + * Now everything is more consolidated and cleaner. The code now detects + * when data that's being decoded is gzip-compressed and will decompress it + * automatically. Generally things are cleaner. You'll probably have to + * change some method calls that you were making to support the new + * options format (<tt>int</tt>s that you "OR" together).</li> + * <li>v1.5.1 - Fixed bug when decompressing and decoding to a + * byte[] using <tt>decode( String s, boolean gzipCompressed )</tt>. + * Added the ability to "suspend" encoding in the Output Stream so + * you can turn on and off the encoding if you need to embed base64 + * data in an otherwise "normal" stream (like an XML file).</li> + * <li>v1.5 - Output stream pases on flush() command but doesn't do anything itself. + * This helps when using GZIP streams. + * Added the ability to GZip-compress objects before encoding them.</li> + * <li>v1.4 - Added helper methods to read/write files.</li> + * <li>v1.3.6 - Fixed OutputStream.flush() so that 'position' is reset.</li> + * <li>v1.3.5 - Added flag to turn on and off line breaks. Fixed bug in input stream + * where last buffer being read, if not completely full, was not returned.</li> + * <li>v1.3.4 - Fixed when "improperly padded stream" error was thrown at the wrong time.</li> + * <li>v1.3.3 - Fixed I/O streams which were totally messed up.</li> + * </ul> + * + * <p> + * I am placing this code in the Public Domain. Do with it as you will. + * This software comes with no guarantees or warranties but with + * plenty of well-wishing instead! + * Please visit <a href="http://iharder.net/base64">http://iharder.net/base64</a> + * periodically to check for updates or to contribute improvements. + * </p> + * + * @author Robert Harder + * @author rob@iharder.net + * @version 2.3.7 + */ +public class Base64 +{ + +/* ******** P U B L I C F I E L D S ******** */ + + + /** No options specified. Value is zero. */ + public final static int NO_OPTIONS = 0; + + /** Specify encoding in first bit. Value is one. */ + public final static int ENCODE = 1; + + + /** Specify decoding in first bit. Value is zero. */ + public final static int DECODE = 0; + + + /** Specify that data should be gzip-compressed in second bit. Value is two. */ + public final static int GZIP = 2; + + /** Specify that gzipped data should <em>not</em> be automatically gunzipped. */ + public final static int DONT_GUNZIP = 4; + + + /** Do break lines when encoding. Value is 8. */ + public final static int DO_BREAK_LINES = 8; + + /** + * Encode using Base64-like encoding that is URL- and Filename-safe as described + * in Section 4 of RFC3548: + * <a href="http://www.faqs.org/rfcs/rfc3548.html">http://www.faqs.org/rfcs/rfc3548.html</a>. + * It is important to note that data encoded this way is <em>not</em> officially valid Base64, + * or at the very least should not be called Base64 without also specifying that is + * was encoded using the URL- and Filename-safe dialect. + */ + public final static int URL_SAFE = 16; + + + /** + * Encode using the special "ordered" dialect of Base64 described here: + * <a href="http://www.faqs.org/qa/rfcc-1940.html">http://www.faqs.org/qa/rfcc-1940.html</a>. + */ + public final static int ORDERED = 32; + + +/* ******** P R I V A T E F I E L D S ******** */ + + + /** Maximum line length (76) of Base64 output. */ + private final static int MAX_LINE_LENGTH = 76; + + + /** The equals sign (=) as a byte. */ + private final static byte EQUALS_SIGN = (byte)'='; + + + /** The new line character (\n) as a byte. */ + private final static byte NEW_LINE = (byte)'\n'; + + + /** Preferred encoding. */ + private final static String PREFERRED_ENCODING = "US-ASCII"; + + + private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding + private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding + + +/* ******** S T A N D A R D B A S E 6 4 A L P H A B E T ******** */ + + /** The 64 valid Base64 values. */ + /* Host platform me be something funny like EBCDIC, so we hardcode these values. */ + private final static byte[] _STANDARD_ALPHABET = { + (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', + (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', + (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', + (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', + (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', + (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', + (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', + (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', + (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', + (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'+', (byte)'/' + }; + + + /** + * Translates a Base64 value to either its 6-bit reconstruction value + * or a negative number indicating some other meaning. + **/ + private final static byte[] _STANDARD_DECODABET = { + -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 + -5,-5, // Whitespace: Tab and Linefeed + -9,-9, // Decimal 11 - 12 + -5, // Whitespace: Carriage Return + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 + -9,-9,-9,-9,-9, // Decimal 27 - 31 + -5, // Whitespace: Space + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 + 62, // Plus sign at decimal 43 + -9,-9,-9, // Decimal 44 - 46 + 63, // Slash at decimal 47 + 52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine + -9,-9,-9, // Decimal 58 - 60 + -1, // Equals sign at decimal 61 + -9,-9,-9, // Decimal 62 - 64 + 0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N' + 14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z' + -9,-9,-9,-9,-9,-9, // Decimal 91 - 96 + 26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm' + 39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z' + -9,-9,-9,-9,-9 // Decimal 123 - 127 + ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 + }; + + +/* ******** U R L S A F E B A S E 6 4 A L P H A B E T ******** */ + + /** + * Used in the URL- and Filename-safe dialect described in Section 4 of RFC3548: + * <a href="http://www.faqs.org/rfcs/rfc3548.html">http://www.faqs.org/rfcs/rfc3548.html</a>. + * Notice that the last two bytes become "hyphen" and "underscore" instead of "plus" and "slash." + */ + private final static byte[] _URL_SAFE_ALPHABET = { + (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', + (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', + (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', + (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', + (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', + (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', + (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', + (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', + (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', + (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'-', (byte)'_' + }; + + /** + * Used in decoding URL- and Filename-safe dialects of Base64. + */ + private final static byte[] _URL_SAFE_DECODABET = { + -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 + -5,-5, // Whitespace: Tab and Linefeed + -9,-9, // Decimal 11 - 12 + -5, // Whitespace: Carriage Return + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 + -9,-9,-9,-9,-9, // Decimal 27 - 31 + -5, // Whitespace: Space + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 + -9, // Plus sign at decimal 43 + -9, // Decimal 44 + 62, // Minus sign at decimal 45 + -9, // Decimal 46 + -9, // Slash at decimal 47 + 52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine + -9,-9,-9, // Decimal 58 - 60 + -1, // Equals sign at decimal 61 + -9,-9,-9, // Decimal 62 - 64 + 0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N' + 14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z' + -9,-9,-9,-9, // Decimal 91 - 94 + 63, // Underscore at decimal 95 + -9, // Decimal 96 + 26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm' + 39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z' + -9,-9,-9,-9,-9 // Decimal 123 - 127 + ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 + }; + + + +/* ******** O R D E R E D B A S E 6 4 A L P H A B E T ******** */ + + /** + * I don't get the point of this technique, but someone requested it, + * and it is described here: + * <a href="http://www.faqs.org/qa/rfcc-1940.html">http://www.faqs.org/qa/rfcc-1940.html</a>. + */ + private final static byte[] _ORDERED_ALPHABET = { + (byte)'-', + (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', + (byte)'5', (byte)'6', (byte)'7', (byte)'8', (byte)'9', + (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', + (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', + (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', + (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', + (byte)'_', + (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', + (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', + (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', + (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z' + }; + + /** + * Used in decoding the "ordered" dialect of Base64. + */ + private final static byte[] _ORDERED_DECODABET = { + -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 + -5,-5, // Whitespace: Tab and Linefeed + -9,-9, // Decimal 11 - 12 + -5, // Whitespace: Carriage Return + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 + -9,-9,-9,-9,-9, // Decimal 27 - 31 + -5, // Whitespace: Space + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 + -9, // Plus sign at decimal 43 + -9, // Decimal 44 + 0, // Minus sign at decimal 45 + -9, // Decimal 46 + -9, // Slash at decimal 47 + 1,2,3,4,5,6,7,8,9,10, // Numbers zero through nine + -9,-9,-9, // Decimal 58 - 60 + -1, // Equals sign at decimal 61 + -9,-9,-9, // Decimal 62 - 64 + 11,12,13,14,15,16,17,18,19,20,21,22,23, // Letters 'A' through 'M' + 24,25,26,27,28,29,30,31,32,33,34,35,36, // Letters 'N' through 'Z' + -9,-9,-9,-9, // Decimal 91 - 94 + 37, // Underscore at decimal 95 + -9, // Decimal 96 + 38,39,40,41,42,43,44,45,46,47,48,49,50, // Letters 'a' through 'm' + 51,52,53,54,55,56,57,58,59,60,61,62,63, // Letters 'n' through 'z' + -9,-9,-9,-9,-9 // Decimal 123 - 127 + ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 + }; + + +/* ******** D E T E R M I N E W H I C H A L H A B E T ******** */ + + + /** + * Returns one of the _SOMETHING_ALPHABET byte arrays depending on + * the options specified. + * It's possible, though silly, to specify ORDERED <b>and</b> URLSAFE + * in which case one of them will be picked, though there is + * no guarantee as to which one will be picked. + */ + private final static byte[] getAlphabet( int options ) { + if ((options & URL_SAFE) == URL_SAFE) { + return _URL_SAFE_ALPHABET; + } else if ((options & ORDERED) == ORDERED) { + return _ORDERED_ALPHABET; + } else { + return _STANDARD_ALPHABET; + } + } // end getAlphabet + + + /** + * Returns one of the _SOMETHING_DECODABET byte arrays depending on + * the options specified. + * It's possible, though silly, to specify ORDERED and URL_SAFE + * in which case one of them will be picked, though there is + * no guarantee as to which one will be picked. + */ + private final static byte[] getDecodabet( int options ) { + if( (options & URL_SAFE) == URL_SAFE) { + return _URL_SAFE_DECODABET; + } else if ((options & ORDERED) == ORDERED) { + return _ORDERED_DECODABET; + } else { + return _STANDARD_DECODABET; + } + } // end getAlphabet + + + + /** Defeats instantiation. */ + private Base64(){} + + + + +/* ******** E N C O D I N G M E T H O D S ******** */ + + + /** + * Encodes up to the first three bytes of array <var>threeBytes</var> + * and returns a four-byte array in Base64 notation. + * The actual number of significant bytes in your array is + * given by <var>numSigBytes</var>. + * The array <var>threeBytes</var> needs only be as big as + * <var>numSigBytes</var>. + * Code can reuse a byte array by passing a four-byte array as <var>b4</var>. + * + * @param b4 A reusable byte array to reduce array instantiation + * @param threeBytes the array to convert + * @param numSigBytes the number of significant bytes in your array + * @return four byte array in Base64 notation. + * @since 1.5.1 + */ + private static byte[] encode3to4( byte[] b4, byte[] threeBytes, int numSigBytes, int options ) { + encode3to4( threeBytes, 0, numSigBytes, b4, 0, options ); + return b4; + } // end encode3to4 + + + /** + * <p>Encodes up to three bytes of the array <var>source</var> + * and writes the resulting four Base64 bytes to <var>destination</var>. + * The source and destination arrays can be manipulated + * anywhere along their length by specifying + * <var>srcOffset</var> and <var>destOffset</var>. + * This method does not check to make sure your arrays + * are large enough to accomodate <var>srcOffset</var> + 3 for + * the <var>source</var> array or <var>destOffset</var> + 4 for + * the <var>destination</var> array. + * The actual number of significant bytes in your array is + * given by <var>numSigBytes</var>.</p> + * <p>This is the lowest level of the encoding methods with + * all possible parameters.</p> + * + * @param source the array to convert + * @param srcOffset the index where conversion begins + * @param numSigBytes the number of significant bytes in your array + * @param destination the array to hold the conversion + * @param destOffset the index where output will be put + * @return the <var>destination</var> array + * @since 1.3 + */ + private static byte[] encode3to4( + byte[] source, int srcOffset, int numSigBytes, + byte[] destination, int destOffset, int options ) { + + byte[] ALPHABET = getAlphabet( options ); + + // 1 2 3 + // 01234567890123456789012345678901 Bit position + // --------000000001111111122222222 Array position from threeBytes + // --------| || || || | Six bit groups to index ALPHABET + // >>18 >>12 >> 6 >> 0 Right shift necessary + // 0x3f 0x3f 0x3f Additional AND + + // Create buffer with zero-padding if there are only one or two + // significant bytes passed in the array. + // We have to shift left 24 in order to flush out the 1's that appear + // when Java treats a value as negative that is cast from a byte to an int. + int inBuff = ( numSigBytes > 0 ? ((source[ srcOffset ] << 24) >>> 8) : 0 ) + | ( numSigBytes > 1 ? ((source[ srcOffset + 1 ] << 24) >>> 16) : 0 ) + | ( numSigBytes > 2 ? ((source[ srcOffset + 2 ] << 24) >>> 24) : 0 ); + + switch( numSigBytes ) + { + case 3: + destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; + destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; + destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ]; + destination[ destOffset + 3 ] = ALPHABET[ (inBuff ) & 0x3f ]; + return destination; + + case 2: + destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; + destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; + destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ]; + destination[ destOffset + 3 ] = EQUALS_SIGN; + return destination; + + case 1: + destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; + destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; + destination[ destOffset + 2 ] = EQUALS_SIGN; + destination[ destOffset + 3 ] = EQUALS_SIGN; + return destination; + + default: + return destination; + } // end switch + } // end encode3to4 + + + + /** + * Performs Base64 encoding on the <code>raw</code> ByteBuffer, + * writing it to the <code>encoded</code> ByteBuffer. + * This is an experimental feature. Currently it does not + * pass along any options (such as {@link #DO_BREAK_LINES} + * or {@link #GZIP}. + * + * @param raw input buffer + * @param encoded output buffer + * @since 2.3 + */ + public static void encode( java.nio.ByteBuffer raw, java.nio.ByteBuffer encoded ){ + byte[] raw3 = new byte[3]; + byte[] enc4 = new byte[4]; + + while( raw.hasRemaining() ){ + int rem = Math.min(3,raw.remaining()); + raw.get(raw3,0,rem); + Base64.encode3to4(enc4, raw3, rem, Base64.NO_OPTIONS ); + encoded.put(enc4); + } // end input remaining + } + + + /** + * Performs Base64 encoding on the <code>raw</code> ByteBuffer, + * writing it to the <code>encoded</code> CharBuffer. + * This is an experimental feature. Currently it does not + * pass along any options (such as {@link #DO_BREAK_LINES} + * or {@link #GZIP}. + * + * @param raw input buffer + * @param encoded output buffer + * @since 2.3 + */ + public static void encode( java.nio.ByteBuffer raw, java.nio.CharBuffer encoded ){ + byte[] raw3 = new byte[3]; + byte[] enc4 = new byte[4]; + + while( raw.hasRemaining() ){ + int rem = Math.min(3,raw.remaining()); + raw.get(raw3,0,rem); + Base64.encode3to4(enc4, raw3, rem, Base64.NO_OPTIONS ); + for( int i = 0; i < 4; i++ ){ + encoded.put( (char)(enc4[i] & 0xFF) ); + } + } // end input remaining + } + + + + + /** + * Serializes an object and returns the Base64-encoded + * version of that serialized object. + * + * <p>As of v 2.3, if the object + * cannot be serialized or there is another error, + * the method will throw an java.io.IOException. <b>This is new to v2.3!</b> + * In earlier versions, it just returned a null value, but + * in retrospect that's a pretty poor way to handle it.</p> + * + * The object is not GZip-compressed before being encoded. + * + * @param serializableObject The object to encode + * @return The Base64-encoded object + * @throws java.io.IOException if there is an error + * @throws NullPointerException if serializedObject is null + * @since 1.4 + */ + public static String encodeObject( java.io.Serializable serializableObject ) + throws java.io.IOException { + return encodeObject( serializableObject, NO_OPTIONS ); + } // end encodeObject + + + + /** + * Serializes an object and returns the Base64-encoded + * version of that serialized object. + * + * <p>As of v 2.3, if the object + * cannot be serialized or there is another error, + * the method will throw an java.io.IOException. <b>This is new to v2.3!</b> + * In earlier versions, it just returned a null value, but + * in retrospect that's a pretty poor way to handle it.</p> + * + * The object is not GZip-compressed before being encoded. + * <p> + * Example options:<pre> + * GZIP: gzip-compresses object before encoding it. + * DO_BREAK_LINES: break lines at 76 characters + * </pre> + * <p> + * Example: <code>encodeObject( myObj, Base64.GZIP )</code> or + * <p> + * Example: <code>encodeObject( myObj, Base64.GZIP | Base64.DO_BREAK_LINES )</code> + * + * @param serializableObject The object to encode + * @param options Specified options + * @return The Base64-encoded object + * @see Base64#GZIP + * @see Base64#DO_BREAK_LINES + * @throws java.io.IOException if there is an error + * @since 2.0 + */ + public static String encodeObject( java.io.Serializable serializableObject, int options ) + throws java.io.IOException { + + if( serializableObject == null ){ + throw new NullPointerException( "Cannot serialize a null object." ); + } // end if: null + + // Streams + java.io.ByteArrayOutputStream baos = null; + java.io.OutputStream b64os = null; + java.util.zip.GZIPOutputStream gzos = null; + java.io.ObjectOutputStream oos = null; + + + try { + // ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream + baos = new java.io.ByteArrayOutputStream(); + b64os = new Base64.OutputStream( baos, ENCODE | options ); + if( (options & GZIP) != 0 ){ + // Gzip + gzos = new java.util.zip.GZIPOutputStream(b64os); + oos = new java.io.ObjectOutputStream( gzos ); + } else { + // Not gzipped + oos = new java.io.ObjectOutputStream( b64os ); + } + oos.writeObject( serializableObject ); + } // end try + catch( java.io.IOException e ) { + // Catch it and then throw it immediately so that + // the finally{} block is called for cleanup. + throw e; + } // end catch + finally { + try{ oos.close(); } catch( Exception e ){} + try{ gzos.close(); } catch( Exception e ){} + try{ b64os.close(); } catch( Exception e ){} + try{ baos.close(); } catch( Exception e ){} + } // end finally + + // Return value according to relevant encoding. + try { + return new String( baos.toByteArray(), PREFERRED_ENCODING ); + } // end try + catch (java.io.UnsupportedEncodingException uue){ + // Fall back to some Java default + return new String( baos.toByteArray() ); + } // end catch + + } // end encode + + + + /** + * Encodes a byte array into Base64 notation. + * Does not GZip-compress data. + * + * @param source The data to convert + * @return The data in Base64-encoded form + * @throws NullPointerException if source array is null + * @since 1.4 + */ + public static String encodeBytes( byte[] source ) { + // Since we're not going to have the GZIP encoding turned on, + // we're not going to have an java.io.IOException thrown, so + // we should not force the user to have to catch it. + String encoded = null; + try { + encoded = encodeBytes(source, 0, source.length, NO_OPTIONS); + } catch (java.io.IOException ex) { + assert false : ex.getMessage(); + } // end catch + assert encoded != null; + return encoded; + } // end encodeBytes + + + + /** + * Encodes a byte array into Base64 notation. + * <p> + * Example options:<pre> + * GZIP: gzip-compresses object before encoding it. + * DO_BREAK_LINES: break lines at 76 characters + * <i>Note: Technically, this makes your encoding non-compliant.</i> + * </pre> + * <p> + * Example: <code>encodeBytes( myData, Base64.GZIP )</code> or + * <p> + * Example: <code>encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES )</code> + * + * + * <p>As of v 2.3, if there is an error with the GZIP stream, + * the method will throw an java.io.IOException. <b>This is new to v2.3!</b> + * In earlier versions, it just returned a null value, but + * in retrospect that's a pretty poor way to handle it.</p> + * + * + * @param source The data to convert + * @param options Specified options + * @return The Base64-encoded data as a String + * @see Base64#GZIP + * @see Base64#DO_BREAK_LINES + * @throws java.io.IOException if there is an error + * @throws NullPointerException if source array is null + * @since 2.0 + */ + public static String encodeBytes( byte[] source, int options ) throws java.io.IOException { + return encodeBytes( source, 0, source.length, options ); + } // end encodeBytes + + + /** + * Encodes a byte array into Base64 notation. + * Does not GZip-compress data. + * + * <p>As of v 2.3, if there is an error, + * the method will throw an java.io.IOException. <b>This is new to v2.3!</b> + * In earlier versions, it just returned a null value, but + * in retrospect that's a pretty poor way to handle it.</p> + * + * + * @param source The data to convert + * @param off Offset in array where conversion should begin + * @param len Length of data to convert + * @return The Base64-encoded data as a String + * @throws NullPointerException if source array is null + * @throws IllegalArgumentException if source array, offset, or length are invalid + * @since 1.4 + */ + public static String encodeBytes( byte[] source, int off, int len ) { + // Since we're not going to have the GZIP encoding turned on, + // we're not going to have an java.io.IOException thrown, so + // we should not force the user to have to catch it. + String encoded = null; + try { + encoded = encodeBytes( source, off, len, NO_OPTIONS ); + } catch (java.io.IOException ex) { + assert false : ex.getMessage(); + } // end catch + assert encoded != null; + return encoded; + } // end encodeBytes + + + + /** + * Encodes a byte array into Base64 notation. + * <p> + * Example options:<pre> + * GZIP: gzip-compresses object before encoding it. + * DO_BREAK_LINES: break lines at 76 characters + * <i>Note: Technically, this makes your encoding non-compliant.</i> + * </pre> + * <p> + * Example: <code>encodeBytes( myData, Base64.GZIP )</code> or + * <p> + * Example: <code>encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES )</code> + * + * + * <p>As of v 2.3, if there is an error with the GZIP stream, + * the method will throw an java.io.IOException. <b>This is new to v2.3!</b> + * In earlier versions, it just returned a null value, but + * in retrospect that's a pretty poor way to handle it.</p> + * + * + * @param source The data to convert + * @param off Offset in array where conversion should begin + * @param len Length of data to convert + * @param options Specified options + * @return The Base64-encoded data as a String + * @see Base64#GZIP + * @see Base64#DO_BREAK_LINES + * @throws java.io.IOException if there is an error + * @throws NullPointerException if source array is null + * @throws IllegalArgumentException if source array, offset, or length are invalid + * @since 2.0 + */ + public static String encodeBytes( byte[] source, int off, int len, int options ) throws java.io.IOException { + byte[] encoded = encodeBytesToBytes( source, off, len, options ); + + // Return value according to relevant encoding. + try { + return new String( encoded, PREFERRED_ENCODING ); + } // end try + catch (java.io.UnsupportedEncodingException uue) { + return new String( encoded ); + } // end catch + + } // end encodeBytes + + + + + /** + * Similar to {@link #encodeBytes(byte[])} but returns + * a byte array instead of instantiating a String. This is more efficient + * if you're working with I/O streams and have large data sets to encode. + * + * + * @param source The data to convert + * @return The Base64-encoded data as a byte[] (of ASCII characters) + * @throws NullPointerException if source array is null + * @since 2.3.1 + */ + public static byte[] encodeBytesToBytes( byte[] source ) { + byte[] encoded = null; + try { + encoded = encodeBytesToBytes( source, 0, source.length, Base64.NO_OPTIONS ); + } catch( java.io.IOException ex ) { + assert false : "IOExceptions only come from GZipping, which is turned off: " + ex.getMessage(); + } + return encoded; + } + + + /** + * Similar to {@link #encodeBytes(byte[], int, int, int)} but returns + * a byte array instead of instantiating a String. This is more efficient + * if you're working with I/O streams and have large data sets to encode. + * + * + * @param source The data to convert + * @param off Offset in array where conversion should begin + * @param len Length of data to convert + * @param options Specified options + * @return The Base64-encoded data as a String + * @see Base64#GZIP + * @see Base64#DO_BREAK_LINES + * @throws java.io.IOException if there is an error + * @throws NullPointerException if source array is null + * @throws IllegalArgumentException if source array, offset, or length are invalid + * @since 2.3.1 + */ + public static byte[] encodeBytesToBytes( byte[] source, int off, int len, int options ) throws java.io.IOException { + + if( source == null ){ + throw new NullPointerException( "Cannot serialize a null array." ); + } // end if: null + + if( off < 0 ){ + throw new IllegalArgumentException( "Cannot have negative offset: " + off ); + } // end if: off < 0 + + if( len < 0 ){ + throw new IllegalArgumentException( "Cannot have length offset: " + len ); + } // end if: len < 0 + + if( off + len > source.length ){ + throw new IllegalArgumentException( + String.format( "Cannot have offset of %d and length of %d with array of length %d", off,len,source.length)); + } // end if: off < 0 + + + + // Compress? + if( (options & GZIP) != 0 ) { + java.io.ByteArrayOutputStream baos = null; + java.util.zip.GZIPOutputStream gzos = null; + Base64.OutputStream b64os = null; + + try { + // GZip -> Base64 -> ByteArray + baos = new java.io.ByteArrayOutputStream(); + b64os = new Base64.OutputStream( baos, ENCODE | options ); + gzos = new java.util.zip.GZIPOutputStream( b64os ); + + gzos.write( source, off, len ); + gzos.close(); + } // end try + catch( java.io.IOException e ) { + // Catch it and then throw it immediately so that + // the finally{} block is called for cleanup. + throw e; + } // end catch + finally { + try{ gzos.close(); } catch( Exception e ){} + try{ b64os.close(); } catch( Exception e ){} + try{ baos.close(); } catch( Exception e ){} + } // end finally + + return baos.toByteArray(); + } // end if: compress + + // Else, don't compress. Better not to use streams at all then. + else { + boolean breakLines = (options & DO_BREAK_LINES) != 0; + + //int len43 = len * 4 / 3; + //byte[] outBuff = new byte[ ( len43 ) // Main 4:3 + // + ( (len % 3) > 0 ? 4 : 0 ) // Account for padding + // + (breakLines ? ( len43 / MAX_LINE_LENGTH ) : 0) ]; // New lines + // Try to determine more precisely how big the array needs to be. + // If we get it right, we don't have to do an array copy, and + // we save a bunch of memory. + int encLen = ( len / 3 ) * 4 + ( len % 3 > 0 ? 4 : 0 ); // Bytes needed for actual encoding + if( breakLines ){ + encLen += encLen / MAX_LINE_LENGTH; // Plus extra newline characters + } + byte[] outBuff = new byte[ encLen ]; + + + int d = 0; + int e = 0; + int len2 = len - 2; + int lineLength = 0; + for( ; d < len2; d+=3, e+=4 ) { + encode3to4( source, d+off, 3, outBuff, e, options ); + + lineLength += 4; + if( breakLines && lineLength >= MAX_LINE_LENGTH ) + { + outBuff[e+4] = NEW_LINE; + e++; + lineLength = 0; + } // end if: end of line + } // en dfor: each piece of array + + if( d < len ) { + encode3to4( source, d+off, len - d, outBuff, e, options ); + e += 4; + } // end if: some padding needed + + + // Only resize array if we didn't guess it right. + if( e <= outBuff.length - 1 ){ + // If breaking lines and the last byte falls right at + // the line length (76 bytes per line), there will be + // one extra byte, and the array will need to be resized. + // Not too bad of an estimate on array size, I'd say. + byte[] finalOut = new byte[e]; + System.arraycopy(outBuff,0, finalOut,0,e); + //System.err.println("Having to resize array from " + outBuff.length + " to " + e ); + return finalOut; + } else { + //System.err.println("No need to resize array."); + return outBuff; + } + + } // end else: don't compress + + } // end encodeBytesToBytes + + + + + +/* ******** D E C O D I N G M E T H O D S ******** */ + + + /** + * Decodes four bytes from array <var>source</var> + * and writes the resulting bytes (up to three of them) + * to <var>destination</var>. + * The source and destination arrays can be manipulated + * anywhere along their length by specifying + * <var>srcOffset</var> and <var>destOffset</var>. + * This method does not check to make sure your arrays + * are large enough to accomodate <var>srcOffset</var> + 4 for + * the <var>source</var> array or <var>destOffset</var> + 3 for + * the <var>destination</var> array. + * This method returns the actual number of bytes that + * were converted from the Base64 encoding. + * <p>This is the lowest level of the decoding methods with + * all possible parameters.</p> + * + * + * @param source the array to convert + * @param srcOffset the index where conversion begins + * @param destination the array to hold the conversion + * @param destOffset the index where output will be put + * @param options alphabet type is pulled from this (standard, url-safe, ordered) + * @return the number of decoded bytes converted + * @throws NullPointerException if source or destination arrays are null + * @throws IllegalArgumentException if srcOffset or destOffset are invalid + * or there is not enough room in the array. + * @since 1.3 + */ + private static int decode4to3( + byte[] source, int srcOffset, + byte[] destination, int destOffset, int options ) { + + // Lots of error checking and exception throwing + if( source == null ){ + throw new NullPointerException( "Source array was null." ); + } // end if + if( destination == null ){ + throw new NullPointerException( "Destination array was null." ); + } // end if + if( srcOffset < 0 || srcOffset + 3 >= source.length ){ + throw new IllegalArgumentException( String.format( + "Source array with length %d cannot have offset of %d and still process four bytes.", source.length, srcOffset ) ); + } // end if + if( destOffset < 0 || destOffset +2 >= destination.length ){ + throw new IllegalArgumentException( String.format( + "Destination array with length %d cannot have offset of %d and still store three bytes.", destination.length, destOffset ) ); + } // end if + + + byte[] DECODABET = getDecodabet( options ); + + // Example: Dk== + if( source[ srcOffset + 2] == EQUALS_SIGN ) { + // Two ways to do the same thing. Don't know which way I like best. + //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) + // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 ); + int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) + | ( ( DECODABET[ source[ srcOffset + 1] ] & 0xFF ) << 12 ); + + destination[ destOffset ] = (byte)( outBuff >>> 16 ); + return 1; + } + + // Example: DkL= + else if( source[ srcOffset + 3 ] == EQUALS_SIGN ) { + // Two ways to do the same thing. Don't know which way I like best. + //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) + // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) + // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ); + int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) + | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 ) + | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6 ); + + destination[ destOffset ] = (byte)( outBuff >>> 16 ); + destination[ destOffset + 1 ] = (byte)( outBuff >>> 8 ); + return 2; + } + + // Example: DkLE + else { + // Two ways to do the same thing. Don't know which way I like best. + //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) + // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) + // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ) + // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 ); + int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) + | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 ) + | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6) + | ( ( DECODABET[ source[ srcOffset + 3 ] ] & 0xFF ) ); + + + destination[ destOffset ] = (byte)( outBuff >> 16 ); + destination[ destOffset + 1 ] = (byte)( outBuff >> 8 ); + destination[ destOffset + 2 ] = (byte)( outBuff ); + + return 3; + } + } // end decodeToBytes + + + + + + /** + * Low-level access to decoding ASCII characters in + * the form of a byte array. <strong>Ignores GUNZIP option, if + * it's set.</strong> This is not generally a recommended method, + * although it is used internally as part of the decoding process. + * Special case: if len = 0, an empty array is returned. Still, + * if you need more speed and reduced memory footprint (and aren't + * gzipping), consider this method. + * + * @param source The Base64 encoded data + * @return decoded data + * @since 2.3.1 + */ + public static byte[] decode( byte[] source ) + throws java.io.IOException { + byte[] decoded = null; +// try { + decoded = decode( source, 0, source.length, Base64.NO_OPTIONS ); +// } catch( java.io.IOException ex ) { +// assert false : "IOExceptions only come from GZipping, which is turned off: " + ex.getMessage(); +// } + return decoded; + } + + + + /** + * Low-level access to decoding ASCII characters in + * the form of a byte array. <strong>Ignores GUNZIP option, if + * it's set.</strong> This is not generally a recommended method, + * although it is used internally as part of the decoding process. + * Special case: if len = 0, an empty array is returned. Still, + * if you need more speed and reduced memory footprint (and aren't + * gzipping), consider this method. + * + * @param source The Base64 encoded data + * @param off The offset of where to begin decoding + * @param len The length of characters to decode + * @param options Can specify options such as alphabet type to use + * @return decoded data + * @throws java.io.IOException If bogus characters exist in source data + * @since 1.3 + */ + public static byte[] decode( byte[] source, int off, int len, int options ) + throws java.io.IOException { + + // Lots of error checking and exception throwing + if( source == null ){ + throw new NullPointerException( "Cannot decode null source array." ); + } // end if + if( off < 0 || off + len > source.length ){ + throw new IllegalArgumentException( String.format( + "Source array with length %d cannot have offset of %d and process %d bytes.", source.length, off, len ) ); + } // end if + + if( len == 0 ){ + return new byte[0]; + }else if( len < 4 ){ + throw new IllegalArgumentException( + "Base64-encoded string must have at least four characters, but length specified was " + len ); + } // end if + + byte[] DECODABET = getDecodabet( options ); + + int len34 = len * 3 / 4; // Estimate on array size + byte[] outBuff = new byte[ len34 ]; // Upper limit on size of output + int outBuffPosn = 0; // Keep track of where we're writing + + byte[] b4 = new byte[4]; // Four byte buffer from source, eliminating white space + int b4Posn = 0; // Keep track of four byte input buffer + int i = 0; // Source array counter + byte sbiDecode = 0; // Special value from DECODABET + + for( i = off; i < off+len; i++ ) { // Loop through source + + sbiDecode = DECODABET[ source[i]&0xFF ]; + + // White space, Equals sign, or legit Base64 character + // Note the values such as -5 and -9 in the + // DECODABETs at the top of the file. + if( sbiDecode >= WHITE_SPACE_ENC ) { + if( sbiDecode >= EQUALS_SIGN_ENC ) { + b4[ b4Posn++ ] = source[i]; // Save non-whitespace + if( b4Posn > 3 ) { // Time to decode? + outBuffPosn += decode4to3( b4, 0, outBuff, outBuffPosn, options ); + b4Posn = 0; + + // If that was the equals sign, break out of 'for' loop + if( source[i] == EQUALS_SIGN ) { + break; + } // end if: equals sign + } // end if: quartet built + } // end if: equals sign or better + } // end if: white space, equals sign or better + else { + // There's a bad input character in the Base64 stream. + throw new java.io.IOException( String.format( + "Bad Base64 input character decimal %d in array position %d", ((int)source[i])&0xFF, i ) ); + } // end else: + } // each input character + + byte[] out = new byte[ outBuffPosn ]; + System.arraycopy( outBuff, 0, out, 0, outBuffPosn ); + return out; + } // end decode + + + + + /** + * Decodes data from Base64 notation, automatically + * detecting gzip-compressed data and decompressing it. + * + * @param s the string to decode + * @return the decoded data + * @throws java.io.IOException If there is a problem + * @since 1.4 + */ + public static byte[] decode( String s ) throws java.io.IOException { + return decode( s, NO_OPTIONS ); + } + + + + /** + * Decodes data from Base64 notation, automatically + * detecting gzip-compressed data and decompressing it. + * + * @param s the string to decode + * @param options encode options such as URL_SAFE + * @return the decoded data + * @throws java.io.IOException if there is an error + * @throws NullPointerException if <tt>s</tt> is null + * @since 1.4 + */ + public static byte[] decode( String s, int options ) throws java.io.IOException { + + if( s == null ){ + throw new NullPointerException( "Input string was null." ); + } // end if + + byte[] bytes; + try { + bytes = s.getBytes( PREFERRED_ENCODING ); + } // end try + catch( java.io.UnsupportedEncodingException uee ) { + bytes = s.getBytes(); + } // end catch + //</change> + + // Decode + bytes = decode( bytes, 0, bytes.length, options ); + + // Check to see if it's gzip-compressed + // GZIP Magic Two-Byte Number: 0x8b1f (35615) + boolean dontGunzip = (options & DONT_GUNZIP) != 0; + if( (bytes != null) && (bytes.length >= 4) && (!dontGunzip) ) { + + int head = ((int)bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00); + if( java.util.zip.GZIPInputStream.GZIP_MAGIC == head ) { + java.io.ByteArrayInputStream bais = null; + java.util.zip.GZIPInputStream gzis = null; + java.io.ByteArrayOutputStream baos = null; + byte[] buffer = new byte[2048]; + int length = 0; + + try { + baos = new java.io.ByteArrayOutputStream(); + bais = new java.io.ByteArrayInputStream( bytes ); + gzis = new java.util.zip.GZIPInputStream( bais ); + + while( ( length = gzis.read( buffer ) ) >= 0 ) { + baos.write(buffer,0,length); + } // end while: reading input + + // No error? Get new bytes. + bytes = baos.toByteArray(); + + } // end try + catch( java.io.IOException e ) { + e.printStackTrace(); + // Just return originally-decoded bytes + } // end catch + finally { + try{ baos.close(); } catch( Exception e ){} + try{ gzis.close(); } catch( Exception e ){} + try{ bais.close(); } catch( Exception e ){} + } // end finally + + } // end if: gzipped + } // end if: bytes.length >= 2 + + return bytes; + } // end decode + + + + /** + * Attempts to decode Base64 data and deserialize a Java + * Object within. Returns <tt>null</tt> if there was an error. + * + * @param encodedObject The Base64 data to decode + * @return The decoded and deserialized object + * @throws NullPointerException if encodedObject is null + * @throws java.io.IOException if there is a general error + * @throws ClassNotFoundException if the decoded object is of a + * class that cannot be found by the JVM + * @since 1.5 + */ + public static Object decodeToObject( String encodedObject ) + throws java.io.IOException, java.lang.ClassNotFoundException { + return decodeToObject(encodedObject,NO_OPTIONS,null); + } + + + /** + * Attempts to decode Base64 data and deserialize a Java + * Object within. Returns <tt>null</tt> if there was an error. + * If <tt>loader</tt> is not null, it will be the class loader + * used when deserializing. + * + * @param encodedObject The Base64 data to decode + * @param options Various parameters related to decoding + * @param loader Optional class loader to use in deserializing classes. + * @return The decoded and deserialized object + * @throws NullPointerException if encodedObject is null + * @throws java.io.IOException if there is a general error + * @throws ClassNotFoundException if the decoded object is of a + * class that cannot be found by the JVM + * @since 2.3.4 + */ + public static Object decodeToObject( + String encodedObject, int options, final ClassLoader loader ) + throws java.io.IOException, java.lang.ClassNotFoundException { + + // Decode and gunzip if necessary + byte[] objBytes = decode( encodedObject, options ); + + java.io.ByteArrayInputStream bais = null; + java.io.ObjectInputStream ois = null; + Object obj = null; + + try { + bais = new java.io.ByteArrayInputStream( objBytes ); + + // If no custom class loader is provided, use Java's builtin OIS. + if( loader == null ){ + ois = new java.io.ObjectInputStream( bais ); + } // end if: no loader provided + + // Else make a customized object input stream that uses + // the provided class loader. + else { + ois = new java.io.ObjectInputStream(bais){ + @Override + public Class<?> resolveClass(java.io.ObjectStreamClass streamClass) + throws java.io.IOException, ClassNotFoundException { + Class<?> c = Class.forName(streamClass.getName(), false, loader); + if( c == null ){ + return super.resolveClass(streamClass); + } else { + return c; // Class loader knows of this class. + } // end else: not null + } // end resolveClass + }; // end ois + } // end else: no custom class loader + + obj = ois.readObject(); + } // end try + catch( java.io.IOException e ) { + throw e; // Catch and throw in order to execute finally{} + } // end catch + catch( java.lang.ClassNotFoundException e ) { + throw e; // Catch and throw in order to execute finally{} + } // end catch + finally { + try{ bais.close(); } catch( Exception e ){} + try{ ois.close(); } catch( Exception e ){} + } // end finally + + return obj; + } // end decodeObject + + + + /** + * Convenience method for encoding data to a file. + * + * <p>As of v 2.3, if there is a error, + * the method will throw an java.io.IOException. <b>This is new to v2.3!</b> + * In earlier versions, it just returned false, but + * in retrospect that's a pretty poor way to handle it.</p> + * + * @param dataToEncode byte array of data to encode in base64 form + * @param filename Filename for saving encoded data + * @throws java.io.IOException if there is an error + * @throws NullPointerException if dataToEncode is null + * @since 2.1 + */ + public static void encodeToFile( byte[] dataToEncode, String filename ) + throws java.io.IOException { + + if( dataToEncode == null ){ + throw new NullPointerException( "Data to encode was null." ); + } // end iff + + Base64.OutputStream bos = null; + try { + bos = new Base64.OutputStream( + new java.io.FileOutputStream( filename ), Base64.ENCODE ); + bos.write( dataToEncode ); + } // end try + catch( java.io.IOException e ) { + throw e; // Catch and throw to execute finally{} block + } // end catch: java.io.IOException + finally { + try{ bos.close(); } catch( Exception e ){} + } // end finally + + } // end encodeToFile + + + /** + * Convenience method for decoding data to a file. + * + * <p>As of v 2.3, if there is a error, + * the method will throw an java.io.IOException. <b>This is new to v2.3!</b> + * In earlier versions, it just returned false, but + * in retrospect that's a pretty poor way to handle it.</p> + * + * @param dataToDecode Base64-encoded data as a string + * @param filename Filename for saving decoded data + * @throws java.io.IOException if there is an error + * @since 2.1 + */ + public static void decodeToFile( String dataToDecode, String filename ) + throws java.io.IOException { + + Base64.OutputStream bos = null; + try{ + bos = new Base64.OutputStream( + new java.io.FileOutputStream( filename ), Base64.DECODE ); + bos.write( dataToDecode.getBytes( PREFERRED_ENCODING ) ); + } // end try + catch( java.io.IOException e ) { + throw e; // Catch and throw to execute finally{} block + } // end catch: java.io.IOException + finally { + try{ bos.close(); } catch( Exception e ){} + } // end finally + + } // end decodeToFile + + + + + /** + * Convenience method for reading a base64-encoded + * file and decoding it. + * + * <p>As of v 2.3, if there is a error, + * the method will throw an java.io.IOException. <b>This is new to v2.3!</b> + * In earlier versions, it just returned false, but + * in retrospect that's a pretty poor way to handle it.</p> + * + * @param filename Filename for reading encoded data + * @return decoded byte array + * @throws java.io.IOException if there is an error + * @since 2.1 + */ + public static byte[] decodeFromFile( String filename ) + throws java.io.IOException { + + byte[] decodedData = null; + Base64.InputStream bis = null; + try + { + // Set up some useful variables + java.io.File file = new java.io.File( filename ); + byte[] buffer = null; + int length = 0; + int numBytes = 0; + + // Check for size of file + if( file.length() > Integer.MAX_VALUE ) + { + throw new java.io.IOException( "File is too big for this convenience method (" + file.length() + " bytes)." ); + } // end if: file too big for int index + buffer = new byte[ (int)file.length() ]; + + // Open a stream + bis = new Base64.InputStream( + new java.io.BufferedInputStream( + new java.io.FileInputStream( file ) ), Base64.DECODE ); + + // Read until done + while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 ) { + length += numBytes; + } // end while + + // Save in a variable to return + decodedData = new byte[ length ]; + System.arraycopy( buffer, 0, decodedData, 0, length ); + + } // end try + catch( java.io.IOException e ) { + throw e; // Catch and release to execute finally{} + } // end catch: java.io.IOException + finally { + try{ bis.close(); } catch( Exception e) {} + } // end finally + + return decodedData; + } // end decodeFromFile + + + + /** + * Convenience method for reading a binary file + * and base64-encoding it. + * + * <p>As of v 2.3, if there is a error, + * the method will throw an java.io.IOException. <b>This is new to v2.3!</b> + * In earlier versions, it just returned false, but + * in retrospect that's a pretty poor way to handle it.</p> + * + * @param filename Filename for reading binary data + * @return base64-encoded string + * @throws java.io.IOException if there is an error + * @since 2.1 + */ + public static String encodeFromFile( String filename ) + throws java.io.IOException { + + String encodedData = null; + Base64.InputStream bis = null; + try + { + // Set up some useful variables + java.io.File file = new java.io.File( filename ); + byte[] buffer = new byte[ Math.max((int)(file.length() * 1.4+1),40) ]; // Need max() for math on small files (v2.2.1); Need +1 for a few corner cases (v2.3.5) + int length = 0; + int numBytes = 0; + + // Open a stream + bis = new Base64.InputStream( + new java.io.BufferedInputStream( + new java.io.FileInputStream( file ) ), Base64.ENCODE ); + + // Read until done + while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 ) { + length += numBytes; + } // end while + + // Save in a variable to return + encodedData = new String( buffer, 0, length, Base64.PREFERRED_ENCODING ); + + } // end try + catch( java.io.IOException e ) { + throw e; // Catch and release to execute finally{} + } // end catch: java.io.IOException + finally { + try{ bis.close(); } catch( Exception e) {} + } // end finally + + return encodedData; + } // end encodeFromFile + + /** + * Reads <tt>infile</tt> and encodes it to <tt>outfile</tt>. + * + * @param infile Input file + * @param outfile Output file + * @throws java.io.IOException if there is an error + * @since 2.2 + */ + public static void encodeFileToFile( String infile, String outfile ) + throws java.io.IOException { + + String encoded = Base64.encodeFromFile( infile ); + java.io.OutputStream out = null; + try{ + out = new java.io.BufferedOutputStream( + new java.io.FileOutputStream( outfile ) ); + out.write( encoded.getBytes("US-ASCII") ); // Strict, 7-bit output. + } // end try + catch( java.io.IOException e ) { + throw e; // Catch and release to execute finally{} + } // end catch + finally { + try { out.close(); } + catch( Exception ex ){} + } // end finally + } // end encodeFileToFile + + + /** + * Reads <tt>infile</tt> and decodes it to <tt>outfile</tt>. + * + * @param infile Input file + * @param outfile Output file + * @throws java.io.IOException if there is an error + * @since 2.2 + */ + public static void decodeFileToFile( String infile, String outfile ) + throws java.io.IOException { + + byte[] decoded = Base64.decodeFromFile( infile ); + java.io.OutputStream out = null; + try{ + out = new java.io.BufferedOutputStream( + new java.io.FileOutputStream( outfile ) ); + out.write( decoded ); + } // end try + catch( java.io.IOException e ) { + throw e; // Catch and release to execute finally{} + } // end catch + finally { + try { out.close(); } + catch( Exception ex ){} + } // end finally + } // end decodeFileToFile + + + /* ******** I N N E R C L A S S I N P U T S T R E A M ******** */ + + + + /** + * A {@link Base64.InputStream} will read data from another + * <tt>java.io.InputStream</tt>, given in the constructor, + * and encode/decode to/from Base64 notation on the fly. + * + * @see Base64 + * @since 1.3 + */ + public static class InputStream extends java.io.FilterInputStream { + + private boolean encode; // Encoding or decoding + private int position; // Current position in the buffer + private byte[] buffer; // Small buffer holding converted data + private int bufferLength; // Length of buffer (3 or 4) + private int numSigBytes; // Number of meaningful bytes in the buffer + private int lineLength; + private boolean breakLines; // Break lines at less than 80 characters + private int options; // Record options used to create the stream. + private byte[] decodabet; // Local copies to avoid extra method calls + + + /** + * Constructs a {@link Base64.InputStream} in DECODE mode. + * + * @param in the <tt>java.io.InputStream</tt> from which to read data. + * @since 1.3 + */ + public InputStream( java.io.InputStream in ) { + this( in, DECODE ); + } // end constructor + + + /** + * Constructs a {@link Base64.InputStream} in + * either ENCODE or DECODE mode. + * <p> + * Valid options:<pre> + * ENCODE or DECODE: Encode or Decode as data is read. + * DO_BREAK_LINES: break lines at 76 characters + * (only meaningful when encoding)</i> + * </pre> + * <p> + * Example: <code>new Base64.InputStream( in, Base64.DECODE )</code> + * + * + * @param in the <tt>java.io.InputStream</tt> from which to read data. + * @param options Specified options + * @see Base64#ENCODE + * @see Base64#DECODE + * @see Base64#DO_BREAK_LINES + * @since 2.0 + */ + public InputStream( java.io.InputStream in, int options ) { + + super( in ); + this.options = options; // Record for later + this.breakLines = (options & DO_BREAK_LINES) > 0; + this.encode = (options & ENCODE) > 0; + this.bufferLength = encode ? 4 : 3; + this.buffer = new byte[ bufferLength ]; + this.position = -1; + this.lineLength = 0; + this.decodabet = getDecodabet(options); + } // end constructor + + /** + * Reads enough of the input stream to convert + * to/from Base64 and returns the next byte. + * + * @return next byte + * @since 1.3 + */ + @Override + public int read() throws java.io.IOException { + + // Do we need to get data? + if( position < 0 ) { + if( encode ) { + byte[] b3 = new byte[3]; + int numBinaryBytes = 0; + for( int i = 0; i < 3; i++ ) { + int b = in.read(); + + // If end of stream, b is -1. + if( b >= 0 ) { + b3[i] = (byte)b; + numBinaryBytes++; + } else { + break; // out of for loop + } // end else: end of stream + + } // end for: each needed input byte + + if( numBinaryBytes > 0 ) { + encode3to4( b3, 0, numBinaryBytes, buffer, 0, options ); + position = 0; + numSigBytes = 4; + } // end if: got data + else { + return -1; // Must be end of stream + } // end else + } // end if: encoding + + // Else decoding + else { + byte[] b4 = new byte[4]; + int i = 0; + for( i = 0; i < 4; i++ ) { + // Read four "meaningful" bytes: + int b = 0; + do{ b = in.read(); } + while( b >= 0 && decodabet[ b & 0x7f ] <= WHITE_SPACE_ENC ); + + if( b < 0 ) { + break; // Reads a -1 if end of stream + } // end if: end of stream + + b4[i] = (byte)b; + } // end for: each needed input byte + + if( i == 4 ) { + numSigBytes = decode4to3( b4, 0, buffer, 0, options ); + position = 0; + } // end if: got four characters + else if( i == 0 ){ + return -1; + } // end else if: also padded correctly + else { + // Must have broken out from above. + throw new java.io.IOException( "Improperly padded Base64 input." ); + } // end + + } // end else: decode + } // end else: get data + + // Got data? + if( position >= 0 ) { + // End of relevant data? + if( /*!encode &&*/ position >= numSigBytes ){ + return -1; + } // end if: got data + + if( encode && breakLines && lineLength >= MAX_LINE_LENGTH ) { + lineLength = 0; + return '\n'; + } // end if + else { + lineLength++; // This isn't important when decoding + // but throwing an extra "if" seems + // just as wasteful. + + int b = buffer[ position++ ]; + + if( position >= bufferLength ) { + position = -1; + } // end if: end + + return b & 0xFF; // This is how you "cast" a byte that's + // intended to be unsigned. + } // end else + } // end if: position >= 0 + + // Else error + else { + throw new java.io.IOException( "Error in Base64 code reading stream." ); + } // end else + } // end read + + + /** + * Calls {@link #read()} repeatedly until the end of stream + * is reached or <var>len</var> bytes are read. + * Returns number of bytes read into array or -1 if + * end of stream is encountered. + * + * @param dest array to hold values + * @param off offset for array + * @param len max number of bytes to read into array + * @return bytes read into array or -1 if end of stream is encountered. + * @since 1.3 + */ + @Override + public int read( byte[] dest, int off, int len ) + throws java.io.IOException { + int i; + int b; + for( i = 0; i < len; i++ ) { + b = read(); + + if( b >= 0 ) { + dest[off + i] = (byte) b; + } + else if( i == 0 ) { + return -1; + } + else { + break; // Out of 'for' loop + } // Out of 'for' loop + } // end for: each byte read + return i; + } // end read + + } // end inner class InputStream + + + + + + + /* ******** I N N E R C L A S S O U T P U T S T R E A M ******** */ + + + + /** + * A {@link Base64.OutputStream} will write data to another + * <tt>java.io.OutputStream</tt>, given in the constructor, + * and encode/decode to/from Base64 notation on the fly. + * + * @see Base64 + * @since 1.3 + */ + public static class OutputStream extends java.io.FilterOutputStream { + + private boolean encode; + private int position; + private byte[] buffer; + private int bufferLength; + private int lineLength; + private boolean breakLines; + private byte[] b4; // Scratch used in a few places + private boolean suspendEncoding; + private int options; // Record for later + private byte[] decodabet; // Local copies to avoid extra method calls + + /** + * Constructs a {@link Base64.OutputStream} in ENCODE mode. + * + * @param out the <tt>java.io.OutputStream</tt> to which data will be written. + * @since 1.3 + */ + public OutputStream( java.io.OutputStream out ) { + this( out, ENCODE ); + } // end constructor + + + /** + * Constructs a {@link Base64.OutputStream} in + * either ENCODE or DECODE mode. + * <p> + * Valid options:<pre> + * ENCODE or DECODE: Encode or Decode as data is read. + * DO_BREAK_LINES: don't break lines at 76 characters + * (only meaningful when encoding)</i> + * </pre> + * <p> + * Example: <code>new Base64.OutputStream( out, Base64.ENCODE )</code> + * + * @param out the <tt>java.io.OutputStream</tt> to which data will be written. + * @param options Specified options. + * @see Base64#ENCODE + * @see Base64#DECODE + * @see Base64#DO_BREAK_LINES + * @since 1.3 + */ + public OutputStream( java.io.OutputStream out, int options ) { + super( out ); + this.breakLines = (options & DO_BREAK_LINES) != 0; + this.encode = (options & ENCODE) != 0; + this.bufferLength = encode ? 3 : 4; + this.buffer = new byte[ bufferLength ]; + this.position = 0; + this.lineLength = 0; + this.suspendEncoding = false; + this.b4 = new byte[4]; + this.options = options; + this.decodabet = getDecodabet(options); + } // end constructor + + + /** + * Writes the byte to the output stream after + * converting to/from Base64 notation. + * When encoding, bytes are buffered three + * at a time before the output stream actually + * gets a write() call. + * When decoding, bytes are buffered four + * at a time. + * + * @param theByte the byte to write + * @since 1.3 + */ + @Override + public void write(int theByte) + throws java.io.IOException { + // Encoding suspended? + if( suspendEncoding ) { + this.out.write( theByte ); + return; + } // end if: supsended + + // Encode? + if( encode ) { + buffer[ position++ ] = (byte)theByte; + if( position >= bufferLength ) { // Enough to encode. + + this.out.write( encode3to4( b4, buffer, bufferLength, options ) ); + + lineLength += 4; + if( breakLines && lineLength >= MAX_LINE_LENGTH ) { + this.out.write( NEW_LINE ); + lineLength = 0; + } // end if: end of line + + position = 0; + } // end if: enough to output + } // end if: encoding + + // Else, Decoding + else { + // Meaningful Base64 character? + if( decodabet[ theByte & 0x7f ] > WHITE_SPACE_ENC ) { + buffer[ position++ ] = (byte)theByte; + if( position >= bufferLength ) { // Enough to output. + + int len = Base64.decode4to3( buffer, 0, b4, 0, options ); + out.write( b4, 0, len ); + position = 0; + } // end if: enough to output + } // end if: meaningful base64 character + else if( decodabet[ theByte & 0x7f ] != WHITE_SPACE_ENC ) { + throw new java.io.IOException( "Invalid character in Base64 data." ); + } // end else: not white space either + } // end else: decoding + } // end write + + + + /** + * Calls {@link #write(int)} repeatedly until <var>len</var> + * bytes are written. + * + * @param theBytes array from which to read bytes + * @param off offset for array + * @param len max number of bytes to read into array + * @since 1.3 + */ + @Override + public void write( byte[] theBytes, int off, int len ) + throws java.io.IOException { + // Encoding suspended? + if( suspendEncoding ) { + this.out.write( theBytes, off, len ); + return; + } // end if: supsended + + for( int i = 0; i < len; i++ ) { + write( theBytes[ off + i ] ); + } // end for: each byte written + + } // end write + + + + /** + * Method added by PHIL. [Thanks, PHIL. -Rob] + * This pads the buffer without closing the stream. + * @throws java.io.IOException if there's an error. + */ + public void flushBase64() throws java.io.IOException { + if( position > 0 ) { + if( encode ) { + out.write( encode3to4( b4, buffer, position, options ) ); + position = 0; + } // end if: encoding + else { + throw new java.io.IOException( "Base64 input not properly padded." ); + } // end else: decoding + } // end if: buffer partially full + + } // end flush + + + /** + * Flushes and closes (I think, in the superclass) the stream. + * + * @since 1.3 + */ + @Override + public void close() throws java.io.IOException { + // 1. Ensure that pending characters are written + flushBase64(); + + // 2. Actually close the stream + // Base class both flushes and closes. + super.close(); + + buffer = null; + out = null; + } // end close + + + + /** + * Suspends encoding of the stream. + * May be helpful if you need to embed a piece of + * base64-encoded data in a stream. + * + * @throws java.io.IOException if there's an error flushing + * @since 1.5.1 + */ + public void suspendEncoding() throws java.io.IOException { + flushBase64(); + this.suspendEncoding = true; + } // end suspendEncoding + + + /** + * Resumes encoding of the stream. + * May be helpful if you need to embed a piece of + * base64-encoded data in a stream. + * + * @since 1.5.1 + */ + public void resumeEncoding() { + this.suspendEncoding = false; + } // end resumeEncoding + + + + } // end inner class OutputStream + + +} // end class Base64 diff --git a/cb-tools/java-source/java-source/src/main/java/org/java_websocket/util/Charsetfunctions.java b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/util/Charsetfunctions.java new file mode 100644 index 00000000..bd8ad299 --- /dev/null +++ b/cb-tools/java-source/java-source/src/main/java/org/java_websocket/util/Charsetfunctions.java @@ -0,0 +1,90 @@ +package org.java_websocket.util; + +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CodingErrorAction; + +import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.framing.CloseFrame; + +public class Charsetfunctions { + + public static CodingErrorAction codingErrorAction = CodingErrorAction.REPORT; + + /* + * @return UTF-8 encoding in bytes + */ + public static byte[] utf8Bytes( String s ) { + try { + return s.getBytes( "UTF8" ); + } catch ( UnsupportedEncodingException e ) { + throw new RuntimeException( e ); + } + } + + /* + * @return ASCII encoding in bytes + */ + public static byte[] asciiBytes( String s ) { + try { + return s.getBytes( "ASCII" ); + } catch ( UnsupportedEncodingException e ) { + throw new RuntimeException( e ); + } + } + + public static String stringAscii( byte[] bytes ) { + return stringAscii( bytes, 0, bytes.length ); + } + + public static String stringAscii( byte[] bytes, int offset, int length ){ + try { + return new String( bytes, offset, length, "ASCII" ); + } catch ( UnsupportedEncodingException e ) { + throw new RuntimeException( e ); + } + } + + public static String stringUtf8( byte[] bytes ) throws InvalidDataException { + return stringUtf8( ByteBuffer.wrap( bytes ) ); + } + + /*public static String stringUtf8( byte[] bytes, int off, int length ) throws InvalidDataException { + CharsetDecoder decode = Charset.forName( "UTF8" ).newDecoder(); + decode.onMalformedInput( codingErrorAction ); + decode.onUnmappableCharacter( codingErrorAction ); + //decode.replaceWith( "X" ); + String s; + try { + s = decode.decode( ByteBuffer.wrap( bytes, off, length ) ).toString(); + } catch ( CharacterCodingException e ) { + throw new InvalidDataException( CloseFrame.NO_UTF8, e ); + } + return s; + }*/ + + public static String stringUtf8( ByteBuffer bytes ) throws InvalidDataException { + CharsetDecoder decode = Charset.forName( "UTF8" ).newDecoder(); + decode.onMalformedInput( codingErrorAction ); + decode.onUnmappableCharacter( codingErrorAction ); + // decode.replaceWith( "X" ); + String s; + try { + bytes.mark(); + s = decode.decode( bytes ).toString(); + bytes.reset(); + } catch ( CharacterCodingException e ) { + throw new InvalidDataException( CloseFrame.NO_UTF8, e ); + } + return s; + } + + public static void main( String[] args ) throws InvalidDataException { + stringUtf8( utf8Bytes( "\0" ) ); + stringAscii( asciiBytes( "\0" ) ); + } + +} diff --git a/cb-tools/java-source/java-source/src/test/java/AutobahnClientTest.java b/cb-tools/java-source/java-source/src/test/java/AutobahnClientTest.java new file mode 100644 index 00000000..a567fd1b --- /dev/null +++ b/cb-tools/java-source/java-source/src/test/java/AutobahnClientTest.java @@ -0,0 +1,163 @@ +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URI; +import java.nio.ByteBuffer; + +import org.java_websocket.WebSocket; +import org.java_websocket.WebSocketImpl; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.drafts.Draft; +import org.java_websocket.drafts.Draft_17; +import org.java_websocket.framing.FrameBuilder; +import org.java_websocket.framing.Framedata; +import org.java_websocket.handshake.ServerHandshake; + +public class AutobahnClientTest extends WebSocketClient { + + public AutobahnClientTest( Draft d , URI uri ) { + super( uri, d ); + } + /** + * @param args + */ + public static void main( String[] args ) { + System.out.println( "Testutility to profile/test this implementation using the Autobahn suit.\n" ); + System.out.println( "Type 'r <casenumber>' to run a testcase. Example: r 1" ); + System.out.println( "Type 'r <first casenumber> <last casenumber>' to run a testcase. Example: r 1 295" ); + System.out.println( "Type 'u' to update the test results." ); + System.out.println( "Type 'ex' to terminate the program." ); + System.out.println( "During sequences of cases the debugoutput will be turned of." ); + + System.out.println( "You can now enter in your commands:" ); + + try { + BufferedReader sysin = new BufferedReader( new InputStreamReader( System.in ) ); + + /*First of the thinks a programmer might want to change*/ + Draft d = new Draft_17(); + String clientname = "tootallnate/websocket"; + + String protocol = "ws"; + String host = "localhost"; + int port = 9001; + + String serverlocation = protocol + "://" + host + ":" + port; + String line = ""; + AutobahnClientTest e; + URI uri = null; + String perviousline = ""; + String nextline = null; + Integer start = null; + Integer end = null; + + while ( !line.contains( "ex" ) ) { + try { + if( nextline != null ) { + line = nextline; + nextline = null; + WebSocketImpl.DEBUG = false; + } else { + System.out.print( ">" ); + line = sysin.readLine(); + WebSocketImpl.DEBUG = true; + } + if( line.equals( "l" ) ) { + line = perviousline; + } + String[] spl = line.split( " " ); + if( line.startsWith( "r" ) ) { + if( spl.length == 3 ) { + start = new Integer( spl[ 1 ] ); + end = new Integer( spl[ 2 ] ); + } + if( start != null && end != null ) { + if( start > end ) { + start = null; + end = null; + } else { + nextline = "r " + start; + start++; + if( spl.length == 3 ) + continue; + } + } + uri = URI.create( serverlocation + "/runCase?case=" + spl[ 1 ] + "&agent=" + clientname ); + + } else if( line.startsWith( "u" ) ) { + WebSocketImpl.DEBUG = false; + uri = URI.create( serverlocation + "/updateReports?agent=" + clientname ); + } else if( line.startsWith( "d" ) ) { + try { + d = (Draft) Class.forName( "Draft_" + spl[ 1 ] ).getConstructor().newInstance(); + } catch ( Exception ex ) { + System.out.println( "Could not change draft" + ex ); + } + } + if( uri == null ) { + System.out.println( "Do not understand the input." ); + continue; + } + System.out.println( "//////////////////////Exec: " + uri.getQuery() ); + e = new AutobahnClientTest( d, uri ); + Thread t = new Thread( e ); + t.start(); + try { + t.join(); + + } catch ( InterruptedException e1 ) { + e1.printStackTrace(); + } finally { + e.close(); + } + } catch ( ArrayIndexOutOfBoundsException e1 ) { + System.out.println( "Bad Input r 1, u 1, d 10, ex" ); + } catch ( IllegalArgumentException e2 ) { + e2.printStackTrace(); + } + + } + } catch ( ArrayIndexOutOfBoundsException e ) { + System.out.println( "Missing server uri" ); + } catch ( IllegalArgumentException e ) { + e.printStackTrace(); + System.out.println( "URI should look like ws://localhost:8887 or wss://echo.websocket.org" ); + } catch ( IOException e ) { + e.printStackTrace(); // for System.in reader + } + System.exit( 0 ); + } + + @Override + public void onMessage( String message ) { + send( message ); + } + + @Override + public void onMessage( ByteBuffer blob ) { + getConnection().send( blob ); + } + + @Override + public void onError( Exception ex ) { + System.out.println( "Error: " ); + ex.printStackTrace(); + } + + @Override + public void onOpen( ServerHandshake handshake ) { + } + + @Override + public void onClose( int code, String reason, boolean remote ) { + System.out.println( "Closed: " + code + " " + reason ); + } + + @Override + public void onWebsocketMessageFragment( WebSocket conn, Framedata frame ) { + FrameBuilder builder = (FrameBuilder) frame; + builder.setTransferemasked( true ); + getConnection().sendFrame( frame ); + } + +} diff --git a/cb-tools/java-source/java-source/src/test/java/AutobahnServerTest.java b/cb-tools/java-source/java-source/src/test/java/AutobahnServerTest.java new file mode 100644 index 00000000..ed7d05c4 --- /dev/null +++ b/cb-tools/java-source/java-source/src/test/java/AutobahnServerTest.java @@ -0,0 +1,72 @@ +import java.net.InetSocketAddress; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.util.Collections; + +import org.java_websocket.WebSocket; +import org.java_websocket.WebSocketImpl; +import org.java_websocket.drafts.Draft; +import org.java_websocket.drafts.Draft_17; +import org.java_websocket.framing.FrameBuilder; +import org.java_websocket.framing.Framedata; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.server.WebSocketServer; + +public class AutobahnServerTest extends WebSocketServer { + private static int counter = 0; + + public AutobahnServerTest( int port , Draft d ) throws UnknownHostException { + super( new InetSocketAddress( port ), Collections.singletonList( d ) ); + } + + public AutobahnServerTest( InetSocketAddress address, Draft d ) { + super( address, Collections.singletonList( d ) ); + } + + @Override + public void onOpen( WebSocket conn, ClientHandshake handshake ) { + counter++; + System.out.println( "///////////Opened connection number" + counter ); + } + + @Override + public void onClose( WebSocket conn, int code, String reason, boolean remote ) { + System.out.println( "closed" ); + } + + @Override + public void onError( WebSocket conn, Exception ex ) { + System.out.println( "Error:" ); + ex.printStackTrace(); + } + + @Override + public void onMessage( WebSocket conn, String message ) { + conn.send( message ); + } + + @Override + public void onMessage( WebSocket conn, ByteBuffer blob ) { + conn.send( blob ); + } + + @Override + public void onWebsocketMessageFragment( WebSocket conn, Framedata frame ) { + FrameBuilder builder = (FrameBuilder) frame; + builder.setTransferemasked( false ); + conn.sendFrame( frame ); + } + + public static void main( String[] args ) throws UnknownHostException { + WebSocketImpl.DEBUG = false; + int port; + try { + port = new Integer( args[ 0 ] ); + } catch ( Exception e ) { + System.out.println( "No port specified. Defaulting to 9003" ); + port = 9003; + } + new AutobahnServerTest( port, new Draft_17() ).start(); + } + +} diff --git a/cb-tools/java-source/pom.xml b/cb-tools/java-source/pom.xml new file mode 100644 index 00000000..9189fba8 --- /dev/null +++ b/cb-tools/java-source/pom.xml @@ -0,0 +1,119 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <parent> + <groupId>org.sonatype.oss</groupId> + <artifactId>oss-parent</artifactId> + <version>7</version> + </parent> + <scm> + <connection>scm:git:git@github.com:TooTallNate/Java-WebSocket.git</connection> + <developerConnection>scm:git:git@github.com:TooTallNate/Java-WebSocket.git</developerConnection> + <url>git@github.com:TooTallNate/Java-WebSocket.git</url> + </scm> + <modelVersion>4.0.0</modelVersion> + <groupId>org.java-websocket</groupId> + <artifactId>Java-WebSocket</artifactId> + <version>1.3.1-SNAPSHOT</version> + <packaging>jar</packaging> + <name>Java WebSocket</name> + <url>http://java-websocket.org/</url> + <description>A barebones WebSocket client and server implementation written in 100% Java</description> + <properties> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <java.version>1.6</java.version> + </properties> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <version>2.5.1</version> + <configuration> + <source>${java.version}</source> + <target>${java.version}</target> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-source-plugin</artifactId> + <version>2.2.1</version> + <executions> + <execution> + <id>attach-sources</id> + <goals> + <goal>jar</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-javadoc-plugin</artifactId> + <version>2.9</version> + <executions> + <execution> + <id>attach-javadocs</id> + <goals> + <goal>jar</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + <developers> + <developer> + <id>TooTallNate</id> + <name>Nathan Rajlich</name> + <email>nathan@tootallnate.net</email> + <url>https://github.com/TooTallNate</url> + <roles> + <role>founder</role> + </roles> + </developer> + <developer> + <id>Davidiusdadi</id> + <name>David Rohmer</name> + <email>rohmer.david@gmail.com</email> + <url>https://github.com/Davidiusdadi</url> + <roles> + <role>maintainer</role> + </roles> + </developer> + </developers> + <licenses> + <license> + <name>MIT License</name> + <url>http://github.com/TooTallNate/Java-WebSocket/blob/master/LICENSE</url> + </license> + </licenses> + <profiles> + <profile> + <id>release-sign-artifacts</id> + <activation> + <property><name>performRelease</name><value>true</value></property> + </activation> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-gpg-plugin</artifactId> + <version>1.1</version> + <executions> + <execution> + <id>sign-artifacts</id> + <phase>verify</phase> + <goals> + <goal>sign</goal> + </goals> + </execution> + </executions> + <configuration> + <keyname>rohmer.david@gmail.com</keyname> + </configuration> + </plugin> + </plugins> + </build> + </profile> + </profiles> +</project> diff --git a/cb-tools/java-source/src/main/example/ChatClient.java b/cb-tools/java-source/src/main/example/ChatClient.java new file mode 100644 index 00000000..96a19100 --- /dev/null +++ b/cb-tools/java-source/src/main/example/ChatClient.java @@ -0,0 +1,163 @@ +import java.awt.Container; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.WindowEvent; +import java.net.URI; +import java.net.URISyntaxException; + +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JFrame; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.JTextField; + +import org.java_websocket.WebSocketImpl; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.drafts.Draft; +import org.java_websocket.drafts.Draft_10; +import org.java_websocket.drafts.Draft_17; +import org.java_websocket.drafts.Draft_75; +import org.java_websocket.drafts.Draft_76; +import org.java_websocket.handshake.ServerHandshake; + +public class ChatClient extends JFrame implements ActionListener { + private static final long serialVersionUID = -6056260699202978657L; + + private final JTextField uriField; + private final JButton connect; + private final JButton close; + private final JTextArea ta; + private final JTextField chatField; + private final JComboBox draft; + private WebSocketClient cc; + + public ChatClient( String defaultlocation ) { + super( "WebSocket Chat Client" ); + Container c = getContentPane(); + GridLayout layout = new GridLayout(); + layout.setColumns( 1 ); + layout.setRows( 6 ); + c.setLayout( layout ); + + Draft[] drafts = { new Draft_17(), new Draft_10(), new Draft_76(), new Draft_75() }; + draft = new JComboBox( drafts ); + c.add( draft ); + + uriField = new JTextField(); + uriField.setText( defaultlocation ); + c.add( uriField ); + + connect = new JButton( "Connect" ); + connect.addActionListener( this ); + c.add( connect ); + + close = new JButton( "Close" ); + close.addActionListener( this ); + close.setEnabled( false ); + c.add( close ); + + JScrollPane scroll = new JScrollPane(); + ta = new JTextArea(); + scroll.setViewportView( ta ); + c.add( scroll ); + + chatField = new JTextField(); + chatField.setText( "" ); + chatField.addActionListener( this ); + c.add( chatField ); + + java.awt.Dimension d = new java.awt.Dimension( 300, 400 ); + setPreferredSize( d ); + setSize( d ); + + addWindowListener( new java.awt.event.WindowAdapter() { + @Override + public void windowClosing( WindowEvent e ) { + if( cc != null ) { + cc.close(); + } + dispose(); + } + } ); + + setLocationRelativeTo( null ); + setVisible( true ); + } + + public void actionPerformed( ActionEvent e ) { + + if( e.getSource() == chatField ) { + if( cc != null ) { + cc.send( chatField.getText() ); + chatField.setText( "" ); + chatField.requestFocus(); + } + + } else if( e.getSource() == connect ) { + try { + // cc = new ChatClient(new URI(uriField.getText()), area, ( Draft ) draft.getSelectedItem() ); + cc = new WebSocketClient( new URI( uriField.getText() ), (Draft) draft.getSelectedItem() ) { + + @Override + public void onMessage( String message ) { + ta.append( "got: " + message + "\n" ); + ta.setCaretPosition( ta.getDocument().getLength() ); + } + + @Override + public void onOpen( ServerHandshake handshake ) { + ta.append( "You are connected to ChatServer: " + getURI() + "\n" ); + ta.setCaretPosition( ta.getDocument().getLength() ); + } + + @Override + public void onClose( int code, String reason, boolean remote ) { + ta.append( "You have been disconnected from: " + getURI() + "; Code: " + code + " " + reason + "\n" ); + ta.setCaretPosition( ta.getDocument().getLength() ); + connect.setEnabled( true ); + uriField.setEditable( true ); + draft.setEditable( true ); + close.setEnabled( false ); + } + + @Override + public void onError( Exception ex ) { + ta.append( "Exception occured ...\n" + ex + "\n" ); + ta.setCaretPosition( ta.getDocument().getLength() ); + ex.printStackTrace(); + connect.setEnabled( true ); + uriField.setEditable( true ); + draft.setEditable( true ); + close.setEnabled( false ); + } + }; + + close.setEnabled( true ); + connect.setEnabled( false ); + uriField.setEditable( false ); + draft.setEditable( false ); + cc.connect(); + } catch ( URISyntaxException ex ) { + ta.append( uriField.getText() + " is not a valid WebSocket URI\n" ); + } + } else if( e.getSource() == close ) { + cc.close(); + } + } + + public static void main( String[] args ) { + WebSocketImpl.DEBUG = true; + String location; + if( args.length != 0 ) { + location = args[ 0 ]; + System.out.println( "Default server url specified: \'" + location + "\'" ); + } else { + location = "ws://localhost:8887"; + System.out.println( "Default server url not specified: defaulting to \'" + location + "\'" ); + } + new ChatClient( location ); + } + +} diff --git a/cb-tools/java-source/src/main/example/ChatServer.java b/cb-tools/java-source/src/main/example/ChatServer.java new file mode 100644 index 00000000..a27cb0ee --- /dev/null +++ b/cb-tools/java-source/src/main/example/ChatServer.java @@ -0,0 +1,99 @@ +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; +import java.util.Collection; + +import org.java_websocket.WebSocket; +import org.java_websocket.WebSocketImpl; +import org.java_websocket.framing.Framedata; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.server.WebSocketServer; + +/** + * A simple WebSocketServer implementation. Keeps track of a "chatroom". + */ +public class ChatServer extends WebSocketServer { + + public ChatServer( int port ) throws UnknownHostException { + super( new InetSocketAddress( port ) ); + } + + public ChatServer( InetSocketAddress address ) { + super( address ); + } + + @Override + public void onOpen( WebSocket conn, ClientHandshake handshake ) { + this.sendToAll( "new connection: " + handshake.getResourceDescriptor() ); + System.out.println( conn.getRemoteSocketAddress().getAddress().getHostAddress() + " entered the room!" ); + } + + @Override + public void onClose( WebSocket conn, int code, String reason, boolean remote ) { + this.sendToAll( conn + " has left the room!" ); + System.out.println( conn + " has left the room!" ); + } + + @Override + public void onMessage( WebSocket conn, String message ) { + this.sendToAll( message ); + System.out.println( conn + ": " + message ); + } + + @Override + public void onFragment( WebSocket conn, Framedata fragment ) { + System.out.println( "received fragment: " + fragment ); + } + + public static void main( String[] args ) throws InterruptedException , IOException { + WebSocketImpl.DEBUG = true; + int port = 8887; // 843 flash policy port + try { + port = Integer.parseInt( args[ 0 ] ); + } catch ( Exception ex ) { + } + ChatServer s = new ChatServer( port ); + s.start(); + System.out.println( "ChatServer started on port: " + s.getPort() ); + + BufferedReader sysin = new BufferedReader( new InputStreamReader( System.in ) ); + while ( true ) { + String in = sysin.readLine(); + s.sendToAll( in ); + if( in.equals( "exit" ) ) { + s.stop(); + break; + } else if( in.equals( "restart" ) ) { + s.stop(); + s.start(); + break; + } + } + } + @Override + public void onError( WebSocket conn, Exception ex ) { + ex.printStackTrace(); + if( conn != null ) { + // some errors like port binding failed may not be assignable to a specific websocket + } + } + + /** + * Sends <var>text</var> to all currently connected WebSocket clients. + * + * @param text + * The String to send across the network. + * @throws InterruptedException + * When socket related I/O errors occur. + */ + public void sendToAll( String text ) { + Collection<WebSocket> con = connections(); + synchronized ( con ) { + for( WebSocket c : con ) { + c.send( text ); + } + } + } +} diff --git a/cb-tools/java-source/src/main/example/ExampleClient.java b/cb-tools/java-source/src/main/example/ExampleClient.java new file mode 100644 index 00000000..3cf68377 --- /dev/null +++ b/cb-tools/java-source/src/main/example/ExampleClient.java @@ -0,0 +1,54 @@ +import java.net.URI; +import java.net.URISyntaxException; + +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.drafts.Draft; +import org.java_websocket.drafts.Draft_10; +import org.java_websocket.framing.Framedata; +import org.java_websocket.handshake.ServerHandshake; + +/** This example demonstrates how to create a websocket connection to a server. Only the most important callbacks are overloaded. */ +public class ExampleClient extends WebSocketClient { + + public ExampleClient( URI serverUri , Draft draft ) { + super( serverUri, draft ); + } + + public ExampleClient( URI serverURI ) { + super( serverURI ); + } + + @Override + public void onOpen( ServerHandshake handshakedata ) { + System.out.println( "opened connection" ); + // if you plan to refuse connection based on ip or httpfields overload: onWebsocketHandshakeReceivedAsClient + } + + @Override + public void onMessage( String message ) { + System.out.println( "received: " + message ); + } + + @Override + public void onFragment( Framedata fragment ) { + System.out.println( "received fragment: " + new String( fragment.getPayloadData().array() ) ); + } + + @Override + public void onClose( int code, String reason, boolean remote ) { + // The codecodes are documented in class org.java_websocket.framing.CloseFrame + System.out.println( "Connection closed by " + ( remote ? "remote peer" : "us" ) ); + } + + @Override + public void onError( Exception ex ) { + ex.printStackTrace(); + // if the error is fatal then onClose will be called additionally + } + + public static void main( String[] args ) throws URISyntaxException { + ExampleClient c = new ExampleClient( new URI( "ws://localhost:8887" ), new Draft_10() ); // more about drafts here: http://github.com/TooTallNate/Java-WebSocket/wiki/Drafts + c.connect(); + } + +} \ No newline at end of file diff --git a/cb-tools/java-source/src/main/example/FragmentedFramesExample.java b/cb-tools/java-source/src/main/example/FragmentedFramesExample.java new file mode 100644 index 00000000..df3c929d --- /dev/null +++ b/cb-tools/java-source/src/main/example/FragmentedFramesExample.java @@ -0,0 +1,57 @@ +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.ByteBuffer; + +import org.java_websocket.WebSocket; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.drafts.Draft_17; +import org.java_websocket.framing.Framedata.Opcode; + +/** + * This example shows how to send fragmented frames.<br> + * For information on when to used fragmented frames see http://tools.ietf.org/html/rfc6455#section-5.4<br> + * Fragmented and normal messages can not be mixed. + * One is however allowed to mix them with control messages like ping/pong. + * + * @see WebSocket#sendFragmentedFrame(Opcode, ByteBuffer, boolean) + **/ +public class FragmentedFramesExample { + public static void main( String[] args ) throws URISyntaxException , IOException , InterruptedException { + // WebSocketImpl.DEBUG = true; // will give extra output + + WebSocketClient websocket = new ExampleClient( new URI( "ws://localhost:8887" ), new Draft_17() ); // Draft_17 is implementation of rfc6455 + if( !websocket.connectBlocking() ) { + System.err.println( "Could not connect to the server." ); + return; + } + + System.out.println( "This example shows how to send fragmented(continuous) messages." ); + + BufferedReader stdin = new BufferedReader( new InputStreamReader( System.in ) ); + while ( websocket.isOpen() ) { + System.out.println( "Please type in a loooooong line(which then will be send in 2 byte fragments):" ); + String longline = stdin.readLine(); + ByteBuffer longelinebuffer = ByteBuffer.wrap( longline.getBytes() ); + longelinebuffer.rewind(); + + for( int position = 2 ; ; position += 2 ) { + if( position < longelinebuffer.capacity() ) { + longelinebuffer.limit( position ); + websocket.sendFragmentedFrame( Opcode.TEXT, longelinebuffer, false );// when sending binary data one should use Opcode.BINARY + assert ( longelinebuffer.remaining() == 0 ); + // after calling sendFragmentedFrame one may reuse the buffer given to the method immediately + } else { + longelinebuffer.limit( longelinebuffer.capacity() ); + websocket.sendFragmentedFrame( Opcode.TEXT, longelinebuffer, true );// sending the last frame + break; + } + + } + System.out.println( "You can not type in the next long message or press Ctr-C to exit." ); + } + System.out.println( "FragmentedFramesExample terminated" ); + } +} diff --git a/cb-tools/java-source/src/main/example/ProxyClientExample.java b/cb-tools/java-source/src/main/example/ProxyClientExample.java new file mode 100644 index 00000000..ddff0ea0 --- /dev/null +++ b/cb-tools/java-source/src/main/example/ProxyClientExample.java @@ -0,0 +1,12 @@ +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.URI; +import java.net.URISyntaxException; + +public class ProxyClientExample { + public static void main( String[] args ) throws URISyntaxException { + ExampleClient c = new ExampleClient( new URI( "ws://echo.websocket.org" ) ); + c.setProxy( new Proxy( Proxy.Type.HTTP, new InetSocketAddress( "proxyaddress", 80 ) ) ); + c.connect(); + } +} diff --git a/cb-tools/java-source/src/main/example/SSLClientExample.java b/cb-tools/java-source/src/main/example/SSLClientExample.java new file mode 100644 index 00000000..e740c9c5 --- /dev/null +++ b/cb-tools/java-source/src/main/example/SSLClientExample.java @@ -0,0 +1,99 @@ +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStreamReader; +import java.net.URI; +import java.security.KeyStore; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManagerFactory; + +import org.java_websocket.WebSocketImpl; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.handshake.ServerHandshake; + +class WebSocketChatClient extends WebSocketClient { + + public WebSocketChatClient( URI serverUri ) { + super( serverUri ); + } + + @Override + public void onOpen( ServerHandshake handshakedata ) { + System.out.println( "Connected" ); + + } + + @Override + public void onMessage( String message ) { + System.out.println( "got: " + message ); + + } + + @Override + public void onClose( int code, String reason, boolean remote ) { + System.out.println( "Disconnected" ); + System.exit( 0 ); + + } + + @Override + public void onError( Exception ex ) { + ex.printStackTrace(); + + } + +} + +public class SSLClientExample { + + /* + * Keystore with certificate created like so (in JKS format): + * + *keytool -genkey -validity 3650 -keystore "keystore.jks" -storepass "storepassword" -keypass "keypassword" -alias "default" -dname "CN=127.0.0.1, OU=MyOrgUnit, O=MyOrg, L=MyCity, S=MyRegion, C=MyCountry" + */ + public static void main( String[] args ) throws Exception { + WebSocketImpl.DEBUG = true; + + WebSocketChatClient chatclient = new WebSocketChatClient( new URI( "wss://localhost:8887" ) ); + + // load up the key store + String STORETYPE = "JKS"; + String KEYSTORE = "keystore.jks"; + String STOREPASSWORD = "storepassword"; + String KEYPASSWORD = "keypassword"; + + KeyStore ks = KeyStore.getInstance( STORETYPE ); + File kf = new File( KEYSTORE ); + ks.load( new FileInputStream( kf ), STOREPASSWORD.toCharArray() ); + + KeyManagerFactory kmf = KeyManagerFactory.getInstance( "SunX509" ); + kmf.init( ks, KEYPASSWORD.toCharArray() ); + TrustManagerFactory tmf = TrustManagerFactory.getInstance( "SunX509" ); + tmf.init( ks ); + + SSLContext sslContext = null; + sslContext = SSLContext.getInstance( "TLS" ); + sslContext.init( kmf.getKeyManagers(), tmf.getTrustManagers(), null ); + // sslContext.init( null, null, null ); // will use java's default key and trust store which is sufficient unless you deal with self-signed certificates + + SSLSocketFactory factory = sslContext.getSocketFactory();// (SSLSocketFactory) SSLSocketFactory.getDefault(); + + chatclient.setSocket( factory.createSocket() ); + + chatclient.connectBlocking(); + + BufferedReader reader = new BufferedReader( new InputStreamReader( System.in ) ); + while ( true ) { + String line = reader.readLine(); + if( line.equals( "close" ) ) { + chatclient.close(); + } else { + chatclient.send( line ); + } + } + + } +} diff --git a/cb-tools/java-source/src/main/example/SSLServerExample.java b/cb-tools/java-source/src/main/example/SSLServerExample.java new file mode 100644 index 00000000..56339540 --- /dev/null +++ b/cb-tools/java-source/src/main/example/SSLServerExample.java @@ -0,0 +1,50 @@ + + +import java.io.File; +import java.io.FileInputStream; +import java.security.KeyStore; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; + +import org.java_websocket.WebSocketImpl; +import org.java_websocket.server.DefaultSSLWebSocketServerFactory; + +public class SSLServerExample { + + /* + * Keystore with certificate created like so (in JKS format): + * + *keytool -genkey -validity 3650 -keystore "keystore.jks" -storepass "storepassword" -keypass "keypassword" -alias "default" -dname "CN=127.0.0.1, OU=MyOrgUnit, O=MyOrg, L=MyCity, S=MyRegion, C=MyCountry" + */ + public static void main( String[] args ) throws Exception { + WebSocketImpl.DEBUG = true; + + ChatServer chatserver = new ChatServer( 8887 ); // Firefox does allow multible ssl connection only via port 443 //tested on FF16 + + // load up the key store + String STORETYPE = "JKS"; + String KEYSTORE = "keystore.jks"; + String STOREPASSWORD = "storepassword"; + String KEYPASSWORD = "keypassword"; + + KeyStore ks = KeyStore.getInstance( STORETYPE ); + File kf = new File( KEYSTORE ); + ks.load( new FileInputStream( kf ), STOREPASSWORD.toCharArray() ); + + KeyManagerFactory kmf = KeyManagerFactory.getInstance( "SunX509" ); + kmf.init( ks, KEYPASSWORD.toCharArray() ); + TrustManagerFactory tmf = TrustManagerFactory.getInstance( "SunX509" ); + tmf.init( ks ); + + SSLContext sslContext = null; + sslContext = SSLContext.getInstance( "TLS" ); + sslContext.init( kmf.getKeyManagers(), tmf.getTrustManagers(), null ); + + chatserver.setWebSocketFactory( new DefaultSSLWebSocketServerFactory( sslContext ) ); + + chatserver.start(); + + } +} diff --git a/cb-tools/java-source/src/main/example/ServerStressTest.java b/cb-tools/java-source/src/main/example/ServerStressTest.java new file mode 100644 index 00000000..8fe2e97a --- /dev/null +++ b/cb-tools/java-source/src/main/example/ServerStressTest.java @@ -0,0 +1,217 @@ +import java.awt.FlowLayout; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.channels.NotYetConnectedException; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; + +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSlider; +import javax.swing.JTextArea; +import javax.swing.JTextField; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.java_websocket.client.WebSocketClient; + +public class ServerStressTest extends JFrame { + private JSlider clients; + private JSlider interval; + private JSlider joinrate; + private JButton start, stop, reset; + private JLabel joinratelabel = new JLabel(); + private JLabel clientslabel = new JLabel(); + private JLabel intervallabel = new JLabel(); + private JTextField uriinput = new JTextField( "ws://localhost:8887" ); + private JTextArea text = new JTextArea( "payload" ); + private Timer timer = new Timer( true ); + private Thread adjustthread; + + private int notyetconnected = 0; + + public ServerStressTest() { + setTitle( "ServerStressTest" ); + setDefaultCloseOperation( EXIT_ON_CLOSE ); + start = new JButton( "Start" ); + start.addActionListener( new ActionListener() { + + @Override + public void actionPerformed( ActionEvent e ) { + start.setEnabled( false ); + stop.setEnabled( true ); + reset.setEnabled( false ); + interval.setEnabled( false ); + clients.setEnabled( false ); + + stopAdjust(); + adjustthread = new Thread( new Runnable() { + @Override + public void run() { + try { + adjust(); + } catch ( InterruptedException e ) { + System.out.println( "adjust chanced" ); + } + } + } ); + adjustthread.start(); + + } + } ); + stop = new JButton( "Stop" ); + stop.setEnabled( false ); + stop.addActionListener( new ActionListener() { + + @Override + public void actionPerformed( ActionEvent e ) { + timer.cancel(); + stopAdjust(); + start.setEnabled( true ); + stop.setEnabled( false ); + reset.setEnabled( true ); + joinrate.setEnabled( true ); + interval.setEnabled( true ); + clients.setEnabled( true ); + } + } ); + reset = new JButton( "reset" ); + reset.setEnabled( true ); + reset.addActionListener( new ActionListener() { + + @Override + public void actionPerformed( ActionEvent e ) { + while ( !websockets.isEmpty() ) + websockets.remove( 0 ).close(); + + } + } ); + joinrate = new JSlider( 0, 5000 ); + joinrate.addChangeListener( new ChangeListener() { + @Override + public void stateChanged( ChangeEvent e ) { + joinratelabel.setText( "Joinrate: " + joinrate.getValue() + " ms " ); + } + } ); + clients = new JSlider( 0, 10000 ); + clients.addChangeListener( new ChangeListener() { + + @Override + public void stateChanged( ChangeEvent e ) { + clientslabel.setText( "Clients: " + clients.getValue() ); + + } + } ); + interval = new JSlider( 0, 5000 ); + interval.addChangeListener( new ChangeListener() { + + @Override + public void stateChanged( ChangeEvent e ) { + intervallabel.setText( "Interval: " + interval.getValue() + " ms " ); + + } + } ); + + setSize( 300, 400 ); + setLayout( new GridLayout( 10, 1, 10, 10 ) ); + add( new JLabel( "URI" ) ); + add( uriinput ); + add( joinratelabel ); + add( joinrate ); + add( clientslabel ); + add( clients ); + add( intervallabel ); + add( interval ); + JPanel south = new JPanel( new FlowLayout( FlowLayout.CENTER ) ); + add( text ); + add( south ); + + south.add( start ); + south.add( stop ); + south.add( reset ); + + joinrate.setValue( 200 ); + interval.setValue( 1000 ); + clients.setValue( 1 ); + + } + + List<WebSocketClient> websockets = Collections.synchronizedList( new LinkedList<WebSocketClient>() ); + URI uri; + public void adjust() throws InterruptedException { + System.out.println( "Adjust" ); + try { + uri = new URI( uriinput.getText() ); + } catch ( URISyntaxException e ) { + e.printStackTrace(); + } + int totalclients = clients.getValue(); + while ( websockets.size() < totalclients ) { + WebSocketClient cl = new ExampleClient( uri ) { + @Override + public void onClose( int code, String reason, boolean remote ) { + System.out.println( "Closed duo " + code + " " + reason ); + clients.setValue( websockets.size() ); + websockets.remove( this ); + } + }; + + cl.connect(); + clients.setValue( websockets.size() ); + websockets.add( cl ); + Thread.sleep( joinrate.getValue() ); + } + while ( websockets.size() > clients.getValue() ) { + websockets.remove( 0 ).close(); + } + timer = new Timer( true ); + timer.scheduleAtFixedRate( new TimerTask() { + @Override + public void run() { + send(); + } + }, 0, interval.getValue() ); + + } + + public void stopAdjust() { + if( adjustthread != null ) { + adjustthread.interrupt(); + try { + adjustthread.join(); + } catch ( InterruptedException e ) { + e.printStackTrace(); + } + } + } + public void send() { + notyetconnected = 0; + String payload = text.getText(); + long time1 = System.currentTimeMillis(); + synchronized ( websockets ) { + for( WebSocketClient cl : websockets ) { + try { + cl.send( payload ); + } catch ( NotYetConnectedException e ) { + notyetconnected++; + } + } + } + System.out.println( websockets.size() + "/" + notyetconnected + " clients sent \"" + payload + "\"" + ( System.currentTimeMillis() - time1 ) ); + } + /** + * @param args + */ + public static void main( String[] args ) { + new ServerStressTest().setVisible( true ); + } + +} diff --git a/cb-tools/java-source/src/main/example/chat.html b/cb-tools/java-source/src/main/example/chat.html new file mode 100644 index 00000000..4a1327e8 --- /dev/null +++ b/cb-tools/java-source/src/main/example/chat.html @@ -0,0 +1,66 @@ +<!DOCTYPE html> +<html> + <head> + <title>WebSocket Chat Client</title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> + + <script type="text/javascript" src="prototype.js"></script> + + <script type="text/javascript"> + document.observe("dom:loaded", function() { + function log(text) { + $("log").innerHTML = (new Date).getTime() + ": " + (!Object.isUndefined(text) && text !== null ? text.escapeHTML() : "null") + $("log").innerHTML; + } + + if (!window.WebSocket) { + alert("FATAL: WebSocket not natively supported. This demo will not work!"); + } + + var ws; + + $("uriForm").observe("submit", function(e) { + e.stop(); + ws = new WebSocket($F("uri")); + ws.onopen = function() { + log("[WebSocket#onopen]\n"); + } + ws.onmessage = function(e) { + log("[WebSocket#onmessage] Message: '" + e.data + "'\n"); + } + ws.onclose = function() { + log("[WebSocket#onclose]\n"); + $("uri", "connect").invoke("enable"); + $("disconnect").disable(); + ws = null; + } + $("uri", "connect").invoke("disable"); + $("disconnect").enable(); + }); + + $("sendForm").observe("submit", function(e) { + e.stop(); + if (ws) { + var textField = $("textField"); + ws.send(textField.value); + log("[WebSocket#send] Send: '" + textField.value + "'\n"); + textField.value = ""; + textField.focus(); + } + }); + + $("disconnect").observe("click", function(e) { + e.stop(); + if (ws) { + ws.close(); + ws = null; + } + }); + }); + </script> + </head> + <body> + <form id="uriForm"><input type="text" id="uri" value="ws://localhost:8887" style="width:200px;"> <input type="submit" id="connect" value="Connect"><input type="button" id="disconnect" value="Disconnect" disabled="disabled"></form><br> + <form id="sendForm"><input type="text" id="textField" value="" style="width:200px;"> <input type="submit" value="Send"></form><br> + <form><textarea id="log" rows="30" cols="100" style="font-family:monospace; color:red;"></textarea></form><br> + </body> +</html> diff --git a/cb-tools/java-source/src/main/example/prototype.js b/cb-tools/java-source/src/main/example/prototype.js new file mode 100644 index 00000000..9fe6e124 --- /dev/null +++ b/cb-tools/java-source/src/main/example/prototype.js @@ -0,0 +1,4874 @@ +/* Prototype JavaScript framework, version 1.6.1 + * (c) 2005-2009 Sam Stephenson + * + * Prototype is freely distributable under the terms of an MIT-style license. + * For details, see the Prototype web site: http://www.prototypejs.org/ + * + *--------------------------------------------------------------------------*/ + +var Prototype = { + Version: '1.6.1', + + Browser: (function(){ + var ua = navigator.userAgent; + var isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]'; + return { + IE: !!window.attachEvent && !isOpera, + Opera: isOpera, + WebKit: ua.indexOf('AppleWebKit/') > -1, + Gecko: ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') === -1, + MobileSafari: /Apple.*Mobile.*Safari/.test(ua) + } + })(), + + BrowserFeatures: { + XPath: !!document.evaluate, + SelectorsAPI: !!document.querySelector, + ElementExtensions: (function() { + var constructor = window.Element || window.HTMLElement; + return !!(constructor && constructor.prototype); + })(), + SpecificElementExtensions: (function() { + if (typeof window.HTMLDivElement !== 'undefined') + return true; + + var div = document.createElement('div'); + var form = document.createElement('form'); + var isSupported = false; + + if (div['__proto__'] && (div['__proto__'] !== form['__proto__'])) { + isSupported = true; + } + + div = form = null; + + return isSupported; + })() + }, + + ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>', + JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/, + + emptyFunction: function() { }, + K: function(x) { return x } +}; + +if (Prototype.Browser.MobileSafari) + Prototype.BrowserFeatures.SpecificElementExtensions = false; + + +var Abstract = { }; + + +var Try = { + these: function() { + var returnValue; + + for (var i = 0, length = arguments.length; i < length; i++) { + var lambda = arguments[i]; + try { + returnValue = lambda(); + break; + } catch (e) { } + } + + return returnValue; + } +}; + +/* Based on Alex Arnell's inheritance implementation. */ + +var Class = (function() { + function subclass() {}; + function create() { + var parent = null, properties = $A(arguments); + if (Object.isFunction(properties[0])) + parent = properties.shift(); + + function klass() { + this.initialize.apply(this, arguments); + } + + Object.extend(klass, Class.Methods); + klass.superclass = parent; + klass.subclasses = []; + + if (parent) { + subclass.prototype = parent.prototype; + klass.prototype = new subclass; + parent.subclasses.push(klass); + } + + for (var i = 0; i < properties.length; i++) + klass.addMethods(properties[i]); + + if (!klass.prototype.initialize) + klass.prototype.initialize = Prototype.emptyFunction; + + klass.prototype.constructor = klass; + return klass; + } + + function addMethods(source) { + var ancestor = this.superclass && this.superclass.prototype; + var properties = Object.keys(source); + + if (!Object.keys({ toString: true }).length) { + if (source.toString != Object.prototype.toString) + properties.push("toString"); + if (source.valueOf != Object.prototype.valueOf) + properties.push("valueOf"); + } + + for (var i = 0, length = properties.length; i < length; i++) { + var property = properties[i], value = source[property]; + if (ancestor && Object.isFunction(value) && + value.argumentNames().first() == "$super") { + var method = value; + value = (function(m) { + return function() { return ancestor[m].apply(this, arguments); }; + })(property).wrap(method); + + value.valueOf = method.valueOf.bind(method); + value.toString = method.toString.bind(method); + } + this.prototype[property] = value; + } + + return this; + } + + return { + create: create, + Methods: { + addMethods: addMethods + } + }; +})(); +(function() { + + var _toString = Object.prototype.toString; + + function extend(destination, source) { + for (var property in source) + destination[property] = source[property]; + return destination; + } + + function inspect(object) { + try { + if (isUndefined(object)) return 'undefined'; + if (object === null) return 'null'; + return object.inspect ? object.inspect() : String(object); + } catch (e) { + if (e instanceof RangeError) return '...'; + throw e; + } + } + + function toJSON(object) { + var type = typeof object; + switch (type) { + case 'undefined': + case 'function': + case 'unknown': return; + case 'boolean': return object.toString(); + } + + if (object === null) return 'null'; + if (object.toJSON) return object.toJSON(); + if (isElement(object)) return; + + var results = []; + for (var property in object) { + var value = toJSON(object[property]); + if (!isUndefined(value)) + results.push(property.toJSON() + ': ' + value); + } + + return '{' + results.join(', ') + '}'; + } + + function toQueryString(object) { + return $H(object).toQueryString(); + } + + function toHTML(object) { + return object && object.toHTML ? object.toHTML() : String.interpret(object); + } + + function keys(object) { + var results = []; + for (var property in object) + results.push(property); + return results; + } + + function values(object) { + var results = []; + for (var property in object) + results.push(object[property]); + return results; + } + + function clone(object) { + return extend({ }, object); + } + + function isElement(object) { + return !!(object && object.nodeType == 1); + } + + function isArray(object) { + return _toString.call(object) == "[object Array]"; + } + + + function isHash(object) { + return object instanceof Hash; + } + + function isFunction(object) { + return typeof object === "function"; + } + + function isString(object) { + return _toString.call(object) == "[object String]"; + } + + function isNumber(object) { + return _toString.call(object) == "[object Number]"; + } + + function isUndefined(object) { + return typeof object === "undefined"; + } + + extend(Object, { + extend: extend, + inspect: inspect, + toJSON: toJSON, + toQueryString: toQueryString, + toHTML: toHTML, + keys: keys, + values: values, + clone: clone, + isElement: isElement, + isArray: isArray, + isHash: isHash, + isFunction: isFunction, + isString: isString, + isNumber: isNumber, + isUndefined: isUndefined + }); +})(); +Object.extend(Function.prototype, (function() { + var slice = Array.prototype.slice; + + function update(array, args) { + var arrayLength = array.length, length = args.length; + while (length--) array[arrayLength + length] = args[length]; + return array; + } + + function merge(array, args) { + array = slice.call(array, 0); + return update(array, args); + } + + function argumentNames() { + var names = this.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1] + .replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, '') + .replace(/\s+/g, '').split(','); + return names.length == 1 && !names[0] ? [] : names; + } + + function bind(context) { + if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this; + var __method = this, args = slice.call(arguments, 1); + return function() { + var a = merge(args, arguments); + return __method.apply(context, a); + } + } + + function bindAsEventListener(context) { + var __method = this, args = slice.call(arguments, 1); + return function(event) { + var a = update([event || window.event], args); + return __method.apply(context, a); + } + } + + function curry() { + if (!arguments.length) return this; + var __method = this, args = slice.call(arguments, 0); + return function() { + var a = merge(args, arguments); + return __method.apply(this, a); + } + } + + function delay(timeout) { + var __method = this, args = slice.call(arguments, 1); + timeout = timeout * 1000 + return window.setTimeout(function() { + return __method.apply(__method, args); + }, timeout); + } + + function defer() { + var args = update([0.01], arguments); + return this.delay.apply(this, args); + } + + function wrap(wrapper) { + var __method = this; + return function() { + var a = update([__method.bind(this)], arguments); + return wrapper.apply(this, a); + } + } + + function methodize() { + if (this._methodized) return this._methodized; + var __method = this; + return this._methodized = function() { + var a = update([this], arguments); + return __method.apply(null, a); + }; + } + + return { + argumentNames: argumentNames, + bind: bind, + bindAsEventListener: bindAsEventListener, + curry: curry, + delay: delay, + defer: defer, + wrap: wrap, + methodize: methodize + } +})()); + + +Date.prototype.toJSON = function() { + return '"' + this.getUTCFullYear() + '-' + + (this.getUTCMonth() + 1).toPaddedString(2) + '-' + + this.getUTCDate().toPaddedString(2) + 'T' + + this.getUTCHours().toPaddedString(2) + ':' + + this.getUTCMinutes().toPaddedString(2) + ':' + + this.getUTCSeconds().toPaddedString(2) + 'Z"'; +}; + + +RegExp.prototype.match = RegExp.prototype.test; + +RegExp.escape = function(str) { + return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); +}; +var PeriodicalExecuter = Class.create({ + initialize: function(callback, frequency) { + this.callback = callback; + this.frequency = frequency; + this.currentlyExecuting = false; + + this.registerCallback(); + }, + + registerCallback: function() { + this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + execute: function() { + this.callback(this); + }, + + stop: function() { + if (!this.timer) return; + clearInterval(this.timer); + this.timer = null; + }, + + onTimerEvent: function() { + if (!this.currentlyExecuting) { + try { + this.currentlyExecuting = true; + this.execute(); + this.currentlyExecuting = false; + } catch(e) { + this.currentlyExecuting = false; + throw e; + } + } + } +}); +Object.extend(String, { + interpret: function(value) { + return value == null ? '' : String(value); + }, + specialChar: { + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '\\': '\\\\' + } +}); + +Object.extend(String.prototype, (function() { + + function prepareReplacement(replacement) { + if (Object.isFunction(replacement)) return replacement; + var template = new Template(replacement); + return function(match) { return template.evaluate(match) }; + } + + function gsub(pattern, replacement) { + var result = '', source = this, match; + replacement = prepareReplacement(replacement); + + if (Object.isString(pattern)) + pattern = RegExp.escape(pattern); + + if (!(pattern.length || pattern.source)) { + replacement = replacement(''); + return replacement + source.split('').join(replacement) + replacement; + } + + while (source.length > 0) { + if (match = source.match(pattern)) { + result += source.slice(0, match.index); + result += String.interpret(replacement(match)); + source = source.slice(match.index + match[0].length); + } else { + result += source, source = ''; + } + } + return result; + } + + function sub(pattern, replacement, count) { + replacement = prepareReplacement(replacement); + count = Object.isUndefined(count) ? 1 : count; + + return this.gsub(pattern, function(match) { + if (--count < 0) return match[0]; + return replacement(match); + }); + } + + function scan(pattern, iterator) { + this.gsub(pattern, iterator); + return String(this); + } + + function truncate(length, truncation) { + length = length || 30; + truncation = Object.isUndefined(truncation) ? '...' : truncation; + return this.length > length ? + this.slice(0, length - truncation.length) + truncation : String(this); + } + + function strip() { + return this.replace(/^\s+/, '').replace(/\s+$/, ''); + } + + function stripTags() { + return this.replace(/<\w+(\s+("[^"]*"|'[^']*'|[^>])+)?>|<\/\w+>/gi, ''); + } + + function stripScripts() { + return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); + } + + function extractScripts() { + var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); + var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); + return (this.match(matchAll) || []).map(function(scriptTag) { + return (scriptTag.match(matchOne) || ['', ''])[1]; + }); + } + + function evalScripts() { + return this.extractScripts().map(function(script) { return eval(script) }); + } + + function escapeHTML() { + return this.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); + } + + function unescapeHTML() { + return this.stripTags().replace(/</g,'<').replace(/>/g,'>').replace(/&/g,'&'); + } + + + function toQueryParams(separator) { + var match = this.strip().match(/([^?#]*)(#.*)?$/); + if (!match) return { }; + + return match[1].split(separator || '&').inject({ }, function(hash, pair) { + if ((pair = pair.split('='))[0]) { + var key = decodeURIComponent(pair.shift()); + var value = pair.length > 1 ? pair.join('=') : pair[0]; + if (value != undefined) value = decodeURIComponent(value); + + if (key in hash) { + if (!Object.isArray(hash[key])) hash[key] = [hash[key]]; + hash[key].push(value); + } + else hash[key] = value; + } + return hash; + }); + } + + function toArray() { + return this.split(''); + } + + function succ() { + return this.slice(0, this.length - 1) + + String.fromCharCode(this.charCodeAt(this.length - 1) + 1); + } + + function times(count) { + return count < 1 ? '' : new Array(count + 1).join(this); + } + + function camelize() { + var parts = this.split('-'), len = parts.length; + if (len == 1) return parts[0]; + + var camelized = this.charAt(0) == '-' + ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1) + : parts[0]; + + for (var i = 1; i < len; i++) + camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1); + + return camelized; + } + + function capitalize() { + return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase(); + } + + function underscore() { + return this.replace(/::/g, '/') + .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2') + .replace(/([a-z\d])([A-Z])/g, '$1_$2') + .replace(/-/g, '_') + .toLowerCase(); + } + + function dasherize() { + return this.replace(/_/g, '-'); + } + + function inspect(useDoubleQuotes) { + var escapedString = this.replace(/[\x00-\x1f\\]/g, function(character) { + if (character in String.specialChar) { + return String.specialChar[character]; + } + return '\\u00' + character.charCodeAt().toPaddedString(2, 16); + }); + if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"'; + return "'" + escapedString.replace(/'/g, '\\\'') + "'"; + } + + function toJSON() { + return this.inspect(true); + } + + function unfilterJSON(filter) { + return this.replace(filter || Prototype.JSONFilter, '$1'); + } + + function isJSON() { + var str = this; + if (str.blank()) return false; + str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''); + return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str); + } + + function evalJSON(sanitize) { + var json = this.unfilterJSON(); + try { + if (!sanitize || json.isJSON()) return eval('(' + json + ')'); + } catch (e) { } + throw new SyntaxError('Badly formed JSON string: ' + this.inspect()); + } + + function include(pattern) { + return this.indexOf(pattern) > -1; + } + + function startsWith(pattern) { + return this.indexOf(pattern) === 0; + } + + function endsWith(pattern) { + var d = this.length - pattern.length; + return d >= 0 && this.lastIndexOf(pattern) === d; + } + + function empty() { + return this == ''; + } + + function blank() { + return /^\s*$/.test(this); + } + + function interpolate(object, pattern) { + return new Template(this, pattern).evaluate(object); + } + + return { + gsub: gsub, + sub: sub, + scan: scan, + truncate: truncate, + strip: String.prototype.trim ? String.prototype.trim : strip, + stripTags: stripTags, + stripScripts: stripScripts, + extractScripts: extractScripts, + evalScripts: evalScripts, + escapeHTML: escapeHTML, + unescapeHTML: unescapeHTML, + toQueryParams: toQueryParams, + parseQuery: toQueryParams, + toArray: toArray, + succ: succ, + times: times, + camelize: camelize, + capitalize: capitalize, + underscore: underscore, + dasherize: dasherize, + inspect: inspect, + toJSON: toJSON, + unfilterJSON: unfilterJSON, + isJSON: isJSON, + evalJSON: evalJSON, + include: include, + startsWith: startsWith, + endsWith: endsWith, + empty: empty, + blank: blank, + interpolate: interpolate + }; +})()); + +var Template = Class.create({ + initialize: function(template, pattern) { + this.template = template.toString(); + this.pattern = pattern || Template.Pattern; + }, + + evaluate: function(object) { + if (object && Object.isFunction(object.toTemplateReplacements)) + object = object.toTemplateReplacements(); + + return this.template.gsub(this.pattern, function(match) { + if (object == null) return (match[1] + ''); + + var before = match[1] || ''; + if (before == '\\') return match[2]; + + var ctx = object, expr = match[3]; + var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/; + match = pattern.exec(expr); + if (match == null) return before; + + while (match != null) { + var comp = match[1].startsWith('[') ? match[2].replace(/\\\\]/g, ']') : match[1]; + ctx = ctx[comp]; + if (null == ctx || '' == match[3]) break; + expr = expr.substring('[' == match[3] ? match[1].length : match[0].length); + match = pattern.exec(expr); + } + + return before + String.interpret(ctx); + }); + } +}); +Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; + +var $break = { }; + +var Enumerable = (function() { + function each(iterator, context) { + var index = 0; + try { + this._each(function(value) { + iterator.call(context, value, index++); + }); + } catch (e) { + if (e != $break) throw e; + } + return this; + } + + function eachSlice(number, iterator, context) { + var index = -number, slices = [], array = this.toArray(); + if (number < 1) return array; + while ((index += number) < array.length) + slices.push(array.slice(index, index+number)); + return slices.collect(iterator, context); + } + + function all(iterator, context) { + iterator = iterator || Prototype.K; + var result = true; + this.each(function(value, index) { + result = result && !!iterator.call(context, value, index); + if (!result) throw $break; + }); + return result; + } + + function any(iterator, context) { + iterator = iterator || Prototype.K; + var result = false; + this.each(function(value, index) { + if (result = !!iterator.call(context, value, index)) + throw $break; + }); + return result; + } + + function collect(iterator, context) { + iterator = iterator || Prototype.K; + var results = []; + this.each(function(value, index) { + results.push(iterator.call(context, value, index)); + }); + return results; + } + + function detect(iterator, context) { + var result; + this.each(function(value, index) { + if (iterator.call(context, value, index)) { + result = value; + throw $break; + } + }); + return result; + } + + function findAll(iterator, context) { + var results = []; + this.each(function(value, index) { + if (iterator.call(context, value, index)) + results.push(value); + }); + return results; + } + + function grep(filter, iterator, context) { + iterator = iterator || Prototype.K; + var results = []; + + if (Object.isString(filter)) + filter = new RegExp(RegExp.escape(filter)); + + this.each(function(value, index) { + if (filter.match(value)) + results.push(iterator.call(context, value, index)); + }); + return results; + } + + function include(object) { + if (Object.isFunction(this.indexOf)) + if (this.indexOf(object) != -1) return true; + + var found = false; + this.each(function(value) { + if (value == object) { + found = true; + throw $break; + } + }); + return found; + } + + function inGroupsOf(number, fillWith) { + fillWith = Object.isUndefined(fillWith) ? null : fillWith; + return this.eachSlice(number, function(slice) { + while(slice.length < number) slice.push(fillWith); + return slice; + }); + } + + function inject(memo, iterator, context) { + this.each(function(value, index) { + memo = iterator.call(context, memo, value, index); + }); + return memo; + } + + function invoke(method) { + var args = $A(arguments).slice(1); + return this.map(function(value) { + return value[method].apply(value, args); + }); + } + + function max(iterator, context) { + iterator = iterator || Prototype.K; + var result; + this.each(function(value, index) { + value = iterator.call(context, value, index); + if (result == null || value >= result) + result = value; + }); + return result; + } + + function min(iterator, context) { + iterator = iterator || Prototype.K; + var result; + this.each(function(value, index) { + value = iterator.call(context, value, index); + if (result == null || value < result) + result = value; + }); + return result; + } + + function partition(iterator, context) { + iterator = iterator || Prototype.K; + var trues = [], falses = []; + this.each(function(value, index) { + (iterator.call(context, value, index) ? + trues : falses).push(value); + }); + return [trues, falses]; + } + + function pluck(property) { + var results = []; + this.each(function(value) { + results.push(value[property]); + }); + return results; + } + + function reject(iterator, context) { + var results = []; + this.each(function(value, index) { + if (!iterator.call(context, value, index)) + results.push(value); + }); + return results; + } + + function sortBy(iterator, context) { + return this.map(function(value, index) { + return { + value: value, + criteria: iterator.call(context, value, index) + }; + }).sort(function(left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + }).pluck('value'); + } + + function toArray() { + return this.map(); + } + + function zip() { + var iterator = Prototype.K, args = $A(arguments); + if (Object.isFunction(args.last())) + iterator = args.pop(); + + var collections = [this].concat(args).map($A); + return this.map(function(value, index) { + return iterator(collections.pluck(index)); + }); + } + + function size() { + return this.toArray().length; + } + + function inspect() { + return '#<Enumerable:' + this.toArray().inspect() + '>'; + } + + + + + + + + + + return { + each: each, + eachSlice: eachSlice, + all: all, + every: all, + any: any, + some: any, + collect: collect, + map: collect, + detect: detect, + findAll: findAll, + select: findAll, + filter: findAll, + grep: grep, + include: include, + member: include, + inGroupsOf: inGroupsOf, + inject: inject, + invoke: invoke, + max: max, + min: min, + partition: partition, + pluck: pluck, + reject: reject, + sortBy: sortBy, + toArray: toArray, + entries: toArray, + zip: zip, + size: size, + inspect: inspect, + find: detect + }; +})(); +function $A(iterable) { + if (!iterable) return []; + if ('toArray' in Object(iterable)) return iterable.toArray(); + var length = iterable.length || 0, results = new Array(length); + while (length--) results[length] = iterable[length]; + return results; +} + +function $w(string) { + if (!Object.isString(string)) return []; + string = string.strip(); + return string ? string.split(/\s+/) : []; +} + +Array.from = $A; + + +(function() { + var arrayProto = Array.prototype, + slice = arrayProto.slice, + _each = arrayProto.forEach; // use native browser JS 1.6 implementation if available + + function each(iterator) { + for (var i = 0, length = this.length; i < length; i++) + iterator(this[i]); + } + if (!_each) _each = each; + + function clear() { + this.length = 0; + return this; + } + + function first() { + return this[0]; + } + + function last() { + return this[this.length - 1]; + } + + function compact() { + return this.select(function(value) { + return value != null; + }); + } + + function flatten() { + return this.inject([], function(array, value) { + if (Object.isArray(value)) + return array.concat(value.flatten()); + array.push(value); + return array; + }); + } + + function without() { + var values = slice.call(arguments, 0); + return this.select(function(value) { + return !values.include(value); + }); + } + + function reverse(inline) { + return (inline !== false ? this : this.toArray())._reverse(); + } + + function uniq(sorted) { + return this.inject([], function(array, value, index) { + if (0 == index || (sorted ? array.last() != value : !array.include(value))) + array.push(value); + return array; + }); + } + + function intersect(array) { + return this.uniq().findAll(function(item) { + return array.detect(function(value) { return item === value }); + }); + } + + + function clone() { + return slice.call(this, 0); + } + + function size() { + return this.length; + } + + function inspect() { + return '[' + this.map(Object.inspect).join(', ') + ']'; + } + + function toJSON() { + var results = []; + this.each(function(object) { + var value = Object.toJSON(object); + if (!Object.isUndefined(value)) results.push(value); + }); + return '[' + results.join(', ') + ']'; + } + + function indexOf(item, i) { + i || (i = 0); + var length = this.length; + if (i < 0) i = length + i; + for (; i < length; i++) + if (this[i] === item) return i; + return -1; + } + + function lastIndexOf(item, i) { + i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1; + var n = this.slice(0, i).reverse().indexOf(item); + return (n < 0) ? n : i - n - 1; + } + + function concat() { + var array = slice.call(this, 0), item; + for (var i = 0, length = arguments.length; i < length; i++) { + item = arguments[i]; + if (Object.isArray(item) && !('callee' in item)) { + for (var j = 0, arrayLength = item.length; j < arrayLength; j++) + array.push(item[j]); + } else { + array.push(item); + } + } + return array; + } + + Object.extend(arrayProto, Enumerable); + + if (!arrayProto._reverse) + arrayProto._reverse = arrayProto.reverse; + + Object.extend(arrayProto, { + _each: _each, + clear: clear, + first: first, + last: last, + compact: compact, + flatten: flatten, + without: without, + reverse: reverse, + uniq: uniq, + intersect: intersect, + clone: clone, + toArray: clone, + size: size, + inspect: inspect, + toJSON: toJSON + }); + + var CONCAT_ARGUMENTS_BUGGY = (function() { + return [].concat(arguments)[0][0] !== 1; + })(1,2) + + if (CONCAT_ARGUMENTS_BUGGY) arrayProto.concat = concat; + + if (!arrayProto.indexOf) arrayProto.indexOf = indexOf; + if (!arrayProto.lastIndexOf) arrayProto.lastIndexOf = lastIndexOf; +})(); +function $H(object) { + return new Hash(object); +}; + +var Hash = Class.create(Enumerable, (function() { + function initialize(object) { + this._object = Object.isHash(object) ? object.toObject() : Object.clone(object); + } + + function _each(iterator) { + for (var key in this._object) { + var value = this._object[key], pair = [key, value]; + pair.key = key; + pair.value = value; + iterator(pair); + } + } + + function set(key, value) { + return this._object[key] = value; + } + + function get(key) { + if (this._object[key] !== Object.prototype[key]) + return this._object[key]; + } + + function unset(key) { + var value = this._object[key]; + delete this._object[key]; + return value; + } + + function toObject() { + return Object.clone(this._object); + } + + function keys() { + return this.pluck('key'); + } + + function values() { + return this.pluck('value'); + } + + function index(value) { + var match = this.detect(function(pair) { + return pair.value === value; + }); + return match && match.key; + } + + function merge(object) { + return this.clone().update(object); + } + + function update(object) { + return new Hash(object).inject(this, function(result, pair) { + result.set(pair.key, pair.value); + return result; + }); + } + + function toQueryPair(key, value) { + if (Object.isUndefined(value)) return key; + return key + '=' + encodeURIComponent(String.interpret(value)); + } + + function toQueryString() { + return this.inject([], function(results, pair) { + var key = encodeURIComponent(pair.key), values = pair.value; + + if (values && typeof values == 'object') { + if (Object.isArray(values)) + return results.concat(values.map(toQueryPair.curry(key))); + } else results.push(toQueryPair(key, values)); + return results; + }).join('&'); + } + + function inspect() { + return '#<Hash:{' + this.map(function(pair) { + return pair.map(Object.inspect).join(': '); + }).join(', ') + '}>'; + } + + function toJSON() { + return Object.toJSON(this.toObject()); + } + + function clone() { + return new Hash(this); + } + + return { + initialize: initialize, + _each: _each, + set: set, + get: get, + unset: unset, + toObject: toObject, + toTemplateReplacements: toObject, + keys: keys, + values: values, + index: index, + merge: merge, + update: update, + toQueryString: toQueryString, + inspect: inspect, + toJSON: toJSON, + clone: clone + }; +})()); + +Hash.from = $H; +Object.extend(Number.prototype, (function() { + function toColorPart() { + return this.toPaddedString(2, 16); + } + + function succ() { + return this + 1; + } + + function times(iterator, context) { + $R(0, this, true).each(iterator, context); + return this; + } + + function toPaddedString(length, radix) { + var string = this.toString(radix || 10); + return '0'.times(length - string.length) + string; + } + + function toJSON() { + return isFinite(this) ? this.toString() : 'null'; + } + + function abs() { + return Math.abs(this); + } + + function round() { + return Math.round(this); + } + + function ceil() { + return Math.ceil(this); + } + + function floor() { + return Math.floor(this); + } + + return { + toColorPart: toColorPart, + succ: succ, + times: times, + toPaddedString: toPaddedString, + toJSON: toJSON, + abs: abs, + round: round, + ceil: ceil, + floor: floor + }; +})()); + +function $R(start, end, exclusive) { + return new ObjectRange(start, end, exclusive); +} + +var ObjectRange = Class.create(Enumerable, (function() { + function initialize(start, end, exclusive) { + this.start = start; + this.end = end; + this.exclusive = exclusive; + } + + function _each(iterator) { + var value = this.start; + while (this.include(value)) { + iterator(value); + value = value.succ(); + } + } + + function include(value) { + if (value < this.start) + return false; + if (this.exclusive) + return value < this.end; + return value <= this.end; + } + + return { + initialize: initialize, + _each: _each, + include: include + }; +})()); + + + +var Ajax = { + getTransport: function() { + return Try.these( + function() {return new XMLHttpRequest()}, + function() {return new ActiveXObject('Msxml2.XMLHTTP')}, + function() {return new ActiveXObject('Microsoft.XMLHTTP')} + ) || false; + }, + + activeRequestCount: 0 +}; + +Ajax.Responders = { + responders: [], + + _each: function(iterator) { + this.responders._each(iterator); + }, + + register: function(responder) { + if (!this.include(responder)) + this.responders.push(responder); + }, + + unregister: function(responder) { + this.responders = this.responders.without(responder); + }, + + dispatch: function(callback, request, transport, json) { + this.each(function(responder) { + if (Object.isFunction(responder[callback])) { + try { + responder[callback].apply(responder, [request, transport, json]); + } catch (e) { } + } + }); + } +}; + +Object.extend(Ajax.Responders, Enumerable); + +Ajax.Responders.register({ + onCreate: function() { Ajax.activeRequestCount++ }, + onComplete: function() { Ajax.activeRequestCount-- } +}); +Ajax.Base = Class.create({ + initialize: function(options) { + this.options = { + method: 'post', + asynchronous: true, + contentType: 'application/x-www-form-urlencoded', + encoding: 'UTF-8', + parameters: '', + evalJSON: true, + evalJS: true + }; + Object.extend(this.options, options || { }); + + this.options.method = this.options.method.toLowerCase(); + + if (Object.isString(this.options.parameters)) + this.options.parameters = this.options.parameters.toQueryParams(); + else if (Object.isHash(this.options.parameters)) + this.options.parameters = this.options.parameters.toObject(); + } +}); +Ajax.Request = Class.create(Ajax.Base, { + _complete: false, + + initialize: function($super, url, options) { + $super(options); + this.transport = Ajax.getTransport(); + this.request(url); + }, + + request: function(url) { + this.url = url; + this.method = this.options.method; + var params = Object.clone(this.options.parameters); + + if (!['get', 'post'].include(this.method)) { + params['_method'] = this.method; + this.method = 'post'; + } + + this.parameters = params; + + if (params = Object.toQueryString(params)) { + if (this.method == 'get') + this.url += (this.url.include('?') ? '&' : '?') + params; + else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) + params += '&_='; + } + + try { + var response = new Ajax.Response(this); + if (this.options.onCreate) this.options.onCreate(response); + Ajax.Responders.dispatch('onCreate', this, response); + + this.transport.open(this.method.toUpperCase(), this.url, + this.options.asynchronous); + + if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1); + + this.transport.onreadystatechange = this.onStateChange.bind(this); + this.setRequestHeaders(); + + this.body = this.method == 'post' ? (this.options.postBody || params) : null; + this.transport.send(this.body); + + /* Force Firefox to handle ready state 4 for synchronous requests */ + if (!this.options.asynchronous && this.transport.overrideMimeType) + this.onStateChange(); + + } + catch (e) { + this.dispatchException(e); + } + }, + + onStateChange: function() { + var readyState = this.transport.readyState; + if (readyState > 1 && !((readyState == 4) && this._complete)) + this.respondToReadyState(this.transport.readyState); + }, + + setRequestHeaders: function() { + var headers = { + 'X-Requested-With': 'XMLHttpRequest', + 'X-Prototype-Version': Prototype.Version, + 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' + }; + + if (this.method == 'post') { + headers['Content-type'] = this.options.contentType + + (this.options.encoding ? '; charset=' + this.options.encoding : ''); + + /* Force "Connection: close" for older Mozilla browsers to work + * around a bug where XMLHttpRequest sends an incorrect + * Content-length header. See Mozilla Bugzilla #246651. + */ + if (this.transport.overrideMimeType && + (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) + headers['Connection'] = 'close'; + } + + if (typeof this.options.requestHeaders == 'object') { + var extras = this.options.requestHeaders; + + if (Object.isFunction(extras.push)) + for (var i = 0, length = extras.length; i < length; i += 2) + headers[extras[i]] = extras[i+1]; + else + $H(extras).each(function(pair) { headers[pair.key] = pair.value }); + } + + for (var name in headers) + this.transport.setRequestHeader(name, headers[name]); + }, + + success: function() { + var status = this.getStatus(); + return !status || (status >= 200 && status < 300); + }, + + getStatus: function() { + try { + return this.transport.status || 0; + } catch (e) { return 0 } + }, + + respondToReadyState: function(readyState) { + var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this); + + if (state == 'Complete') { + try { + this._complete = true; + (this.options['on' + response.status] + || this.options['on' + (this.success() ? 'Success' : 'Failure')] + || Prototype.emptyFunction)(response, response.headerJSON); + } catch (e) { + this.dispatchException(e); + } + + var contentType = response.getHeader('Content-type'); + if (this.options.evalJS == 'force' + || (this.options.evalJS && this.isSameOrigin() && contentType + && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i))) + this.evalResponse(); + } + + try { + (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON); + Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON); + } catch (e) { + this.dispatchException(e); + } + + if (state == 'Complete') { + this.transport.onreadystatechange = Prototype.emptyFunction; + } + }, + + isSameOrigin: function() { + var m = this.url.match(/^\s*https?:\/\/[^\/]*/); + return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({ + protocol: location.protocol, + domain: document.domain, + port: location.port ? ':' + location.port : '' + })); + }, + + getHeader: function(name) { + try { + return this.transport.getResponseHeader(name) || null; + } catch (e) { return null; } + }, + + evalResponse: function() { + try { + return eval((this.transport.responseText || '').unfilterJSON()); + } catch (e) { + this.dispatchException(e); + } + }, + + dispatchException: function(exception) { + (this.options.onException || Prototype.emptyFunction)(this, exception); + Ajax.Responders.dispatch('onException', this, exception); + } +}); + +Ajax.Request.Events = + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; + + + + + + + + +Ajax.Response = Class.create({ + initialize: function(request){ + this.request = request; + var transport = this.transport = request.transport, + readyState = this.readyState = transport.readyState; + + if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) { + this.status = this.getStatus(); + this.statusText = this.getStatusText(); + this.responseText = String.interpret(transport.responseText); + this.headerJSON = this._getHeaderJSON(); + } + + if(readyState == 4) { + var xml = transport.responseXML; + this.responseXML = Object.isUndefined(xml) ? null : xml; + this.responseJSON = this._getResponseJSON(); + } + }, + + status: 0, + + statusText: '', + + getStatus: Ajax.Request.prototype.getStatus, + + getStatusText: function() { + try { + return this.transport.statusText || ''; + } catch (e) { return '' } + }, + + getHeader: Ajax.Request.prototype.getHeader, + + getAllHeaders: function() { + try { + return this.getAllResponseHeaders(); + } catch (e) { return null } + }, + + getResponseHeader: function(name) { + return this.transport.getResponseHeader(name); + }, + + getAllResponseHeaders: function() { + return this.transport.getAllResponseHeaders(); + }, + + _getHeaderJSON: function() { + var json = this.getHeader('X-JSON'); + if (!json) return null; + json = decodeURIComponent(escape(json)); + try { + return json.evalJSON(this.request.options.sanitizeJSON || + !this.request.isSameOrigin()); + } catch (e) { + this.request.dispatchException(e); + } + }, + + _getResponseJSON: function() { + var options = this.request.options; + if (!options.evalJSON || (options.evalJSON != 'force' && + !(this.getHeader('Content-type') || '').include('application/json')) || + this.responseText.blank()) + return null; + try { + return this.responseText.evalJSON(options.sanitizeJSON || + !this.request.isSameOrigin()); + } catch (e) { + this.request.dispatchException(e); + } + } +}); + +Ajax.Updater = Class.create(Ajax.Request, { + initialize: function($super, container, url, options) { + this.container = { + success: (container.success || container), + failure: (container.failure || (container.success ? null : container)) + }; + + options = Object.clone(options); + var onComplete = options.onComplete; + options.onComplete = (function(response, json) { + this.updateContent(response.responseText); + if (Object.isFunction(onComplete)) onComplete(response, json); + }).bind(this); + + $super(url, options); + }, + + updateContent: function(responseText) { + var receiver = this.container[this.success() ? 'success' : 'failure'], + options = this.options; + + if (!options.evalScripts) responseText = responseText.stripScripts(); + + if (receiver = $(receiver)) { + if (options.insertion) { + if (Object.isString(options.insertion)) { + var insertion = { }; insertion[options.insertion] = responseText; + receiver.insert(insertion); + } + else options.insertion(receiver, responseText); + } + else receiver.update(responseText); + } + } +}); + +Ajax.PeriodicalUpdater = Class.create(Ajax.Base, { + initialize: function($super, container, url, options) { + $super(options); + this.onComplete = this.options.onComplete; + + this.frequency = (this.options.frequency || 2); + this.decay = (this.options.decay || 1); + + this.updater = { }; + this.container = container; + this.url = url; + + this.start(); + }, + + start: function() { + this.options.onComplete = this.updateComplete.bind(this); + this.onTimerEvent(); + }, + + stop: function() { + this.updater.options.onComplete = undefined; + clearTimeout(this.timer); + (this.onComplete || Prototype.emptyFunction).apply(this, arguments); + }, + + updateComplete: function(response) { + if (this.options.decay) { + this.decay = (response.responseText == this.lastText ? + this.decay * this.options.decay : 1); + + this.lastText = response.responseText; + } + this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency); + }, + + onTimerEvent: function() { + this.updater = new Ajax.Updater(this.container, this.url, this.options); + } +}); + + + +function $(element) { + if (arguments.length > 1) { + for (var i = 0, elements = [], length = arguments.length; i < length; i++) + elements.push($(arguments[i])); + return elements; + } + if (Object.isString(element)) + element = document.getElementById(element); + return Element.extend(element); +} + +if (Prototype.BrowserFeatures.XPath) { + document._getElementsByXPath = function(expression, parentElement) { + var results = []; + var query = document.evaluate(expression, $(parentElement) || document, + null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + for (var i = 0, length = query.snapshotLength; i < length; i++) + results.push(Element.extend(query.snapshotItem(i))); + return results; + }; +} + +/*--------------------------------------------------------------------------*/ + +if (!window.Node) var Node = { }; + +if (!Node.ELEMENT_NODE) { + Object.extend(Node, { + ELEMENT_NODE: 1, + ATTRIBUTE_NODE: 2, + TEXT_NODE: 3, + CDATA_SECTION_NODE: 4, + ENTITY_REFERENCE_NODE: 5, + ENTITY_NODE: 6, + PROCESSING_INSTRUCTION_NODE: 7, + COMMENT_NODE: 8, + DOCUMENT_NODE: 9, + DOCUMENT_TYPE_NODE: 10, + DOCUMENT_FRAGMENT_NODE: 11, + NOTATION_NODE: 12 + }); +} + + +(function(global) { + + var SETATTRIBUTE_IGNORES_NAME = (function(){ + var elForm = document.createElement("form"); + var elInput = document.createElement("input"); + var root = document.documentElement; + elInput.setAttribute("name", "test"); + elForm.appendChild(elInput); + root.appendChild(elForm); + var isBuggy = elForm.elements + ? (typeof elForm.elements.test == "undefined") + : null; + root.removeChild(elForm); + elForm = elInput = null; + return isBuggy; + })(); + + var element = global.Element; + global.Element = function(tagName, attributes) { + attributes = attributes || { }; + tagName = tagName.toLowerCase(); + var cache = Element.cache; + if (SETATTRIBUTE_IGNORES_NAME && attributes.name) { + tagName = '<' + tagName + ' name="' + attributes.name + '">'; + delete attributes.name; + return Element.writeAttribute(document.createElement(tagName), attributes); + } + if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName)); + return Element.writeAttribute(cache[tagName].cloneNode(false), attributes); + }; + Object.extend(global.Element, element || { }); + if (element) global.Element.prototype = element.prototype; +})(this); + +Element.cache = { }; +Element.idCounter = 1; + +Element.Methods = { + visible: function(element) { + return $(element).style.display != 'none'; + }, + + toggle: function(element) { + element = $(element); + Element[Element.visible(element) ? 'hide' : 'show'](element); + return element; + }, + + + hide: function(element) { + element = $(element); + element.style.display = 'none'; + return element; + }, + + show: function(element) { + element = $(element); + element.style.display = ''; + return element; + }, + + remove: function(element) { + element = $(element); + element.parentNode.removeChild(element); + return element; + }, + + update: (function(){ + + var SELECT_ELEMENT_INNERHTML_BUGGY = (function(){ + var el = document.createElement("select"), + isBuggy = true; + el.innerHTML = "<option value=\"test\">test</option>"; + if (el.options && el.options[0]) { + isBuggy = el.options[0].nodeName.toUpperCase() !== "OPTION"; + } + el = null; + return isBuggy; + })(); + + var TABLE_ELEMENT_INNERHTML_BUGGY = (function(){ + try { + var el = document.createElement("table"); + if (el && el.tBodies) { + el.innerHTML = "<tbody><tr><td>test</td></tr></tbody>"; + var isBuggy = typeof el.tBodies[0] == "undefined"; + el = null; + return isBuggy; + } + } catch (e) { + return true; + } + })(); + + var SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING = (function () { + var s = document.createElement("script"), + isBuggy = false; + try { + s.appendChild(document.createTextNode("")); + isBuggy = !s.firstChild || + s.firstChild && s.firstChild.nodeType !== 3; + } catch (e) { + isBuggy = true; + } + s = null; + return isBuggy; + })(); + + function update(element, content) { + element = $(element); + + if (content && content.toElement) + content = content.toElement(); + + if (Object.isElement(content)) + return element.update().insert(content); + + content = Object.toHTML(content); + + var tagName = element.tagName.toUpperCase(); + + if (tagName === 'SCRIPT' && SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING) { + element.text = content; + return element; + } + + if (SELECT_ELEMENT_INNERHTML_BUGGY || TABLE_ELEMENT_INNERHTML_BUGGY) { + if (tagName in Element._insertionTranslations.tags) { + while (element.firstChild) { + element.removeChild(element.firstChild); + } + Element._getContentFromAnonymousElement(tagName, content.stripScripts()) + .each(function(node) { + element.appendChild(node) + }); + } + else { + element.innerHTML = content.stripScripts(); + } + } + else { + element.innerHTML = content.stripScripts(); + } + + content.evalScripts.bind(content).defer(); + return element; + } + + return update; + })(), + + replace: function(element, content) { + element = $(element); + if (content && content.toElement) content = content.toElement(); + else if (!Object.isElement(content)) { + content = Object.toHTML(content); + var range = element.ownerDocument.createRange(); + range.selectNode(element); + content.evalScripts.bind(content).defer(); + content = range.createContextualFragment(content.stripScripts()); + } + element.parentNode.replaceChild(content, element); + return element; + }, + + insert: function(element, insertions) { + element = $(element); + + if (Object.isString(insertions) || Object.isNumber(insertions) || + Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML))) + insertions = {bottom:insertions}; + + var content, insert, tagName, childNodes; + + for (var position in insertions) { + content = insertions[position]; + position = position.toLowerCase(); + insert = Element._insertionTranslations[position]; + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) { + insert(element, content); + continue; + } + + content = Object.toHTML(content); + + tagName = ((position == 'before' || position == 'after') + ? element.parentNode : element).tagName.toUpperCase(); + + childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); + + if (position == 'top' || position == 'after') childNodes.reverse(); + childNodes.each(insert.curry(element)); + + content.evalScripts.bind(content).defer(); + } + + return element; + }, + + wrap: function(element, wrapper, attributes) { + element = $(element); + if (Object.isElement(wrapper)) + $(wrapper).writeAttribute(attributes || { }); + else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes); + else wrapper = new Element('div', wrapper); + if (element.parentNode) + element.parentNode.replaceChild(wrapper, element); + wrapper.appendChild(element); + return wrapper; + }, + + inspect: function(element) { + element = $(element); + var result = '<' + element.tagName.toLowerCase(); + $H({'id': 'id', 'className': 'class'}).each(function(pair) { + var property = pair.first(), attribute = pair.last(); + var value = (element[property] || '').toString(); + if (value) result += ' ' + attribute + '=' + value.inspect(true); + }); + return result + '>'; + }, + + recursivelyCollect: function(element, property) { + element = $(element); + var elements = []; + while (element = element[property]) + if (element.nodeType == 1) + elements.push(Element.extend(element)); + return elements; + }, + + ancestors: function(element) { + return Element.recursivelyCollect(element, 'parentNode'); + }, + + descendants: function(element) { + return Element.select(element, "*"); + }, + + firstDescendant: function(element) { + element = $(element).firstChild; + while (element && element.nodeType != 1) element = element.nextSibling; + return $(element); + }, + + immediateDescendants: function(element) { + if (!(element = $(element).firstChild)) return []; + while (element && element.nodeType != 1) element = element.nextSibling; + if (element) return [element].concat($(element).nextSiblings()); + return []; + }, + + previousSiblings: function(element) { + return Element.recursivelyCollect(element, 'previousSibling'); + }, + + nextSiblings: function(element) { + return Element.recursivelyCollect(element, 'nextSibling'); + }, + + siblings: function(element) { + element = $(element); + return Element.previousSiblings(element).reverse() + .concat(Element.nextSiblings(element)); + }, + + match: function(element, selector) { + if (Object.isString(selector)) + selector = new Selector(selector); + return selector.match($(element)); + }, + + up: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return $(element.parentNode); + var ancestors = Element.ancestors(element); + return Object.isNumber(expression) ? ancestors[expression] : + Selector.findElement(ancestors, expression, index); + }, + + down: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return Element.firstDescendant(element); + return Object.isNumber(expression) ? Element.descendants(element)[expression] : + Element.select(element, expression)[index || 0]; + }, + + previous: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element)); + var previousSiblings = Element.previousSiblings(element); + return Object.isNumber(expression) ? previousSiblings[expression] : + Selector.findElement(previousSiblings, expression, index); + }, + + next: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element)); + var nextSiblings = Element.nextSiblings(element); + return Object.isNumber(expression) ? nextSiblings[expression] : + Selector.findElement(nextSiblings, expression, index); + }, + + + select: function(element) { + var args = Array.prototype.slice.call(arguments, 1); + return Selector.findChildElements(element, args); + }, + + adjacent: function(element) { + var args = Array.prototype.slice.call(arguments, 1); + return Selector.findChildElements(element.parentNode, args).without(element); + }, + + identify: function(element) { + element = $(element); + var id = Element.readAttribute(element, 'id'); + if (id) return id; + do { id = 'anonymous_element_' + Element.idCounter++ } while ($(id)); + Element.writeAttribute(element, 'id', id); + return id; + }, + + readAttribute: function(element, name) { + element = $(element); + if (Prototype.Browser.IE) { + var t = Element._attributeTranslations.read; + if (t.values[name]) return t.values[name](element, name); + if (t.names[name]) name = t.names[name]; + if (name.include(':')) { + return (!element.attributes || !element.attributes[name]) ? null : + element.attributes[name].value; + } + } + return element.getAttribute(name); + }, + + writeAttribute: function(element, name, value) { + element = $(element); + var attributes = { }, t = Element._attributeTranslations.write; + + if (typeof name == 'object') attributes = name; + else attributes[name] = Object.isUndefined(value) ? true : value; + + for (var attr in attributes) { + name = t.names[attr] || attr; + value = attributes[attr]; + if (t.values[attr]) name = t.values[attr](element, value); + if (value === false || value === null) + element.removeAttribute(name); + else if (value === true) + element.setAttribute(name, name); + else element.setAttribute(name, value); + } + return element; + }, + + getHeight: function(element) { + return Element.getDimensions(element).height; + }, + + getWidth: function(element) { + return Element.getDimensions(element).width; + }, + + classNames: function(element) { + return new Element.ClassNames(element); + }, + + hasClassName: function(element, className) { + if (!(element = $(element))) return; + var elementClassName = element.className; + return (elementClassName.length > 0 && (elementClassName == className || + new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName))); + }, + + addClassName: function(element, className) { + if (!(element = $(element))) return; + if (!Element.hasClassName(element, className)) + element.className += (element.className ? ' ' : '') + className; + return element; + }, + + removeClassName: function(element, className) { + if (!(element = $(element))) return; + element.className = element.className.replace( + new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip(); + return element; + }, + + toggleClassName: function(element, className) { + if (!(element = $(element))) return; + return Element[Element.hasClassName(element, className) ? + 'removeClassName' : 'addClassName'](element, className); + }, + + cleanWhitespace: function(element) { + element = $(element); + var node = element.firstChild; + while (node) { + var nextNode = node.nextSibling; + if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) + element.removeChild(node); + node = nextNode; + } + return element; + }, + + empty: function(element) { + return $(element).innerHTML.blank(); + }, + + descendantOf: function(element, ancestor) { + element = $(element), ancestor = $(ancestor); + + if (element.compareDocumentPosition) + return (element.compareDocumentPosition(ancestor) & 8) === 8; + + if (ancestor.contains) + return ancestor.contains(element) && ancestor !== element; + + while (element = element.parentNode) + if (element == ancestor) return true; + + return false; + }, + + scrollTo: function(element) { + element = $(element); + var pos = Element.cumulativeOffset(element); + window.scrollTo(pos[0], pos[1]); + return element; + }, + + getStyle: function(element, style) { + element = $(element); + style = style == 'float' ? 'cssFloat' : style.camelize(); + var value = element.style[style]; + if (!value || value == 'auto') { + var css = document.defaultView.getComputedStyle(element, null); + value = css ? css[style] : null; + } + if (style == 'opacity') return value ? parseFloat(value) : 1.0; + return value == 'auto' ? null : value; + }, + + getOpacity: function(element) { + return $(element).getStyle('opacity'); + }, + + setStyle: function(element, styles) { + element = $(element); + var elementStyle = element.style, match; + if (Object.isString(styles)) { + element.style.cssText += ';' + styles; + return styles.include('opacity') ? + element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element; + } + for (var property in styles) + if (property == 'opacity') element.setOpacity(styles[property]); + else + elementStyle[(property == 'float' || property == 'cssFloat') ? + (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') : + property] = styles[property]; + + return element; + }, + + setOpacity: function(element, value) { + element = $(element); + element.style.opacity = (value == 1 || value === '') ? '' : + (value < 0.00001) ? 0 : value; + return element; + }, + + getDimensions: function(element) { + element = $(element); + var display = Element.getStyle(element, 'display'); + if (display != 'none' && display != null) // Safari bug + return {width: element.offsetWidth, height: element.offsetHeight}; + + var els = element.style; + var originalVisibility = els.visibility; + var originalPosition = els.position; + var originalDisplay = els.display; + els.visibility = 'hidden'; + if (originalPosition != 'fixed') // Switching fixed to absolute causes issues in Safari + els.position = 'absolute'; + els.display = 'block'; + var originalWidth = element.clientWidth; + var originalHeight = element.clientHeight; + els.display = originalDisplay; + els.position = originalPosition; + els.visibility = originalVisibility; + return {width: originalWidth, height: originalHeight}; + }, + + makePositioned: function(element) { + element = $(element); + var pos = Element.getStyle(element, 'position'); + if (pos == 'static' || !pos) { + element._madePositioned = true; + element.style.position = 'relative'; + if (Prototype.Browser.Opera) { + element.style.top = 0; + element.style.left = 0; + } + } + return element; + }, + + undoPositioned: function(element) { + element = $(element); + if (element._madePositioned) { + element._madePositioned = undefined; + element.style.position = + element.style.top = + element.style.left = + element.style.bottom = + element.style.right = ''; + } + return element; + }, + + makeClipping: function(element) { + element = $(element); + if (element._overflow) return element; + element._overflow = Element.getStyle(element, 'overflow') || 'auto'; + if (element._overflow !== 'hidden') + element.style.overflow = 'hidden'; + return element; + }, + + undoClipping: function(element) { + element = $(element); + if (!element._overflow) return element; + element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; + element._overflow = null; + return element; + }, + + cumulativeOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + positionedOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + if (element.tagName.toUpperCase() == 'BODY') break; + var p = Element.getStyle(element, 'position'); + if (p !== 'static') break; + } + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + absolutize: function(element) { + element = $(element); + if (Element.getStyle(element, 'position') == 'absolute') return element; + + var offsets = Element.positionedOffset(element); + var top = offsets[1]; + var left = offsets[0]; + var width = element.clientWidth; + var height = element.clientHeight; + + element._originalLeft = left - parseFloat(element.style.left || 0); + element._originalTop = top - parseFloat(element.style.top || 0); + element._originalWidth = element.style.width; + element._originalHeight = element.style.height; + + element.style.position = 'absolute'; + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.width = width + 'px'; + element.style.height = height + 'px'; + return element; + }, + + relativize: function(element) { + element = $(element); + if (Element.getStyle(element, 'position') == 'relative') return element; + + element.style.position = 'relative'; + var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); + var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); + + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.height = element._originalHeight; + element.style.width = element._originalWidth; + return element; + }, + + cumulativeScrollOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + getOffsetParent: function(element) { + if (element.offsetParent) return $(element.offsetParent); + if (element == document.body) return $(element); + + while ((element = element.parentNode) && element != document.body) + if (Element.getStyle(element, 'position') != 'static') + return $(element); + + return $(document.body); + }, + + viewportOffset: function(forElement) { + var valueT = 0, valueL = 0; + + var element = forElement; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + + if (element.offsetParent == document.body && + Element.getStyle(element, 'position') == 'absolute') break; + + } while (element = element.offsetParent); + + element = forElement; + do { + if (!Prototype.Browser.Opera || (element.tagName && (element.tagName.toUpperCase() == 'BODY'))) { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; + } + } while (element = element.parentNode); + + return Element._returnOffset(valueL, valueT); + }, + + clonePosition: function(element, source) { + var options = Object.extend({ + setLeft: true, + setTop: true, + setWidth: true, + setHeight: true, + offsetTop: 0, + offsetLeft: 0 + }, arguments[2] || { }); + + source = $(source); + var p = Element.viewportOffset(source); + + element = $(element); + var delta = [0, 0]; + var parent = null; + if (Element.getStyle(element, 'position') == 'absolute') { + parent = Element.getOffsetParent(element); + delta = Element.viewportOffset(parent); + } + + if (parent == document.body) { + delta[0] -= document.body.offsetLeft; + delta[1] -= document.body.offsetTop; + } + + if (options.setLeft) element.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; + if (options.setTop) element.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; + if (options.setWidth) element.style.width = source.offsetWidth + 'px'; + if (options.setHeight) element.style.height = source.offsetHeight + 'px'; + return element; + } +}; + +Object.extend(Element.Methods, { + getElementsBySelector: Element.Methods.select, + + childElements: Element.Methods.immediateDescendants +}); + +Element._attributeTranslations = { + write: { + names: { + className: 'class', + htmlFor: 'for' + }, + values: { } + } +}; + +if (Prototype.Browser.Opera) { + Element.Methods.getStyle = Element.Methods.getStyle.wrap( + function(proceed, element, style) { + switch (style) { + case 'left': case 'top': case 'right': case 'bottom': + if (proceed(element, 'position') === 'static') return null; + case 'height': case 'width': + if (!Element.visible(element)) return null; + + var dim = parseInt(proceed(element, style), 10); + + if (dim !== element['offset' + style.capitalize()]) + return dim + 'px'; + + var properties; + if (style === 'height') { + properties = ['border-top-width', 'padding-top', + 'padding-bottom', 'border-bottom-width']; + } + else { + properties = ['border-left-width', 'padding-left', + 'padding-right', 'border-right-width']; + } + return properties.inject(dim, function(memo, property) { + var val = proceed(element, property); + return val === null ? memo : memo - parseInt(val, 10); + }) + 'px'; + default: return proceed(element, style); + } + } + ); + + Element.Methods.readAttribute = Element.Methods.readAttribute.wrap( + function(proceed, element, attribute) { + if (attribute === 'title') return element.title; + return proceed(element, attribute); + } + ); +} + +else if (Prototype.Browser.IE) { + Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap( + function(proceed, element) { + element = $(element); + try { element.offsetParent } + catch(e) { return $(document.body) } + var position = element.getStyle('position'); + if (position !== 'static') return proceed(element); + element.setStyle({ position: 'relative' }); + var value = proceed(element); + element.setStyle({ position: position }); + return value; + } + ); + + $w('positionedOffset viewportOffset').each(function(method) { + Element.Methods[method] = Element.Methods[method].wrap( + function(proceed, element) { + element = $(element); + try { element.offsetParent } + catch(e) { return Element._returnOffset(0,0) } + var position = element.getStyle('position'); + if (position !== 'static') return proceed(element); + var offsetParent = element.getOffsetParent(); + if (offsetParent && offsetParent.getStyle('position') === 'fixed') + offsetParent.setStyle({ zoom: 1 }); + element.setStyle({ position: 'relative' }); + var value = proceed(element); + element.setStyle({ position: position }); + return value; + } + ); + }); + + Element.Methods.cumulativeOffset = Element.Methods.cumulativeOffset.wrap( + function(proceed, element) { + try { element.offsetParent } + catch(e) { return Element._returnOffset(0,0) } + return proceed(element); + } + ); + + Element.Methods.getStyle = function(element, style) { + element = $(element); + style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize(); + var value = element.style[style]; + if (!value && element.currentStyle) value = element.currentStyle[style]; + + if (style == 'opacity') { + if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) + if (value[1]) return parseFloat(value[1]) / 100; + return 1.0; + } + + if (value == 'auto') { + if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none')) + return element['offset' + style.capitalize()] + 'px'; + return null; + } + return value; + }; + + Element.Methods.setOpacity = function(element, value) { + function stripAlpha(filter){ + return filter.replace(/alpha\([^\)]*\)/gi,''); + } + element = $(element); + var currentStyle = element.currentStyle; + if ((currentStyle && !currentStyle.hasLayout) || + (!currentStyle && element.style.zoom == 'normal')) + element.style.zoom = 1; + + var filter = element.getStyle('filter'), style = element.style; + if (value == 1 || value === '') { + (filter = stripAlpha(filter)) ? + style.filter = filter : style.removeAttribute('filter'); + return element; + } else if (value < 0.00001) value = 0; + style.filter = stripAlpha(filter) + + 'alpha(opacity=' + (value * 100) + ')'; + return element; + }; + + Element._attributeTranslations = (function(){ + + var classProp = 'className'; + var forProp = 'for'; + + var el = document.createElement('div'); + + el.setAttribute(classProp, 'x'); + + if (el.className !== 'x') { + el.setAttribute('class', 'x'); + if (el.className === 'x') { + classProp = 'class'; + } + } + el = null; + + el = document.createElement('label'); + el.setAttribute(forProp, 'x'); + if (el.htmlFor !== 'x') { + el.setAttribute('htmlFor', 'x'); + if (el.htmlFor === 'x') { + forProp = 'htmlFor'; + } + } + el = null; + + return { + read: { + names: { + 'class': classProp, + 'className': classProp, + 'for': forProp, + 'htmlFor': forProp + }, + values: { + _getAttr: function(element, attribute) { + return element.getAttribute(attribute); + }, + _getAttr2: function(element, attribute) { + return element.getAttribute(attribute, 2); + }, + _getAttrNode: function(element, attribute) { + var node = element.getAttributeNode(attribute); + return node ? node.value : ""; + }, + _getEv: (function(){ + + var el = document.createElement('div'); + el.onclick = Prototype.emptyFunction; + var value = el.getAttribute('onclick'); + var f; + + if (String(value).indexOf('{') > -1) { + f = function(element, attribute) { + attribute = element.getAttribute(attribute); + if (!attribute) return null; + attribute = attribute.toString(); + attribute = attribute.split('{')[1]; + attribute = attribute.split('}')[0]; + return attribute.strip(); + }; + } + else if (value === '') { + f = function(element, attribute) { + attribute = element.getAttribute(attribute); + if (!attribute) return null; + return attribute.strip(); + }; + } + el = null; + return f; + })(), + _flag: function(element, attribute) { + return $(element).hasAttribute(attribute) ? attribute : null; + }, + style: function(element) { + return element.style.cssText.toLowerCase(); + }, + title: function(element) { + return element.title; + } + } + } + } + })(); + + Element._attributeTranslations.write = { + names: Object.extend({ + cellpadding: 'cellPadding', + cellspacing: 'cellSpacing' + }, Element._attributeTranslations.read.names), + values: { + checked: function(element, value) { + element.checked = !!value; + }, + + style: function(element, value) { + element.style.cssText = value ? value : ''; + } + } + }; + + Element._attributeTranslations.has = {}; + + $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' + + 'encType maxLength readOnly longDesc frameBorder').each(function(attr) { + Element._attributeTranslations.write.names[attr.toLowerCase()] = attr; + Element._attributeTranslations.has[attr.toLowerCase()] = attr; + }); + + (function(v) { + Object.extend(v, { + href: v._getAttr2, + src: v._getAttr2, + type: v._getAttr, + action: v._getAttrNode, + disabled: v._flag, + checked: v._flag, + readonly: v._flag, + multiple: v._flag, + onload: v._getEv, + onunload: v._getEv, + onclick: v._getEv, + ondblclick: v._getEv, + onmousedown: v._getEv, + onmouseup: v._getEv, + onmouseover: v._getEv, + onmousemove: v._getEv, + onmouseout: v._getEv, + onfocus: v._getEv, + onblur: v._getEv, + onkeypress: v._getEv, + onkeydown: v._getEv, + onkeyup: v._getEv, + onsubmit: v._getEv, + onreset: v._getEv, + onselect: v._getEv, + onchange: v._getEv + }); + })(Element._attributeTranslations.read.values); + + if (Prototype.BrowserFeatures.ElementExtensions) { + (function() { + function _descendants(element) { + var nodes = element.getElementsByTagName('*'), results = []; + for (var i = 0, node; node = nodes[i]; i++) + if (node.tagName !== "!") // Filter out comment nodes. + results.push(node); + return results; + } + + Element.Methods.down = function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return element.firstDescendant(); + return Object.isNumber(expression) ? _descendants(element)[expression] : + Element.select(element, expression)[index || 0]; + } + })(); + } + +} + +else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) { + Element.Methods.setOpacity = function(element, value) { + element = $(element); + element.style.opacity = (value == 1) ? 0.999999 : + (value === '') ? '' : (value < 0.00001) ? 0 : value; + return element; + }; +} + +else if (Prototype.Browser.WebKit) { + Element.Methods.setOpacity = function(element, value) { + element = $(element); + element.style.opacity = (value == 1 || value === '') ? '' : + (value < 0.00001) ? 0 : value; + + if (value == 1) + if(element.tagName.toUpperCase() == 'IMG' && element.width) { + element.width++; element.width--; + } else try { + var n = document.createTextNode(' '); + element.appendChild(n); + element.removeChild(n); + } catch (e) { } + + return element; + }; + + Element.Methods.cumulativeOffset = function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == document.body) + if (Element.getStyle(element, 'position') == 'absolute') break; + + element = element.offsetParent; + } while (element); + + return Element._returnOffset(valueL, valueT); + }; +} + +if ('outerHTML' in document.documentElement) { + Element.Methods.replace = function(element, content) { + element = $(element); + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) { + element.parentNode.replaceChild(content, element); + return element; + } + + content = Object.toHTML(content); + var parent = element.parentNode, tagName = parent.tagName.toUpperCase(); + + if (Element._insertionTranslations.tags[tagName]) { + var nextSibling = element.next(); + var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); + parent.removeChild(element); + if (nextSibling) + fragments.each(function(node) { parent.insertBefore(node, nextSibling) }); + else + fragments.each(function(node) { parent.appendChild(node) }); + } + else element.outerHTML = content.stripScripts(); + + content.evalScripts.bind(content).defer(); + return element; + }; +} + +Element._returnOffset = function(l, t) { + var result = [l, t]; + result.left = l; + result.top = t; + return result; +}; + +Element._getContentFromAnonymousElement = function(tagName, html) { + var div = new Element('div'), t = Element._insertionTranslations.tags[tagName]; + if (t) { + div.innerHTML = t[0] + html + t[1]; + t[2].times(function() { div = div.firstChild }); + } else div.innerHTML = html; + return $A(div.childNodes); +}; + +Element._insertionTranslations = { + before: function(element, node) { + element.parentNode.insertBefore(node, element); + }, + top: function(element, node) { + element.insertBefore(node, element.firstChild); + }, + bottom: function(element, node) { + element.appendChild(node); + }, + after: function(element, node) { + element.parentNode.insertBefore(node, element.nextSibling); + }, + tags: { + TABLE: ['<table>', '</table>', 1], + TBODY: ['<table><tbody>', '</tbody></table>', 2], + TR: ['<table><tbody><tr>', '</tr></tbody></table>', 3], + TD: ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4], + SELECT: ['<select>', '</select>', 1] + } +}; + +(function() { + var tags = Element._insertionTranslations.tags; + Object.extend(tags, { + THEAD: tags.TBODY, + TFOOT: tags.TBODY, + TH: tags.TD + }); +})(); + +Element.Methods.Simulated = { + hasAttribute: function(element, attribute) { + attribute = Element._attributeTranslations.has[attribute] || attribute; + var node = $(element).getAttributeNode(attribute); + return !!(node && node.specified); + } +}; + +Element.Methods.ByTag = { }; + +Object.extend(Element, Element.Methods); + +(function(div) { + + if (!Prototype.BrowserFeatures.ElementExtensions && div['__proto__']) { + window.HTMLElement = { }; + window.HTMLElement.prototype = div['__proto__']; + Prototype.BrowserFeatures.ElementExtensions = true; + } + + div = null; + +})(document.createElement('div')) + +Element.extend = (function() { + + function checkDeficiency(tagName) { + if (typeof window.Element != 'undefined') { + var proto = window.Element.prototype; + if (proto) { + var id = '_' + (Math.random()+'').slice(2); + var el = document.createElement(tagName); + proto[id] = 'x'; + var isBuggy = (el[id] !== 'x'); + delete proto[id]; + el = null; + return isBuggy; + } + } + return false; + } + + function extendElementWith(element, methods) { + for (var property in methods) { + var value = methods[property]; + if (Object.isFunction(value) && !(property in element)) + element[property] = value.methodize(); + } + } + + var HTMLOBJECTELEMENT_PROTOTYPE_BUGGY = checkDeficiency('object'); + + if (Prototype.BrowserFeatures.SpecificElementExtensions) { + if (HTMLOBJECTELEMENT_PROTOTYPE_BUGGY) { + return function(element) { + if (element && typeof element._extendedByPrototype == 'undefined') { + var t = element.tagName; + if (t && (/^(?:object|applet|embed)$/i.test(t))) { + extendElementWith(element, Element.Methods); + extendElementWith(element, Element.Methods.Simulated); + extendElementWith(element, Element.Methods.ByTag[t.toUpperCase()]); + } + } + return element; + } + } + return Prototype.K; + } + + var Methods = { }, ByTag = Element.Methods.ByTag; + + var extend = Object.extend(function(element) { + if (!element || typeof element._extendedByPrototype != 'undefined' || + element.nodeType != 1 || element == window) return element; + + var methods = Object.clone(Methods), + tagName = element.tagName.toUpperCase(); + + if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]); + + extendElementWith(element, methods); + + element._extendedByPrototype = Prototype.emptyFunction; + return element; + + }, { + refresh: function() { + if (!Prototype.BrowserFeatures.ElementExtensions) { + Object.extend(Methods, Element.Methods); + Object.extend(Methods, Element.Methods.Simulated); + } + } + }); + + extend.refresh(); + return extend; +})(); + +Element.hasAttribute = function(element, attribute) { + if (element.hasAttribute) return element.hasAttribute(attribute); + return Element.Methods.Simulated.hasAttribute(element, attribute); +}; + +Element.addMethods = function(methods) { + var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag; + + if (!methods) { + Object.extend(Form, Form.Methods); + Object.extend(Form.Element, Form.Element.Methods); + Object.extend(Element.Methods.ByTag, { + "FORM": Object.clone(Form.Methods), + "INPUT": Object.clone(Form.Element.Methods), + "SELECT": Object.clone(Form.Element.Methods), + "TEXTAREA": Object.clone(Form.Element.Methods) + }); + } + + if (arguments.length == 2) { + var tagName = methods; + methods = arguments[1]; + } + + if (!tagName) Object.extend(Element.Methods, methods || { }); + else { + if (Object.isArray(tagName)) tagName.each(extend); + else extend(tagName); + } + + function extend(tagName) { + tagName = tagName.toUpperCase(); + if (!Element.Methods.ByTag[tagName]) + Element.Methods.ByTag[tagName] = { }; + Object.extend(Element.Methods.ByTag[tagName], methods); + } + + function copy(methods, destination, onlyIfAbsent) { + onlyIfAbsent = onlyIfAbsent || false; + for (var property in methods) { + var value = methods[property]; + if (!Object.isFunction(value)) continue; + if (!onlyIfAbsent || !(property in destination)) + destination[property] = value.methodize(); + } + } + + function findDOMClass(tagName) { + var klass; + var trans = { + "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph", + "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList", + "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading", + "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote", + "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION": + "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD": + "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR": + "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET": + "FrameSet", "IFRAME": "IFrame" + }; + if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element'; + if (window[klass]) return window[klass]; + klass = 'HTML' + tagName + 'Element'; + if (window[klass]) return window[klass]; + klass = 'HTML' + tagName.capitalize() + 'Element'; + if (window[klass]) return window[klass]; + + var element = document.createElement(tagName); + var proto = element['__proto__'] || element.constructor.prototype; + element = null; + return proto; + } + + var elementPrototype = window.HTMLElement ? HTMLElement.prototype : + Element.prototype; + + if (F.ElementExtensions) { + copy(Element.Methods, elementPrototype); + copy(Element.Methods.Simulated, elementPrototype, true); + } + + if (F.SpecificElementExtensions) { + for (var tag in Element.Methods.ByTag) { + var klass = findDOMClass(tag); + if (Object.isUndefined(klass)) continue; + copy(T[tag], klass.prototype); + } + } + + Object.extend(Element, Element.Methods); + delete Element.ByTag; + + if (Element.extend.refresh) Element.extend.refresh(); + Element.cache = { }; +}; + + +document.viewport = { + + getDimensions: function() { + return { width: this.getWidth(), height: this.getHeight() }; + }, + + getScrollOffsets: function() { + return Element._returnOffset( + window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft, + window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop); + } +}; + +(function(viewport) { + var B = Prototype.Browser, doc = document, element, property = {}; + + function getRootElement() { + if (B.WebKit && !doc.evaluate) + return document; + + if (B.Opera && window.parseFloat(window.opera.version()) < 9.5) + return document.body; + + return document.documentElement; + } + + function define(D) { + if (!element) element = getRootElement(); + + property[D] = 'client' + D; + + viewport['get' + D] = function() { return element[property[D]] }; + return viewport['get' + D](); + } + + viewport.getWidth = define.curry('Width'); + + viewport.getHeight = define.curry('Height'); +})(document.viewport); + + +Element.Storage = { + UID: 1 +}; + +Element.addMethods({ + getStorage: function(element) { + if (!(element = $(element))) return; + + var uid; + if (element === window) { + uid = 0; + } else { + if (typeof element._prototypeUID === "undefined") + element._prototypeUID = [Element.Storage.UID++]; + uid = element._prototypeUID[0]; + } + + if (!Element.Storage[uid]) + Element.Storage[uid] = $H(); + + return Element.Storage[uid]; + }, + + store: function(element, key, value) { + if (!(element = $(element))) return; + + if (arguments.length === 2) { + Element.getStorage(element).update(key); + } else { + Element.getStorage(element).set(key, value); + } + + return element; + }, + + retrieve: function(element, key, defaultValue) { + if (!(element = $(element))) return; + var hash = Element.getStorage(element), value = hash.get(key); + + if (Object.isUndefined(value)) { + hash.set(key, defaultValue); + value = defaultValue; + } + + return value; + }, + + clone: function(element, deep) { + if (!(element = $(element))) return; + var clone = element.cloneNode(deep); + clone._prototypeUID = void 0; + if (deep) { + var descendants = Element.select(clone, '*'), + i = descendants.length; + while (i--) { + descendants[i]._prototypeUID = void 0; + } + } + return Element.extend(clone); + } +}); +/* Portions of the Selector class are derived from Jack Slocum's DomQuery, + * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style + * license. Please see http://www.yui-ext.com/ for more information. */ + +var Selector = Class.create({ + initialize: function(expression) { + this.expression = expression.strip(); + + if (this.shouldUseSelectorsAPI()) { + this.mode = 'selectorsAPI'; + } else if (this.shouldUseXPath()) { + this.mode = 'xpath'; + this.compileXPathMatcher(); + } else { + this.mode = "normal"; + this.compileMatcher(); + } + + }, + + shouldUseXPath: (function() { + + var IS_DESCENDANT_SELECTOR_BUGGY = (function(){ + var isBuggy = false; + if (document.evaluate && window.XPathResult) { + var el = document.createElement('div'); + el.innerHTML = '<ul><li></li></ul><div><ul><li></li></ul></div>'; + + var xpath = ".//*[local-name()='ul' or local-name()='UL']" + + "//*[local-name()='li' or local-name()='LI']"; + + var result = document.evaluate(xpath, el, null, + XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + + isBuggy = (result.snapshotLength !== 2); + el = null; + } + return isBuggy; + })(); + + return function() { + if (!Prototype.BrowserFeatures.XPath) return false; + + var e = this.expression; + + if (Prototype.Browser.WebKit && + (e.include("-of-type") || e.include(":empty"))) + return false; + + if ((/(\[[\w-]*?:|:checked)/).test(e)) + return false; + + if (IS_DESCENDANT_SELECTOR_BUGGY) return false; + + return true; + } + + })(), + + shouldUseSelectorsAPI: function() { + if (!Prototype.BrowserFeatures.SelectorsAPI) return false; + + if (Selector.CASE_INSENSITIVE_CLASS_NAMES) return false; + + if (!Selector._div) Selector._div = new Element('div'); + + try { + Selector._div.querySelector(this.expression); + } catch(e) { + return false; + } + + return true; + }, + + compileMatcher: function() { + var e = this.expression, ps = Selector.patterns, h = Selector.handlers, + c = Selector.criteria, le, p, m, len = ps.length, name; + + if (Selector._cache[e]) { + this.matcher = Selector._cache[e]; + return; + } + + this.matcher = ["this.matcher = function(root) {", + "var r = root, h = Selector.handlers, c = false, n;"]; + + while (e && le != e && (/\S/).test(e)) { + le = e; + for (var i = 0; i<len; i++) { + p = ps[i].re; + name = ps[i].name; + if (m = e.match(p)) { + this.matcher.push(Object.isFunction(c[name]) ? c[name](m) : + new Template(c[name]).evaluate(m)); + e = e.replace(m[0], ''); + break; + } + } + } + + this.matcher.push("return h.unique(n);\n}"); + eval(this.matcher.join('\n')); + Selector._cache[this.expression] = this.matcher; + }, + + compileXPathMatcher: function() { + var e = this.expression, ps = Selector.patterns, + x = Selector.xpath, le, m, len = ps.length, name; + + if (Selector._cache[e]) { + this.xpath = Selector._cache[e]; return; + } + + this.matcher = ['.//*']; + while (e && le != e && (/\S/).test(e)) { + le = e; + for (var i = 0; i<len; i++) { + name = ps[i].name; + if (m = e.match(ps[i].re)) { + this.matcher.push(Object.isFunction(x[name]) ? x[name](m) : + new Template(x[name]).evaluate(m)); + e = e.replace(m[0], ''); + break; + } + } + } + + this.xpath = this.matcher.join(''); + Selector._cache[this.expression] = this.xpath; + }, + + findElements: function(root) { + root = root || document; + var e = this.expression, results; + + switch (this.mode) { + case 'selectorsAPI': + if (root !== document) { + var oldId = root.id, id = $(root).identify(); + id = id.replace(/([\.:])/g, "\\$1"); + e = "#" + id + " " + e; + } + + results = $A(root.querySelectorAll(e)).map(Element.extend); + root.id = oldId; + + return results; + case 'xpath': + return document._getElementsByXPath(this.xpath, root); + default: + return this.matcher(root); + } + }, + + match: function(element) { + this.tokens = []; + + var e = this.expression, ps = Selector.patterns, as = Selector.assertions; + var le, p, m, len = ps.length, name; + + while (e && le !== e && (/\S/).test(e)) { + le = e; + for (var i = 0; i<len; i++) { + p = ps[i].re; + name = ps[i].name; + if (m = e.match(p)) { + if (as[name]) { + this.tokens.push([name, Object.clone(m)]); + e = e.replace(m[0], ''); + } else { + return this.findElements(document).include(element); + } + } + } + } + + var match = true, name, matches; + for (var i = 0, token; token = this.tokens[i]; i++) { + name = token[0], matches = token[1]; + if (!Selector.assertions[name](element, matches)) { + match = false; break; + } + } + + return match; + }, + + toString: function() { + return this.expression; + }, + + inspect: function() { + return "#<Selector:" + this.expression.inspect() + ">"; + } +}); + +if (Prototype.BrowserFeatures.SelectorsAPI && + document.compatMode === 'BackCompat') { + Selector.CASE_INSENSITIVE_CLASS_NAMES = (function(){ + var div = document.createElement('div'), + span = document.createElement('span'); + + div.id = "prototype_test_id"; + span.className = 'Test'; + div.appendChild(span); + var isIgnored = (div.querySelector('#prototype_test_id .test') !== null); + div = span = null; + return isIgnored; + })(); +} + +Object.extend(Selector, { + _cache: { }, + + xpath: { + descendant: "//*", + child: "/*", + adjacent: "/following-sibling::*[1]", + laterSibling: '/following-sibling::*', + tagName: function(m) { + if (m[1] == '*') return ''; + return "[local-name()='" + m[1].toLowerCase() + + "' or local-name()='" + m[1].toUpperCase() + "']"; + }, + className: "[contains(concat(' ', @class, ' '), ' #{1} ')]", + id: "[@id='#{1}']", + attrPresence: function(m) { + m[1] = m[1].toLowerCase(); + return new Template("[@#{1}]").evaluate(m); + }, + attr: function(m) { + m[1] = m[1].toLowerCase(); + m[3] = m[5] || m[6]; + return new Template(Selector.xpath.operators[m[2]]).evaluate(m); + }, + pseudo: function(m) { + var h = Selector.xpath.pseudos[m[1]]; + if (!h) return ''; + if (Object.isFunction(h)) return h(m); + return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m); + }, + operators: { + '=': "[@#{1}='#{3}']", + '!=': "[@#{1}!='#{3}']", + '^=': "[starts-with(@#{1}, '#{3}')]", + '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']", + '*=': "[contains(@#{1}, '#{3}')]", + '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]", + '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]" + }, + pseudos: { + 'first-child': '[not(preceding-sibling::*)]', + 'last-child': '[not(following-sibling::*)]', + 'only-child': '[not(preceding-sibling::* or following-sibling::*)]', + 'empty': "[count(*) = 0 and (count(text()) = 0)]", + 'checked': "[@checked]", + 'disabled': "[(@disabled) and (@type!='hidden')]", + 'enabled': "[not(@disabled) and (@type!='hidden')]", + 'not': function(m) { + var e = m[6], p = Selector.patterns, + x = Selector.xpath, le, v, len = p.length, name; + + var exclusion = []; + while (e && le != e && (/\S/).test(e)) { + le = e; + for (var i = 0; i<len; i++) { + name = p[i].name + if (m = e.match(p[i].re)) { + v = Object.isFunction(x[name]) ? x[name](m) : new Template(x[name]).evaluate(m); + exclusion.push("(" + v.substring(1, v.length - 1) + ")"); + e = e.replace(m[0], ''); + break; + } + } + } + return "[not(" + exclusion.join(" and ") + ")]"; + }, + 'nth-child': function(m) { + return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m); + }, + 'nth-last-child': function(m) { + return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m); + }, + 'nth-of-type': function(m) { + return Selector.xpath.pseudos.nth("position() ", m); + }, + 'nth-last-of-type': function(m) { + return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m); + }, + 'first-of-type': function(m) { + m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m); + }, + 'last-of-type': function(m) { + m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m); + }, + 'only-of-type': function(m) { + var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m); + }, + nth: function(fragment, m) { + var mm, formula = m[6], predicate; + if (formula == 'even') formula = '2n+0'; + if (formula == 'odd') formula = '2n+1'; + if (mm = formula.match(/^(\d+)$/)) // digit only + return '[' + fragment + "= " + mm[1] + ']'; + if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b + if (mm[1] == "-") mm[1] = -1; + var a = mm[1] ? Number(mm[1]) : 1; + var b = mm[2] ? Number(mm[2]) : 0; + predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " + + "((#{fragment} - #{b}) div #{a} >= 0)]"; + return new Template(predicate).evaluate({ + fragment: fragment, a: a, b: b }); + } + } + } + }, + + criteria: { + tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;', + className: 'n = h.className(n, r, "#{1}", c); c = false;', + id: 'n = h.id(n, r, "#{1}", c); c = false;', + attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;', + attr: function(m) { + m[3] = (m[5] || m[6]); + return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m); + }, + pseudo: function(m) { + if (m[6]) m[6] = m[6].replace(/"/g, '\\"'); + return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m); + }, + descendant: 'c = "descendant";', + child: 'c = "child";', + adjacent: 'c = "adjacent";', + laterSibling: 'c = "laterSibling";' + }, + + patterns: [ + { name: 'laterSibling', re: /^\s*~\s*/ }, + { name: 'child', re: /^\s*>\s*/ }, + { name: 'adjacent', re: /^\s*\+\s*/ }, + { name: 'descendant', re: /^\s/ }, + + { name: 'tagName', re: /^\s*(\*|[\w\-]+)(\b|$)?/ }, + { name: 'id', re: /^#([\w\-\*]+)(\b|$)/ }, + { name: 'className', re: /^\.([\w\-\*]+)(\b|$)/ }, + { name: 'pseudo', re: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/ }, + { name: 'attrPresence', re: /^\[((?:[\w-]+:)?[\w-]+)\]/ }, + { name: 'attr', re: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ } + ], + + assertions: { + tagName: function(element, matches) { + return matches[1].toUpperCase() == element.tagName.toUpperCase(); + }, + + className: function(element, matches) { + return Element.hasClassName(element, matches[1]); + }, + + id: function(element, matches) { + return element.id === matches[1]; + }, + + attrPresence: function(element, matches) { + return Element.hasAttribute(element, matches[1]); + }, + + attr: function(element, matches) { + var nodeValue = Element.readAttribute(element, matches[1]); + return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]); + } + }, + + handlers: { + concat: function(a, b) { + for (var i = 0, node; node = b[i]; i++) + a.push(node); + return a; + }, + + mark: function(nodes) { + var _true = Prototype.emptyFunction; + for (var i = 0, node; node = nodes[i]; i++) + node._countedByPrototype = _true; + return nodes; + }, + + unmark: (function(){ + + var PROPERTIES_ATTRIBUTES_MAP = (function(){ + var el = document.createElement('div'), + isBuggy = false, + propName = '_countedByPrototype', + value = 'x' + el[propName] = value; + isBuggy = (el.getAttribute(propName) === value); + el = null; + return isBuggy; + })(); + + return PROPERTIES_ATTRIBUTES_MAP ? + function(nodes) { + for (var i = 0, node; node = nodes[i]; i++) + node.removeAttribute('_countedByPrototype'); + return nodes; + } : + function(nodes) { + for (var i = 0, node; node = nodes[i]; i++) + node._countedByPrototype = void 0; + return nodes; + } + })(), + + index: function(parentNode, reverse, ofType) { + parentNode._countedByPrototype = Prototype.emptyFunction; + if (reverse) { + for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) { + var node = nodes[i]; + if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; + } + } else { + for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++) + if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; + } + }, + + unique: function(nodes) { + if (nodes.length == 0) return nodes; + var results = [], n; + for (var i = 0, l = nodes.length; i < l; i++) + if (typeof (n = nodes[i])._countedByPrototype == 'undefined') { + n._countedByPrototype = Prototype.emptyFunction; + results.push(Element.extend(n)); + } + return Selector.handlers.unmark(results); + }, + + descendant: function(nodes) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + h.concat(results, node.getElementsByTagName('*')); + return results; + }, + + child: function(nodes) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) { + for (var j = 0, child; child = node.childNodes[j]; j++) + if (child.nodeType == 1 && child.tagName != '!') results.push(child); + } + return results; + }, + + adjacent: function(nodes) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + var next = this.nextElementSibling(node); + if (next) results.push(next); + } + return results; + }, + + laterSibling: function(nodes) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + h.concat(results, Element.nextSiblings(node)); + return results; + }, + + nextElementSibling: function(node) { + while (node = node.nextSibling) + if (node.nodeType == 1) return node; + return null; + }, + + previousElementSibling: function(node) { + while (node = node.previousSibling) + if (node.nodeType == 1) return node; + return null; + }, + + tagName: function(nodes, root, tagName, combinator) { + var uTagName = tagName.toUpperCase(); + var results = [], h = Selector.handlers; + if (nodes) { + if (combinator) { + if (combinator == "descendant") { + for (var i = 0, node; node = nodes[i]; i++) + h.concat(results, node.getElementsByTagName(tagName)); + return results; + } else nodes = this[combinator](nodes); + if (tagName == "*") return nodes; + } + for (var i = 0, node; node = nodes[i]; i++) + if (node.tagName.toUpperCase() === uTagName) results.push(node); + return results; + } else return root.getElementsByTagName(tagName); + }, + + id: function(nodes, root, id, combinator) { + var targetNode = $(id), h = Selector.handlers; + + if (root == document) { + if (!targetNode) return []; + if (!nodes) return [targetNode]; + } else { + if (!root.sourceIndex || root.sourceIndex < 1) { + var nodes = root.getElementsByTagName('*'); + for (var j = 0, node; node = nodes[j]; j++) { + if (node.id === id) return [node]; + } + } + } + + if (nodes) { + if (combinator) { + if (combinator == 'child') { + for (var i = 0, node; node = nodes[i]; i++) + if (targetNode.parentNode == node) return [targetNode]; + } else if (combinator == 'descendant') { + for (var i = 0, node; node = nodes[i]; i++) + if (Element.descendantOf(targetNode, node)) return [targetNode]; + } else if (combinator == 'adjacent') { + for (var i = 0, node; node = nodes[i]; i++) + if (Selector.handlers.previousElementSibling(targetNode) == node) + return [targetNode]; + } else nodes = h[combinator](nodes); + } + for (var i = 0, node; node = nodes[i]; i++) + if (node == targetNode) return [targetNode]; + return []; + } + return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : []; + }, + + className: function(nodes, root, className, combinator) { + if (nodes && combinator) nodes = this[combinator](nodes); + return Selector.handlers.byClassName(nodes, root, className); + }, + + byClassName: function(nodes, root, className) { + if (!nodes) nodes = Selector.handlers.descendant([root]); + var needle = ' ' + className + ' '; + for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) { + nodeClassName = node.className; + if (nodeClassName.length == 0) continue; + if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle)) + results.push(node); + } + return results; + }, + + attrPresence: function(nodes, root, attr, combinator) { + if (!nodes) nodes = root.getElementsByTagName("*"); + if (nodes && combinator) nodes = this[combinator](nodes); + var results = []; + for (var i = 0, node; node = nodes[i]; i++) + if (Element.hasAttribute(node, attr)) results.push(node); + return results; + }, + + attr: function(nodes, root, attr, value, operator, combinator) { + if (!nodes) nodes = root.getElementsByTagName("*"); + if (nodes && combinator) nodes = this[combinator](nodes); + var handler = Selector.operators[operator], results = []; + for (var i = 0, node; node = nodes[i]; i++) { + var nodeValue = Element.readAttribute(node, attr); + if (nodeValue === null) continue; + if (handler(nodeValue, value)) results.push(node); + } + return results; + }, + + pseudo: function(nodes, name, value, root, combinator) { + if (nodes && combinator) nodes = this[combinator](nodes); + if (!nodes) nodes = root.getElementsByTagName("*"); + return Selector.pseudos[name](nodes, value, root); + } + }, + + pseudos: { + 'first-child': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + if (Selector.handlers.previousElementSibling(node)) continue; + results.push(node); + } + return results; + }, + 'last-child': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + if (Selector.handlers.nextElementSibling(node)) continue; + results.push(node); + } + return results; + }, + 'only-child': function(nodes, value, root) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!h.previousElementSibling(node) && !h.nextElementSibling(node)) + results.push(node); + return results; + }, + 'nth-child': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root); + }, + 'nth-last-child': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root, true); + }, + 'nth-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root, false, true); + }, + 'nth-last-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root, true, true); + }, + 'first-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, "1", root, false, true); + }, + 'last-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, "1", root, true, true); + }, + 'only-of-type': function(nodes, formula, root) { + var p = Selector.pseudos; + return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root); + }, + + getIndices: function(a, b, total) { + if (a == 0) return b > 0 ? [b] : []; + return $R(1, total).inject([], function(memo, i) { + if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i); + return memo; + }); + }, + + nth: function(nodes, formula, root, reverse, ofType) { + if (nodes.length == 0) return []; + if (formula == 'even') formula = '2n+0'; + if (formula == 'odd') formula = '2n+1'; + var h = Selector.handlers, results = [], indexed = [], m; + h.mark(nodes); + for (var i = 0, node; node = nodes[i]; i++) { + if (!node.parentNode._countedByPrototype) { + h.index(node.parentNode, reverse, ofType); + indexed.push(node.parentNode); + } + } + if (formula.match(/^\d+$/)) { // just a number + formula = Number(formula); + for (var i = 0, node; node = nodes[i]; i++) + if (node.nodeIndex == formula) results.push(node); + } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b + if (m[1] == "-") m[1] = -1; + var a = m[1] ? Number(m[1]) : 1; + var b = m[2] ? Number(m[2]) : 0; + var indices = Selector.pseudos.getIndices(a, b, nodes.length); + for (var i = 0, node, l = indices.length; node = nodes[i]; i++) { + for (var j = 0; j < l; j++) + if (node.nodeIndex == indices[j]) results.push(node); + } + } + h.unmark(nodes); + h.unmark(indexed); + return results; + }, + + 'empty': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + if (node.tagName == '!' || node.firstChild) continue; + results.push(node); + } + return results; + }, + + 'not': function(nodes, selector, root) { + var h = Selector.handlers, selectorType, m; + var exclusions = new Selector(selector).findElements(root); + h.mark(exclusions); + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!node._countedByPrototype) results.push(node); + h.unmark(exclusions); + return results; + }, + + 'enabled': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!node.disabled && (!node.type || node.type !== 'hidden')) + results.push(node); + return results; + }, + + 'disabled': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (node.disabled) results.push(node); + return results; + }, + + 'checked': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (node.checked) results.push(node); + return results; + } + }, + + operators: { + '=': function(nv, v) { return nv == v; }, + '!=': function(nv, v) { return nv != v; }, + '^=': function(nv, v) { return nv == v || nv && nv.startsWith(v); }, + '$=': function(nv, v) { return nv == v || nv && nv.endsWith(v); }, + '*=': function(nv, v) { return nv == v || nv && nv.include(v); }, + '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); }, + '|=': function(nv, v) { return ('-' + (nv || "").toUpperCase() + + '-').include('-' + (v || "").toUpperCase() + '-'); } + }, + + split: function(expression) { + var expressions = []; + expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) { + expressions.push(m[1].strip()); + }); + return expressions; + }, + + matchElements: function(elements, expression) { + var matches = $$(expression), h = Selector.handlers; + h.mark(matches); + for (var i = 0, results = [], element; element = elements[i]; i++) + if (element._countedByPrototype) results.push(element); + h.unmark(matches); + return results; + }, + + findElement: function(elements, expression, index) { + if (Object.isNumber(expression)) { + index = expression; expression = false; + } + return Selector.matchElements(elements, expression || '*')[index || 0]; + }, + + findChildElements: function(element, expressions) { + expressions = Selector.split(expressions.join(',')); + var results = [], h = Selector.handlers; + for (var i = 0, l = expressions.length, selector; i < l; i++) { + selector = new Selector(expressions[i].strip()); + h.concat(results, selector.findElements(element)); + } + return (l > 1) ? h.unique(results) : results; + } +}); + +if (Prototype.Browser.IE) { + Object.extend(Selector.handlers, { + concat: function(a, b) { + for (var i = 0, node; node = b[i]; i++) + if (node.tagName !== "!") a.push(node); + return a; + } + }); +} + +function $$() { + return Selector.findChildElements(document, $A(arguments)); +} + +var Form = { + reset: function(form) { + form = $(form); + form.reset(); + return form; + }, + + serializeElements: function(elements, options) { + if (typeof options != 'object') options = { hash: !!options }; + else if (Object.isUndefined(options.hash)) options.hash = true; + var key, value, submitted = false, submit = options.submit; + + var data = elements.inject({ }, function(result, element) { + if (!element.disabled && element.name) { + key = element.name; value = $(element).getValue(); + if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted && + submit !== false && (!submit || key == submit) && (submitted = true)))) { + if (key in result) { + if (!Object.isArray(result[key])) result[key] = [result[key]]; + result[key].push(value); + } + else result[key] = value; + } + } + return result; + }); + + return options.hash ? data : Object.toQueryString(data); + } +}; + +Form.Methods = { + serialize: function(form, options) { + return Form.serializeElements(Form.getElements(form), options); + }, + + getElements: function(form) { + var elements = $(form).getElementsByTagName('*'), + element, + arr = [ ], + serializers = Form.Element.Serializers; + for (var i = 0; element = elements[i]; i++) { + arr.push(element); + } + return arr.inject([], function(elements, child) { + if (serializers[child.tagName.toLowerCase()]) + elements.push(Element.extend(child)); + return elements; + }) + }, + + getInputs: function(form, typeName, name) { + form = $(form); + var inputs = form.getElementsByTagName('input'); + + if (!typeName && !name) return $A(inputs).map(Element.extend); + + for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) { + var input = inputs[i]; + if ((typeName && input.type != typeName) || (name && input.name != name)) + continue; + matchingInputs.push(Element.extend(input)); + } + + return matchingInputs; + }, + + disable: function(form) { + form = $(form); + Form.getElements(form).invoke('disable'); + return form; + }, + + enable: function(form) { + form = $(form); + Form.getElements(form).invoke('enable'); + return form; + }, + + findFirstElement: function(form) { + var elements = $(form).getElements().findAll(function(element) { + return 'hidden' != element.type && !element.disabled; + }); + var firstByIndex = elements.findAll(function(element) { + return element.hasAttribute('tabIndex') && element.tabIndex >= 0; + }).sortBy(function(element) { return element.tabIndex }).first(); + + return firstByIndex ? firstByIndex : elements.find(function(element) { + return /^(?:input|select|textarea)$/i.test(element.tagName); + }); + }, + + focusFirstElement: function(form) { + form = $(form); + form.findFirstElement().activate(); + return form; + }, + + request: function(form, options) { + form = $(form), options = Object.clone(options || { }); + + var params = options.parameters, action = form.readAttribute('action') || ''; + if (action.blank()) action = window.location.href; + options.parameters = form.serialize(true); + + if (params) { + if (Object.isString(params)) params = params.toQueryParams(); + Object.extend(options.parameters, params); + } + + if (form.hasAttribute('method') && !options.method) + options.method = form.method; + + return new Ajax.Request(action, options); + } +}; + +/*--------------------------------------------------------------------------*/ + + +Form.Element = { + focus: function(element) { + $(element).focus(); + return element; + }, + + select: function(element) { + $(element).select(); + return element; + } +}; + +Form.Element.Methods = { + + serialize: function(element) { + element = $(element); + if (!element.disabled && element.name) { + var value = element.getValue(); + if (value != undefined) { + var pair = { }; + pair[element.name] = value; + return Object.toQueryString(pair); + } + } + return ''; + }, + + getValue: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + return Form.Element.Serializers[method](element); + }, + + setValue: function(element, value) { + element = $(element); + var method = element.tagName.toLowerCase(); + Form.Element.Serializers[method](element, value); + return element; + }, + + clear: function(element) { + $(element).value = ''; + return element; + }, + + present: function(element) { + return $(element).value != ''; + }, + + activate: function(element) { + element = $(element); + try { + element.focus(); + if (element.select && (element.tagName.toLowerCase() != 'input' || + !(/^(?:button|reset|submit)$/i.test(element.type)))) + element.select(); + } catch (e) { } + return element; + }, + + disable: function(element) { + element = $(element); + element.disabled = true; + return element; + }, + + enable: function(element) { + element = $(element); + element.disabled = false; + return element; + } +}; + +/*--------------------------------------------------------------------------*/ + +var Field = Form.Element; + +var $F = Form.Element.Methods.getValue; + +/*--------------------------------------------------------------------------*/ + +Form.Element.Serializers = { + input: function(element, value) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + return Form.Element.Serializers.inputSelector(element, value); + default: + return Form.Element.Serializers.textarea(element, value); + } + }, + + inputSelector: function(element, value) { + if (Object.isUndefined(value)) return element.checked ? element.value : null; + else element.checked = !!value; + }, + + textarea: function(element, value) { + if (Object.isUndefined(value)) return element.value; + else element.value = value; + }, + + select: function(element, value) { + if (Object.isUndefined(value)) + return this[element.type == 'select-one' ? + 'selectOne' : 'selectMany'](element); + else { + var opt, currentValue, single = !Object.isArray(value); + for (var i = 0, length = element.length; i < length; i++) { + opt = element.options[i]; + currentValue = this.optionValue(opt); + if (single) { + if (currentValue == value) { + opt.selected = true; + return; + } + } + else opt.selected = value.include(currentValue); + } + } + }, + + selectOne: function(element) { + var index = element.selectedIndex; + return index >= 0 ? this.optionValue(element.options[index]) : null; + }, + + selectMany: function(element) { + var values, length = element.length; + if (!length) return null; + + for (var i = 0, values = []; i < length; i++) { + var opt = element.options[i]; + if (opt.selected) values.push(this.optionValue(opt)); + } + return values; + }, + + optionValue: function(opt) { + return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text; + } +}; + +/*--------------------------------------------------------------------------*/ + + +Abstract.TimedObserver = Class.create(PeriodicalExecuter, { + initialize: function($super, element, frequency, callback) { + $super(callback, frequency); + this.element = $(element); + this.lastValue = this.getValue(); + }, + + execute: function() { + var value = this.getValue(); + if (Object.isString(this.lastValue) && Object.isString(value) ? + this.lastValue != value : String(this.lastValue) != String(value)) { + this.callback(this.element, value); + this.lastValue = value; + } + } +}); + +Form.Element.Observer = Class.create(Abstract.TimedObserver, { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.Observer = Class.create(Abstract.TimedObserver, { + getValue: function() { + return Form.serialize(this.element); + } +}); + +/*--------------------------------------------------------------------------*/ + +Abstract.EventObserver = Class.create({ + initialize: function(element, callback) { + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + if (this.element.tagName.toLowerCase() == 'form') + this.registerFormCallbacks(); + else + this.registerCallback(this.element); + }, + + onElementEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + }, + + registerFormCallbacks: function() { + Form.getElements(this.element).each(this.registerCallback, this); + }, + + registerCallback: function(element) { + if (element.type) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + Event.observe(element, 'click', this.onElementEvent.bind(this)); + break; + default: + Event.observe(element, 'change', this.onElementEvent.bind(this)); + break; + } + } + } +}); + +Form.Element.EventObserver = Class.create(Abstract.EventObserver, { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.EventObserver = Class.create(Abstract.EventObserver, { + getValue: function() { + return Form.serialize(this.element); + } +}); +(function() { + + var Event = { + KEY_BACKSPACE: 8, + KEY_TAB: 9, + KEY_RETURN: 13, + KEY_ESC: 27, + KEY_LEFT: 37, + KEY_UP: 38, + KEY_RIGHT: 39, + KEY_DOWN: 40, + KEY_DELETE: 46, + KEY_HOME: 36, + KEY_END: 35, + KEY_PAGEUP: 33, + KEY_PAGEDOWN: 34, + KEY_INSERT: 45, + + cache: {} + }; + + var docEl = document.documentElement; + var MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED = 'onmouseenter' in docEl + && 'onmouseleave' in docEl; + + var _isButton; + if (Prototype.Browser.IE) { + var buttonMap = { 0: 1, 1: 4, 2: 2 }; + _isButton = function(event, code) { + return event.button === buttonMap[code]; + }; + } else if (Prototype.Browser.WebKit) { + _isButton = function(event, code) { + switch (code) { + case 0: return event.which == 1 && !event.metaKey; + case 1: return event.which == 1 && event.metaKey; + default: return false; + } + }; + } else { + _isButton = function(event, code) { + return event.which ? (event.which === code + 1) : (event.button === code); + }; + } + + function isLeftClick(event) { return _isButton(event, 0) } + + function isMiddleClick(event) { return _isButton(event, 1) } + + function isRightClick(event) { return _isButton(event, 2) } + + function element(event) { + event = Event.extend(event); + + var node = event.target, type = event.type, + currentTarget = event.currentTarget; + + if (currentTarget && currentTarget.tagName) { + if (type === 'load' || type === 'error' || + (type === 'click' && currentTarget.tagName.toLowerCase() === 'input' + && currentTarget.type === 'radio')) + node = currentTarget; + } + + if (node.nodeType == Node.TEXT_NODE) + node = node.parentNode; + + return Element.extend(node); + } + + function findElement(event, expression) { + var element = Event.element(event); + if (!expression) return element; + var elements = [element].concat(element.ancestors()); + return Selector.findElement(elements, expression, 0); + } + + function pointer(event) { + return { x: pointerX(event), y: pointerY(event) }; + } + + function pointerX(event) { + var docElement = document.documentElement, + body = document.body || { scrollLeft: 0 }; + + return event.pageX || (event.clientX + + (docElement.scrollLeft || body.scrollLeft) - + (docElement.clientLeft || 0)); + } + + function pointerY(event) { + var docElement = document.documentElement, + body = document.body || { scrollTop: 0 }; + + return event.pageY || (event.clientY + + (docElement.scrollTop || body.scrollTop) - + (docElement.clientTop || 0)); + } + + + function stop(event) { + Event.extend(event); + event.preventDefault(); + event.stopPropagation(); + + event.stopped = true; + } + + Event.Methods = { + isLeftClick: isLeftClick, + isMiddleClick: isMiddleClick, + isRightClick: isRightClick, + + element: element, + findElement: findElement, + + pointer: pointer, + pointerX: pointerX, + pointerY: pointerY, + + stop: stop + }; + + + var methods = Object.keys(Event.Methods).inject({ }, function(m, name) { + m[name] = Event.Methods[name].methodize(); + return m; + }); + + if (Prototype.Browser.IE) { + function _relatedTarget(event) { + var element; + switch (event.type) { + case 'mouseover': element = event.fromElement; break; + case 'mouseout': element = event.toElement; break; + default: return null; + } + return Element.extend(element); + } + + Object.extend(methods, { + stopPropagation: function() { this.cancelBubble = true }, + preventDefault: function() { this.returnValue = false }, + inspect: function() { return '[object Event]' } + }); + + Event.extend = function(event, element) { + if (!event) return false; + if (event._extendedByPrototype) return event; + + event._extendedByPrototype = Prototype.emptyFunction; + var pointer = Event.pointer(event); + + Object.extend(event, { + target: event.srcElement || element, + relatedTarget: _relatedTarget(event), + pageX: pointer.x, + pageY: pointer.y + }); + + return Object.extend(event, methods); + }; + } else { + Event.prototype = window.Event.prototype || document.createEvent('HTMLEvents').__proto__; + Object.extend(Event.prototype, methods); + Event.extend = Prototype.K; + } + + function _createResponder(element, eventName, handler) { + var registry = Element.retrieve(element, 'prototype_event_registry'); + + if (Object.isUndefined(registry)) { + CACHE.push(element); + registry = Element.retrieve(element, 'prototype_event_registry', $H()); + } + + var respondersForEvent = registry.get(eventName); + if (Object.isUndefined(respondersForEvent)) { + respondersForEvent = []; + registry.set(eventName, respondersForEvent); + } + + if (respondersForEvent.pluck('handler').include(handler)) return false; + + var responder; + if (eventName.include(":")) { + responder = function(event) { + if (Object.isUndefined(event.eventName)) + return false; + + if (event.eventName !== eventName) + return false; + + Event.extend(event, element); + handler.call(element, event); + }; + } else { + if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED && + (eventName === "mouseenter" || eventName === "mouseleave")) { + if (eventName === "mouseenter" || eventName === "mouseleave") { + responder = function(event) { + Event.extend(event, element); + + var parent = event.relatedTarget; + while (parent && parent !== element) { + try { parent = parent.parentNode; } + catch(e) { parent = element; } + } + + if (parent === element) return; + + handler.call(element, event); + }; + } + } else { + responder = function(event) { + Event.extend(event, element); + handler.call(element, event); + }; + } + } + + responder.handler = handler; + respondersForEvent.push(responder); + return responder; + } + + function _destroyCache() { + for (var i = 0, length = CACHE.length; i < length; i++) { + Event.stopObserving(CACHE[i]); + CACHE[i] = null; + } + } + + var CACHE = []; + + if (Prototype.Browser.IE) + window.attachEvent('onunload', _destroyCache); + + if (Prototype.Browser.WebKit) + window.addEventListener('unload', Prototype.emptyFunction, false); + + + var _getDOMEventName = Prototype.K; + + if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED) { + _getDOMEventName = function(eventName) { + var translations = { mouseenter: "mouseover", mouseleave: "mouseout" }; + return eventName in translations ? translations[eventName] : eventName; + }; + } + + function observe(element, eventName, handler) { + element = $(element); + + var responder = _createResponder(element, eventName, handler); + + if (!responder) return element; + + if (eventName.include(':')) { + if (element.addEventListener) + element.addEventListener("dataavailable", responder, false); + else { + element.attachEvent("ondataavailable", responder); + element.attachEvent("onfilterchange", responder); + } + } else { + var actualEventName = _getDOMEventName(eventName); + + if (element.addEventListener) + element.addEventListener(actualEventName, responder, false); + else + element.attachEvent("on" + actualEventName, responder); + } + + return element; + } + + function stopObserving(element, eventName, handler) { + element = $(element); + + var registry = Element.retrieve(element, 'prototype_event_registry'); + + if (Object.isUndefined(registry)) return element; + + if (eventName && !handler) { + var responders = registry.get(eventName); + + if (Object.isUndefined(responders)) return element; + + responders.each( function(r) { + Element.stopObserving(element, eventName, r.handler); + }); + return element; + } else if (!eventName) { + registry.each( function(pair) { + var eventName = pair.key, responders = pair.value; + + responders.each( function(r) { + Element.stopObserving(element, eventName, r.handler); + }); + }); + return element; + } + + var responders = registry.get(eventName); + + if (!responders) return; + + var responder = responders.find( function(r) { return r.handler === handler; }); + if (!responder) return element; + + var actualEventName = _getDOMEventName(eventName); + + if (eventName.include(':')) { + if (element.removeEventListener) + element.removeEventListener("dataavailable", responder, false); + else { + element.detachEvent("ondataavailable", responder); + element.detachEvent("onfilterchange", responder); + } + } else { + if (element.removeEventListener) + element.removeEventListener(actualEventName, responder, false); + else + element.detachEvent('on' + actualEventName, responder); + } + + registry.set(eventName, responders.without(responder)); + + return element; + } + + function fire(element, eventName, memo, bubble) { + element = $(element); + + if (Object.isUndefined(bubble)) + bubble = true; + + if (element == document && document.createEvent && !element.dispatchEvent) + element = document.documentElement; + + var event; + if (document.createEvent) { + event = document.createEvent('HTMLEvents'); + event.initEvent('dataavailable', true, true); + } else { + event = document.createEventObject(); + event.eventType = bubble ? 'ondataavailable' : 'onfilterchange'; + } + + event.eventName = eventName; + event.memo = memo || { }; + + if (document.createEvent) + element.dispatchEvent(event); + else + element.fireEvent(event.eventType, event); + + return Event.extend(event); + } + + + Object.extend(Event, Event.Methods); + + Object.extend(Event, { + fire: fire, + observe: observe, + stopObserving: stopObserving + }); + + Element.addMethods({ + fire: fire, + + observe: observe, + + stopObserving: stopObserving + }); + + Object.extend(document, { + fire: fire.methodize(), + + observe: observe.methodize(), + + stopObserving: stopObserving.methodize(), + + loaded: false + }); + + if (window.Event) Object.extend(window.Event, Event); + else window.Event = Event; +})(); + +(function() { + /* Support for the DOMContentLoaded event is based on work by Dan Webb, + Matthias Miller, Dean Edwards, John Resig, and Diego Perini. */ + + var timer; + + function fireContentLoadedEvent() { + if (document.loaded) return; + if (timer) window.clearTimeout(timer); + document.loaded = true; + document.fire('dom:loaded'); + } + + function checkReadyState() { + if (document.readyState === 'complete') { + document.stopObserving('readystatechange', checkReadyState); + fireContentLoadedEvent(); + } + } + + function pollDoScroll() { + try { document.documentElement.doScroll('left'); } + catch(e) { + timer = pollDoScroll.defer(); + return; + } + fireContentLoadedEvent(); + } + + if (document.addEventListener) { + document.addEventListener('DOMContentLoaded', fireContentLoadedEvent, false); + } else { + document.observe('readystatechange', checkReadyState); + if (window == top) + timer = pollDoScroll.defer(); + } + + Event.observe(window, 'load', fireContentLoadedEvent); +})(); + +Element.addMethods(); + +/*------------------------------- DEPRECATED -------------------------------*/ + +Hash.toQueryString = Object.toQueryString; + +var Toggle = { display: Element.toggle }; + +Element.Methods.childOf = Element.Methods.descendantOf; + +var Insertion = { + Before: function(element, content) { + return Element.insert(element, {before:content}); + }, + + Top: function(element, content) { + return Element.insert(element, {top:content}); + }, + + Bottom: function(element, content) { + return Element.insert(element, {bottom:content}); + }, + + After: function(element, content) { + return Element.insert(element, {after:content}); + } +}; + +var $continue = new Error('"throw $continue" is deprecated, use "return" instead'); + +var Position = { + includeScrollOffsets: false, + + prepare: function() { + this.deltaX = window.pageXOffset + || document.documentElement.scrollLeft + || document.body.scrollLeft + || 0; + this.deltaY = window.pageYOffset + || document.documentElement.scrollTop + || document.body.scrollTop + || 0; + }, + + within: function(element, x, y) { + if (this.includeScrollOffsets) + return this.withinIncludingScrolloffsets(element, x, y); + this.xcomp = x; + this.ycomp = y; + this.offset = Element.cumulativeOffset(element); + + return (y >= this.offset[1] && + y < this.offset[1] + element.offsetHeight && + x >= this.offset[0] && + x < this.offset[0] + element.offsetWidth); + }, + + withinIncludingScrolloffsets: function(element, x, y) { + var offsetcache = Element.cumulativeScrollOffset(element); + + this.xcomp = x + offsetcache[0] - this.deltaX; + this.ycomp = y + offsetcache[1] - this.deltaY; + this.offset = Element.cumulativeOffset(element); + + return (this.ycomp >= this.offset[1] && + this.ycomp < this.offset[1] + element.offsetHeight && + this.xcomp >= this.offset[0] && + this.xcomp < this.offset[0] + element.offsetWidth); + }, + + overlap: function(mode, element) { + if (!mode) return 0; + if (mode == 'vertical') + return ((this.offset[1] + element.offsetHeight) - this.ycomp) / + element.offsetHeight; + if (mode == 'horizontal') + return ((this.offset[0] + element.offsetWidth) - this.xcomp) / + element.offsetWidth; + }, + + + cumulativeOffset: Element.Methods.cumulativeOffset, + + positionedOffset: Element.Methods.positionedOffset, + + absolutize: function(element) { + Position.prepare(); + return Element.absolutize(element); + }, + + relativize: function(element) { + Position.prepare(); + return Element.relativize(element); + }, + + realOffset: Element.Methods.cumulativeScrollOffset, + + offsetParent: Element.Methods.getOffsetParent, + + page: Element.Methods.viewportOffset, + + clone: function(source, target, options) { + options = options || { }; + return Element.clonePosition(target, source, options); + } +}; + +/*--------------------------------------------------------------------------*/ + +if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){ + function iter(name) { + return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]"; + } + + instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ? + function(element, className) { + className = className.toString().strip(); + var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className); + return cond ? document._getElementsByXPath('.//*' + cond, element) : []; + } : function(element, className) { + className = className.toString().strip(); + var elements = [], classNames = (/\s/.test(className) ? $w(className) : null); + if (!classNames && !className) return elements; + + var nodes = $(element).getElementsByTagName('*'); + className = ' ' + className + ' '; + + for (var i = 0, child, cn; child = nodes[i]; i++) { + if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) || + (classNames && classNames.all(function(name) { + return !name.toString().blank() && cn.include(' ' + name + ' '); + })))) + elements.push(Element.extend(child)); + } + return elements; + }; + + return function(className, parentElement) { + return $(parentElement || document.body).getElementsByClassName(className); + }; +}(Element.Methods); + +/*--------------------------------------------------------------------------*/ + +Element.ClassNames = Class.create(); +Element.ClassNames.prototype = { + initialize: function(element) { + this.element = $(element); + }, + + _each: function(iterator) { + this.element.className.split(/\s+/).select(function(name) { + return name.length > 0; + })._each(iterator); + }, + + set: function(className) { + this.element.className = className; + }, + + add: function(classNameToAdd) { + if (this.include(classNameToAdd)) return; + this.set($A(this).concat(classNameToAdd).join(' ')); + }, + + remove: function(classNameToRemove) { + if (!this.include(classNameToRemove)) return; + this.set($A(this).without(classNameToRemove).join(' ')); + }, + + toString: function() { + return $A(this).join(' '); + } +}; + +Object.extend(Element.ClassNames.prototype, Enumerable); + +/*--------------------------------------------------------------------------*/ diff --git a/cb-tools/java-source/src/main/example/web-socket-js/WebSocketMain.swf b/cb-tools/java-source/src/main/example/web-socket-js/WebSocketMain.swf new file mode 100644 index 0000000000000000000000000000000000000000..8174466912475a494681e9436844f4bf90d909f9 GIT binary patch literal 177114 zcmV(-K-|AWS5peeg9QM1+O)k1U{l5RKi=$ZXrUmexW?@XuhKT@-si&2)}(EgX6XhR zvfM0flAC5}+WLI9Y~li<pn@zGLD>{!Q<QyuA}F$>z@zM-fNTOH$baVCBuz^}-}`<4 z@B2;fEN5oUoH^%n&dj}o4GHYS2?^alPDr?yks$5aHz6V6<-0a95)z*F+w6rpsa(+D zaeD)WDEG{h&S212n2`|<httDZ>6G7*AuK2;$jB6Bh(rSr#ehJgH)w7c;0-+X<g?I$ zlnhw?E?>|^c?CFcwoswqGfzGl>D6Y9b?OWG-O#1YnnAir59tjCGKA?u)RE0vXs7%h zbMRTS&*yeo&A7Xah5-R5WvvaH>&XFjw>jW^I-{Ksw+Xs}Zt_{eMp;OK+)XwJvIRtY zd!VLCFtM$j(P!hPX~wP7t&}Ij=cjBTE8@wHx`M{>O>h^!kj3o^I7$DrA#bgh3e!Wh zmtq>LpEL)ljug={T<<n}9U-%Wd{(B0x>z1~GzZCNMVUfjh7d_Sk7fdwJ)O}>){zpl ze9tCGQ%|y<PLMEf-n?1OXQB1wq92>F<=F)IpS&jH<!rRxNBCYcXmYqbt{`a&la>II zEh?xWEqX|)%r0-js@|p+P6DD%u=~v(QkamMke=`#PQn=;GnJpo7NRk3_UPBl9LQiL zCY|J+<&Ao>g?sZR<C(kr^uDKCa!U6e9A2-x?!E8+3{h5gPHtX)K_T~{zo$JlAYERh zt+q9Do157;Z?al8j{l1L>YmU4=>Er-KW|97JZn=6<J`K<Q<EoNe03*h+MW}ax%+=R zdo^+Qu-A7ae|~!J0@nVe=MQuC9cr1zob$#9%h`Ju4Bf*0@Wa*nxzome|1Epl;@=iB zcgz~w>-eNs-ejJceR2-_hY`z{aX(!9>uK)n4}Sj-d*h8W>zO0IYJL3bZ?E29ymxc% zME1HxXXbFXUY~Y@J8gRFIrg%T4(?{|oV?(D_V!OUz0bijWlcCZYdrg{R}Z|(+c$jQ zH>~s1*Kg&td_H<SW991`r!m+4zF-#T(pSH|!`rxQ<iED8TiD9{W$%n%m~&p;zJtAg z$J%3@AKyCu1#jEN*|{gS&zQy-@y`Ag%%y8jpJQI%x8M|e-1}`4IXjQfp2c1E&a6ee zbF1eJW4t?b+_#KTdk>svY&kpaE9R2l)_%lVH1XWmtZ#N6zRcb_{^EYl#e-k2;hr6_ zat800jqhyWo!Bz-d+vqf%Z{*a&iHZ^XYQgmuQMjTKkOi<8HpPGnCQo7ZXVd&ENE^{ zZEjA)MWco%4#&S{{A3Rwg-g*7KS!ZPxQ0C(m9#dCo14Y0%>!Feqv4|lj%pq?uz7g% zK-3n|j%vm!RDw(K5U3PYwyZ`2uW4qtw4fhm0_9s!z8U44hqfXf&~Iq-Sj-)i;6IqP zR#Y?;aS3TCglZrh%N`q<%uwdo=9Zx-)zXYp=m*7+!6_(#`k_qJ!y`k-HVe>?(L5H? zj|>G#pdT&=*)%_b(u~!u&G^q)C_yRA1<oL5TcJ92HHZ?(pr-j8ueq(qn^TuJEIPhr zBI{<$b>665H>NS?jXyqtvHtj)`@cVOeG7BKugi$z>sn{CuYA0EBkS~p88cZ+=8n70 z+IQ^No6L7_Ox(u(_rj4MF%KVmW#Ha5GbV9QO+N4%@6A)Y+<Q-dHJ-Qk{rPY3rhL8q zN8Y&AoAxlzy*_jubIfNaTez=Jxq6iQ<H6+{cx_8t=5h{y_Tg8Y)w5bZ;Qn-B);!MV z(>~e7{BGMTuQA5$*nEUH=jzHy+yk%8Jixqsq^*^=f9kkH+?JV}w(+jKv3osp!dol$ zv*ur%_ak@Qy4D5UE#un8aNG7?SjS%2yzm!J%Z(Y6nHLW(d2#5gpPplW(>DD(?%MAs zj$s~Jx^O9X`<jj0xu@S<euaDV%E?KL3A;{T;awiO{9D$_g|k2BE?%{AE_>bPw@>p{ zuK096>w{NMHM2k7HR5y5;ho=IW{*63V*%^J%HIz%Rt!Hqo;_pUx|OU+E&txi-gNBS z!o3%_&0~Hwy7_PG*W4J+Uc2JL1<sMLXKiN8KX~Tf+$A#?Tx9)x?D#~+FMBut$ok;J z^S^Lk`F{Cl+(qwwxry=S$!YW0D_Ty><-B&Zc?aXSqYGNte{8<KoO|%ookv(-uULJJ zdF|)RZ?V3<ykR0|=&E7oSo1!avXimy=I`s6pKU$;4R^xtCtqQl{^`OV_P&q*eU>?P z{@H`<H6NdTkJ~cgz#`V-RqGEjTjy@w&uv|?<OX~4$W`mP6V@;Pjk){8q796eD`SuI zj<5e?A>+!dlcSg;f8Dc!*E;Un54=w&k7xYn_{L$(Wi74anDf^hS;3ifX6$t4ln=+u z<;?l^kMB6IzA|DX@53*C9md>n@!fl7f3Rmf_vFIXF^o0izrD!*X8ZQNyzR~Fe`H+# zdgwmpl7pvYA8h|+Dd(MqUwqFz@Z;(!oQZFqlKnJh{3Y(GL#Ot!Z>;>_TlUTQC%14< z&v<JQ=c5sOH!)^U_-GdI{RP{vv5tN9+K-&wn^x`T9zVbM6no^FOUKz8cf3BC`=1HF zr>@#Ptz^@|pMPbI|7`zJ*0e*j_i=~+e0?2v<c4AIa3+tMdXBY!|Fu7OOONea%DH)B zXzIdKyN<E;UK;%g^YXzN$9OGccD=`J+5Fxf*8U~0uVjrGx%yx8C(k;_ns#OAc-DgB z*VZtWzPtSbd(?%`e`gPSf7CVZ*pJ$dF@L*yU<UI-+wn!54~}k{#hG#V(~HdG%YJU= zOc--$JMX>4(_4ARM}9PcJ8I#cv8;D5z4kG4-TNcnVQd@q-VS8t-w)>;*!1&6#?<pS z$MAMd-q6CBv}pZR&iPMwJ+kbx^=+(Izj=ET=j_#;=NOB2UERi+{N2r^ylbN`oo9df z<$If%AB~+fj&t?xMHiTt_RTuSSh4%kXRJ539NoldyYT9Fj8Wf>d5bgak44EN*0s&y z4*hK22JWZ7uh_#JesJkN&aBfTXEV;No$@tv?40Xwa6kKg;Z(-d>pS{>xqj^t_RJqf z4dc#V@yQJCH;1l#%lQ8CPjk2n7f&0;UUX{CA=a)PzrVqn_4}`HaSwj{`J0TEk6ITo zW{=%nH}TiQYk42Nx_cGp$kA7SV7xo})!mGpZA&L|W*t6vhWpF0`TyqpbaT?jtRH62 z`iV3Ct2JZUZ8NX7FhAe0Y(9I+<>hm@Bi1e+!~TB#h|{c%TNdx<tlPBnD(~%6pMJ-E zef-<Y*&~j9vzXKT$JydbhhLk{Xr4T3GIP%RudU_$^z$Ce%9CH5VH|3^F_HQ9q0N(6 z`>u>S#Qovy)#JRG$KD&xo;vN&R>qQf$H#E5?|J)s&ZZUDeBZvcdnn_##W%m=?md6? zRo>L|(@(KZth+j!ab?ZUQS2+TU;m8v)5&2ISzpXPIE6ibT}v}(-suzP8JDJxn8uiL zb<b(`j%7Pu;cfU&^Ahfe`IjI3e&2!{yy36S8^c<2;nIB0N9We8WnS7bb^_<e(F@BC z&pNk}JELXUYVO%dBUdm#ym9k4_UE7O*uz`9^OIrhCCjh9!hC(oroF5yzs@|%*!|h! zee4BWMlWGry0mNu>&n(w)^b1jv}F$at4mwva9)`-W;k>C)@h$|e%rkMH`e6q+pciV z{b%wcM}BDSzv|=R8`)>aop|oem19S-XIz{zl>OfRX{R|qPrvat>*!DGN3j>5J2RKr zwscMlchcU`zcbdJ`o=f)*zkMW=8pM+d+4KU6IqAf9s2`o*4fw3Fb|KtG@bj)#*aVb ztl2n4H}}Ab9jq_*PX3v@Y}R|rn6Ll1>TB-rtIjUs&iL`{Yn)XVx9#Ws{OK<Xc#94k z{gO3v-)n2RTPE%r$C!M0*&5y#!#91;J$_|m&i5P79Ar;u{bUE@+P=AC7<+$z_Y~`g zPey&n9y#-C?RVd;_=x+*E8}KzuU<JcjQj2C(aqf9```VFcW`-28*AnKmKD5-BPPz_ zym{)#1@^j&lO}P#Ty*gZ_Mt7G?BR`B^yMP<xp~`0Fuz_uZ8T%*j_q@K)5a}cz<uM! zTFpnRkMCrUy}s;c#*}MEcQFobdiVKLho`S&{xSOKmz?c?Ect`ovV21`XJ+dpYRwye ze92ofx^*;n&Vm-!;cMqdvi7Wa|4qh`oku1z$1i(*D(BU&-@D2kHSF9z_KNXeZ)VN7 zy6GtI%VnRx&0GA%h~11On=cft|6uVM_J97f?gC@^$LkMsPrSM35AOOYBfj8#cXQ4H z)|g+HE#<bgG!J8cG5N$c)}$ZD@8(S1y5<;X?b$bOus5`h9L-#O;D>G8`MZ%%{CdR9 zIoz}7S54sDSpLOg_QpwTj<fe&IlhVc{`4i=xQ8dM{D6D;z}SiG-!{CzhB<QX#|PM_ z{`1yV)(_)PuHuc`{QD2AAAVVTg1PP8c|C7zx!i5mkC%?{wr;+DopIshkGnZrewx0M zbK{4#E7)7+pIO74wSUXkytjs3U(4IM@10qUkJgTOaK)i<X&=sCbCfk{-;Ey_zpi`Z zB4gt5i=Q)RE!;YrH}urr``$XcVJ&mpyw+dXW4>IshIRO}Uzf9vPJZQ8&cf>}zv7+x z__vMh=5M#Qa&PP#zlD2z?PSG)miIp7Ze6ix9cSKK!wxYH{POz&&g%CM|HxSO?;QuY z8~!-4owa`a&hxBUQ|7+Mo4RbpG|uQrOIvxP-W@iSy=B9WU)evsvFT&h@PoS(7q2>; zGxW;!-?&5f44uu|{ME_Ct6RTb#$2#w*C_6a&Er30ZNBj37|s{tHvh)lG-mBi#+;ES z`hInJ_Xq6BuOC~<oO5;3e8%~!|NiH;Z9l!kIM%vpBxBj<M;0?yt~q>^eRTVgajZ2b zXYXaaaq5i&>?w!eeEF>fKVN1XKYaKz#?bc{ea>03XU`7KduR5Z<4$T_dzd}`lZj(^ zugy9+jy1CNmHF)EsjcsDFMo99Q`U&>n?~_g-&j9^v*X{x7Bbqd&D+2_w{Pkk=7x1E zSMb_a?;6JZ?Bd*AygzmxKf&HVeEK(xH{LmW&(U3@yMOx4jH|38t5z-N96vUCH|v!- z7Z<VK`)b`V)?0Jh)^cZlaC{~=VK^tD!~b#z(oaF;DT+ML5vZ(g$!}-SbM*&<?m2>< z3C|90I*Fdmlfp+1;qt=D(o?wnO~aX=aQ>&u_X7QaFY_>6+pjJ2&!Xp$wTawgxWCrz zvwnf{sZQKpQ2+ByoL_zA{WP2(BI>>f@^$^T9!1ZApR8Yp+b1~hPQ?8U{B>_G?!UF$ z;SG2^!S_%1{u$RdzI^R8Zg;KC4�{?S0&?Rq)c&c-(}SFHOPy4tz`b5gun?zakh{ z@cpxA+wgoCGneChv*N&ec-#cHYY&V&=E|NEnEvMD1{gQ>_!JnI_kJNBxAp13K0I#g zme<|^xhNhVf!nnOhx=i^yx`j~?%NX&;c;8v{No0U`_=28;c*l8Pu>9ITKWxv`F>CR z1boY1n2E;~_<!`_aof<&4CL1M@Y3VBKDZ3z&}=wYbQtG<eBBE3)c=}*`C9$l?)!27 zZ9AJ^#Ov0a{;w>|pCI2e4&-$1>nphZz`uXP!}YD-=EM3V{ANqQ{kL7s_!*Bs<X?>a zAkTgm7Q^~JQI(JTZ5=C9<8cMOZ&YBuhiockL3?kV64MXKe(em5$9i6b+cmEj?7+{~ z&o;K;`86|pOoaIpZ4+QUZXUb`_cvrp%4@j4wtr<G1i3$y@g44`_4{Alke~U-Y+T>m z^V1=qkL*+?^n*6-@MI^@d3G$H5UG#QMe{V>K%>!!`5tngc`@YKPqyIx1#f-(D#-nZ zH@?B+w&jlK1AN^7S_anlwnq=m!g3NU`%w&hTc67Sdk8-AH<<5|%lBjc+Aa@b!Mtze zJb?RYO?+De>w59cCqPf84)EiCRu4V60FOUp=fkh#@mJ4xyp8KuKR;Ux^3E+O0eQ~6 zZzS$_2s`&HoKJWxLjn9e=(r#EoAB;GwgMkT+gQNg)p>{U_-(fSAH#aCobe4_|7QP^ zVm$BWk*`mL_1-o1?=bJ7YhMC>*1obC_q*D9!3X2X_gn)xv2MHve28*vSRY&GzJ3nN zN$~l+Owgwx>xY9q)=hVVy-e8U2YV?R^)IlqgyCahezh0Z;eH2>8*l*Z?4i0Q(4R9S z_|X2?r8OYe;NNPXod2&Pkl*yDo(22dW>(<-1na(^g6EsC>}(32*O2ayFNO70iWzv^ zA=Nj`xW6IPV|Rf){O5icZy@WNmx2D3{#(JG?wNcBkGJ}A{~WO6k9yg`Zf5lxi{&7g zw&!P%S3=W%;KStFi2H5hhx&p%PSg@G-it36;`-J13U-1XG+we|IVC)|VkhuvlG*_` zECW8l<wJ~fT48>teKR0`<+aVA*W=Ht@VG<%apo}C^Fzj2V1Jdz-+*<P_tUG;U*D2O z;HUP|b9g=p+02JP55k$ypWw}Z7K43%l6Vl$vpH{58SZD`pq2lC`ER5qf;<mQ*nbMj z`yK`SKDeU?=&ka_Wmql=S-rLco_)8#i0cyyf9#3t6P6#nip$%`*Z+<AX?u6!cYx!j z{eR$Rvs)$xc{A_n33=8^2H=M1j{@jF=iU(L-`TZmKyIr(QiDESk_>_VTKX=Ac}+R{ zDd@BQ>22U|t|z?%?e6~PhcM6kml&bHwt2lk&n#ma0T+{J>;im#Vbo86V_$w80K7f4 zq!EuZr0KD*K|eQbU5Dqf`msC)*d<k*26FeVwgE1DdCUoV@+b-NY&-GO8d%@`4dX!G z&EGu@_B;B34!0M~aBl%S%6;w%;O$4v$6?$pb3OxoxpY1Y^r;{DInX^Z%?o<E{_$a8 z*SmLq3VLzRmMM6=)uo@`0Dn2A>>ZHL{;YDKb3WP)?5T)1jP`#go(2Az^@X^+^&##J zpkF$d4R~UEa17wdkk>AP{EjEkI51@v;6|H#5D(_r!r%i=w+&;#`c0p@2Gh0XJ>G)( z6ukT>;QElWiji2(%^T<44LG#;#TS6zesA;xd7ij;B8>m_^f7=hi~1H}KHJ_KxCZ2$ zbN(H`*CXm3VE0V32tNm|4b_2Mj-8kYcJ|rCb6}4Z7eU_53#k5pH?KVZCOlV7_d&a+ z32(qUCk{IT_-%L0f_1z$CI{B%Ov5G6lLObwU|ugj_XqHQtYQz?-8|=hz?I7bo`-(d zY+eJnG406HFt5UGJkSTv-9rJt_Dov~>!5jlGT_x~udD+9we!>qAn$U^VR(MKDg<_Q z+FA*?xNuY$>@T$u;vB)X;~B6XD+WRQ(rS49Tae4Noj-t{&b&YZZk(Mp3Hn)>2l(37 z=pG1q@^<Y!+<wTK9g9FO*A5Ed^=RHRmTvz}?K{B#Xx|H9M|mDL@cB?48}!lIQiJOS zcU}4!&u?|c#g%wI1MfP|g}7rLc^&Za+Pdqs{_fj{^KCCT(Ej0h^XD+$1O0yoJ^kcC zh--$7d1Wh%fBoHLkmpOr5a{LMS3R)a*IZQ)AB}q9B+R?M;tb3$Yby)Zed+A)LEdMa zuVQ%(eAaLp>_3?L7WBXKaz3=XtN(Psr?07nF#gmVn*pER`0gd}W2)od0e`9IR>S!G z-yZ~ie$5_$+YQ{?JPmMvP$L88FMIQA(6_N4JE8x!PricjnqCAuO>mxc1FpO=aw)9; z(NzOLpJwwe1O6Qg><2wx83MgbP|bZ3*6X2uV?p264c`Rz^|vQRgIvkcQvpX(295_j z8pCb@yq`7dDBy2m-FeW5oQ1og{WC3Bz@J~X9f0=NXGuZ-^+Y51hgIJ~e3&3eIR*N# z;MY`;Yr^CO;0Ly@s{*^f@v|Mq3-sO!e(nq3vw%at7XBOLbJ;%_o_6P4D1Vr(0eQ}T z!3=ug-~BMG(~Mq9I!+3G3v!yhy%)&8WYMD#A3pWOJK)dk|Df%tSh)e>xQxGXK+h5$ zTm|?z*K!Z|@7a$oh4LBSPJno2-?>AeclR}bezyrHRKUKX_k%Y9kFISx4fynEW;OV+ z$M@`k`4WcD!JZcpd!V1Ui0{CT5;aV~fe$Wx4)R?&^*7M3hiCr+_C8}(0FSqN`Zfow z)7gVh0Iq)g@COjDyfKIgcDL%qXJ8)v|M?jBS@XBFUVm7#74#|L!S`uBegxvZA(tL* z2L3KQG85Ke5w#EGa;Tse$Z6-?N1^?B(gJZ}M%qxoRo#9z;Pw+6VSh2iA^R3^?&;zS zV9$;Dg8~0XPk99NK=#rSkW;r^TIf%B1NJd({IOqv{8O(!1i0k<>^WGc%dCIHJSQGJ z4!CYtIYDmTU4Z{>nTe^u@A^I~fUhka*dGjBylN20<HVFPp#Mwr&V&E`t`7V`Yw^l4 z(C*waE7;{$Vl>$E@(XDY2P~ZPKEwr^1_?m_CWjsYJNRm#58{E`Bfr4>TK>_8j#m^T zVISBg=?3;h{3jFSrv3e0(9a!JPs2Xy<?q{IU6z(70L~5XX@Yq-C%M61Wxp_i@BN=W z1naa>TnX#_%}WqZt={)8>_-HP-+USLXx?KG54V+g!9O%Vye9y1oB!Gzz_<73^FYoA zpMDDXo%Ep=#^wI76qh&4<rg9D68lO4H@|%0Wmuo%>ITrGlTshpdw5(P=+O@0hoGO9 zvxk76H>bmXcgUOfw!!|&GweM0$GqJt7;o$7w_!bg^zH<_l9=ZKf8Qwbp#K9i-UfRg zvVSwkcV+c+AgA>QwBYwD@_T|@L#wBQ+<I3}1^<?p+yHu~9Pkp*&H3;;>@S*LF9kgq zTyz5b*YltJ1ai1`;~4bw`wR3w_R)7906kirdKvoJ^|TrKo%^r|*6+gm8zKM7k{<!b zemntj;gGv3Rsx=n$>D%rFVAMd`s-g_2>$b}hWj9{*zn1Hun*yXG7t8{U$40Udc3W+ z3GDfc(GS7Cdu`w)Sm)=D0G<v!Ht<Qn$%Ah!0zOx+`aAf~_lhbYu6}M10rvIOYk;S1 zB?|$+1fqv+(EeVX2K1tFs{_s}sQq_Cyff=b67-9?#t3qhe*P)g;~GT({Cmd0_W?gO zs>OgqUrl@$aQwZorO=OY?I#e2og94v%HQ~>9^`REy_eo!Zvns2R%7}3G{ocL?({y4 z`U&bsB;<l$lJFJ*t|jgK7X04tjtd~aCo~RNx2JhMVcn{(upoZyF%jgu`q^s}VBGvi z+d$td`vATTTy*7C;Nyou_k&&)4Vnh}yX9}&PtrKP3ix(CaX0wA+|3t(&*!FB!+OY$ zUVwedjG=yrGu#UYgP)kPV>ih4^<*R1-_L@*fEUE4Lty<z1&4sV){K4-@*iI~0ysR# ze;>?e1UCqFBY8F**6s1{z5w|T+UNy4IKSvP#DB-02td1&o;BbHiYLtmJ_|HkV4mM@ zoe6l=I_x8mXXUlGKyUT7gS5Q=H3M*Wy?P_a?PG2T^x&z~sbE))>iggvL$_r+;6P#j zJka+md-nq00nNXFpO=es0ROLlVFP=WHV=gP{?hmZ=--GP^I*O1V#9uS^>;s<1f1UV z@|WNjm-T!eaP+x<9ss|c^1*$u&O0Y<2E5tv0O;+&zil}M{gqsYxJQup3)or0zC{A? zqfc%=1p5>_<0|;wOUi>Mh~0t&c0J{?arHsJ%j@8YDauWny<Cl@hO`EId&yur{Vhd$ z>|2WNv2Q74^`tk*a(ROs!tXaXCd20|=_D=<aG;VMLbZ&4q$*==N-2kf^mDCl7phKH zYGi&t<p<hcdeR#5yMm4FMJZZ8<?xe%0OY#S{fB}scOXgJ7$jlTc6Q|WTaFZ($mR<8 z+~&p<DgC6UbGMc9q&q`?x7iy^xB44>K`PxJFmrW!qJ(U`r;A!E=@0g;%*iZ}poGhg zzquhLl-rFub5Y(zdB_Wi9-T<K8YqaEHk9fml-KTZ@Rd??(5&?OLcwm3)P#a4ornlv z_FQ$yV<G*J$5T(DXN8u7OV5>GN||k>U+J||squq(%t0q#>~bi*K@!Qit2z1nUI$la z_Sz^<vc%<cl74*%EpVVKS|=KntxypX7W#D)hupPvi@*b25C<AP9uldMm8YbsI1<El zfQ{xy@|8Lge|Ln0)jPf^Ud(hfkt8J%@Cpr$KC+w1MCh|jru2H!$}LBNru=NB)@<V_ zRg{fPQR<_kpe5rd6^Ow^Jy{nby;hQ|EXOpwN&{&|wNG@Vy@j1sk>#cw91;sXrNc); zT)ht|Bl&2Tophi~vYL@b_>F!y$3|L0j=Mc(Z^-P{Qf`;EQSNe+uvRvHINB({2OB91 zks&fUKn5kz_K7GX#a;Aqqb5?)ZVtJF?fvw|rK(Py^L=K2fCMe!!Y5cHJ3xAE9IKlO zkSTuBO1kRl>Ohi%3~Kx?2VxneBotNaB2y@DoH3{P{FF6nZ-K||Ja-`Cc@VkTK_-UH z0SSf<wiPV_7cml`yqF0unHZ#KbV>9y(ALW{>Pn3$ks2*1j1(lrI`09!WAil!tw@(1 zx-&0KmyLx~iW@+*9+K^(0ztNq@}ob<Ah;Avx|bj8Ngznt5Ud0#E9DMwtsn%Rlf>2( zNb;M*bbm?6Tq7xDgY9Ls`Eu#uQ_WT@>5J>HJK5kv8cW)wE(aQjYYExyq`w>8yFQ5R z013v{2uZFR^e@F?4v@LoGB1{!KSdm$<Dg|y+EUP8U98z%C`WV}w?}jl5qn<5Kx|PZ zSP5daN(#WGPc+{FaC=hTNNZ%HP?8*_!vNK@-t2bauk;iMIRZ{|Et!bbOiOtkNjL%E zOY0&ed)XpO2Q1J4?<YM}Jt+pEh)Fx;C%ZXFFX>0UB6Cf~qA?oe7}@bkV1Pm}#<8Q( z-F#Yuu#VgnQ<{!tv?8K{bSo@UG7XXRiV3<PDC|JcHDnjYAe)P3fMnIp+79!4gb?)+ z=B;r^nvDALVYh`O8MxQw@S>SYD36Eos*z+=u7C#%rgzXE3IqwaJI-+g?uSA>>9*@p zJJJ?kcMmd7k1uEpkbXJkr*#0=^CW~Gp(L5&rVtV#W+2C_M+%J?=!q~zD^taF)CW(M z{vs3`73#IYMFvuIa)~Hkm`5-9V_nh|cA!bZrHu+?YII7A?5&TDM2qVuZLs24R}iuV zFvw{!3HDb!E3IZpF@;M(Mr*CrkpUl)7ug%Nb|Kt~Dl_zSPqeUIBcVLiY@>%Nbs@t> zx^$N^s?}OE*25qg{_nS0+b&2sXxYdhwzzg@hjVla`==fbjQUo>?Lep!bb68k(c11f z)xPq*VA7<@j2QEyk^BhMFa<<DwEx9<J?Td)Md!Jp0EvQ;o>+fEL9(3myX=j4uJ=Yt z;#QHayvq?YdNd>eM4%5wtXpRG)a@1gR$?~Ra#k(b$hXAAz_-R^z(X~9R}<M2JymE( zzsu|{CA|&=waRi_k?e_+P$G^ZaA~(VyFe7}a!Izv2?Z%jyF^kW)}xMEO-j7g1bTEb zmqaupaG>1~XR-#X)9iJScgFyu1Gsi~A!YLi)nvHT<s}0gn;(095-PjXk0=^-qqC6a z*xjLkGg^X7t~Sv`<9vW?vQi<0SSB~>kYfr{{#xukWL^Y1<Q)KpCP?)~(A*TUT%L)B z9G;2BE3PR34!{(+WxaZuP>COl5Md~2L8`KoTJ(yNRN$}%ih7!S2)Pj;##;4?mb4S~ zGC8A&uQfN~2n3B5E7Flx%5MuKnp_BsNwX)>M9~S%ELympCMt-OCQfgA_{a|V?lgtc zMo%`y<Y~mUbY(Y`i`H<^?ES$M<b%vaz1ih9Tij%_$!ErR0HIVj@IEw0F=0j_s5?|@ zypj5QVl#*m&~|uhIc=}^M%F^nxw=ogEF@+OXhD}ZMD}Pe1=hL)@3F=-GqG%{mqOB@ z-8*>RyP=*Qxg<`Jp_pbguDC}%KtZg#yE^u+Al+{2;f`gubltaOJv|1r?AujKsOf?I zeq27u?((AW6d5Vh#P!j=of5C@Lm2k(WNFB^RUnjz*ASqN^H-hn5#;MMh7?7becH!~ zn_Du(CoV6(zF<qinb>T4Lj$0SY=uz#20h1JvA_V#$7QtY^e~0NIFQq_g^&uQ1R^Q0 z8H{l#NQHDtJ3EB6HWk}Wyg%<lhmYEh_Rz;q;=O98QaZ9C&~U*%v~qM}x;IVGk?ZbG z(9-Cx5_VpW0Oh9A5qx0?LUe4sf)MuYs-_Uv+yS;|SBz20<}!PGm?+p}1G*vv=@Eql zj5`6!&oc#L^ce2CTQHi&EIBC>6_7SA-Cy^P-Jxg!4GBvDDF}$^Bt)286GYZH(ujp@ zX!nD5B*<c5U=&{>9S9;<g<Mf07$l-(<FanJZRF@95z>gaq-Y`lLA#U+VK#A6j#e_r z!zmP>rr@Uv?bBSC7D(yS1|ZctQBaf$k*zlXE(%lih%Z2W^o-9#6J8`3P6l0zu$Dw; z@G!Adpc^D<?&xA*FA<Wc@9vO@F-Vis9Ma@8i;#?CiEfWLNQow3v|cFQ=%kuxo6sJM z{>hdRwgxPO6D)tAo=f?#59Rr2x59Np1i?muL2cxCqhq+=UM9TFQBC1(k80Q^kJ;A~ z1&bX+;qIs;K8vHN_Ut_>s>$Vcn{^ZwUmVD|-SD@ef3WLwr$YMNi9s^F3>(A8sAJna zJ|X&3C*)Wu(r)Kl&0Z-94RBIMTV^s2PI26ULn<ELW+C<GMa-6Gqv_drA-S|;NHj$% z6HSrYL|?5nAoQ7Sd`N`6D9J^8U-U>s56H7^A&)Q7MrrZIFDoA<=*^BODVAg*Vcy%- zX-*-kc<eU75r5>wr9(<K95bm1N#Oq5G{SWc-sXI&5DtN`RNy=f0!@@rM1oE})>veS zSXc<qa0vMD?RtvTC!*<_0;mBS;XV1l9jJvYKjmiHgUs4sa-7Er+-~ymG>_hfChnAt z9Yn!)A_`@}_EO0PAHs9eRuU^fVJA}Wdb|S_yMlovOH?no2->td`9rrk|0|@c*#VNY zF#|$oK*Tf)nPw5wCuI6WOq-Bt6D2xP5O4C>a*`l{{G~9-OEv^y=)`D>WwV$QJxEMI z2?<QeQb}{}zn$PvU7|J8gk|-3nRYwV;b1zQOqc7)+wIjlhqE-w#~1a#;}$-ik}pwe z6*8R(Nj52xRF-pH^&SLAsYuso50B$)v^inp>2m{QoykqX!|8hX4zWBOd*dLL13vdo zq_{q{v;8E&sWwKr`#M(@#;R_+uJo>n0~-a7Qma!JalkLKeaMAye4x}mU(nCRE06X| z=1`E*qhuewzVxr}8M`C~J;nOR#}cH$oP#Y%2!BOS{zcH{ncQ$*%0;m~>YI(@3N|iE zbOq>LUNTC=wtPKlkn0dXqKbG2g%JQy44DbuFzZ9${t$|J5dfLp*r_Q4fe`7B72S<b z*`ga*dIw^6k$&8jnhM4auW*L22P0<$u_K=DvFHfY;fX(sp@8cC&gb}r?dSN6;Ny|o zyiq%f_C|R!;>ZFFGcQPin(*q)?hvW5bG@VkW3|~5NW%MXv!66j9Ipl7!WE>q?JT7E z$arm9Dy+ok$ds?qK=JH;IDt#TPw10lb(=lDB%53BblHOjD#_-Lq!Ob=iO~{HAY?I6 zOtXjY!Lf(Wje1NB)P-RDM4PK#M|x1g<*k>x9IhbO1&7WogmD}fikE{d#3HH<mg8w8 z;b0L``~VJD=t8SA<fYLLt++bmK~vY6ZLS7j7*gGWRP03&mqdfR11t}UK0M|IJo&II zfS5sXhzXeu3;j4KN%@(SAD1;yeir&A(bZ^=#zhab;4G9(3gB6mlJ+1UQaYEz8DzUr zpZI*JlnRshSR#l(nQcXdiRdT6X9GNhowziKb^<7cED;wZT2X}OM+u(Y?ed`~2ZbgE ziiQ3rB1s1QW@|7pDoHL9pUZ}WQI8qzxk!I6x`4oYlt@t$9h4$!zKyWiG<G{0h~=Zg zJ)*r}F8u~7mN)u*IM1c^faS2-a1tp1A0GHrg6!c6X#JE0N9@Q}JY+YI3%|S2nJq3a z&xcBoDRNaL@&g^;rvZG?<t&X-+I7@`Dp*pPuIoxv!KKe{Zta*Z?{%x0aV7BRduvT0 zUBj~EW^<JI4c8sQ3slRC9KhW+7{EkqY*e@RdW0m&h1`%IM<z)W>I_nekV43m2$d+o z55yvtNAC2Rtq{K!V<BwiK){B#KC!rviy#`+K6c0UG|l689)k`=AfEWM{{9M0<P{y_ z7w_uW*l96c^<1|(5LA{kmE}B)#pT&#<Y{~GcAgHL8-`#l@@#m@Y#frLDCtcn%{80Q zNwOcfT@SdD;bPP<^ehG0aLa4SLR8obiRdpz8Mjg*l(9b4r7p)_cH&v$Cw?DCBQYmP zAx(d<%VyE{oGj#Ad*Q<tB8X&a2}Pk1LmmB2<l!#cx7V}rpnba}!Zj#*6Wt+t6Q$Do z$9BK?MA!D*iHb$JY>Ck~J{&l(=z%?M5W@-0*yCMqCpr?b0zrQgoFU;HmK~kC)q}Rb zb;fTU5P}7{elqev6(k2?A9huB2=`LOW<Lt|g0Uu#-0F}!Q7}AAzld3v#q@+y)FHP! zR`Jj+O*>TIrJ}q+2jwCCjj^)FZ)ws65!2&id3<j4b2397mM17mg?H>O>m9i6Ou;i6 zBnE`t4|FN(NF@$oNsL61JPUcVdvBwaL0%;O=(@*9B^T|NL^-(+-C;Y0su@l;3X9!5 ziCBVx;?Cd{A>v8o;=e#l6aD?p@D%@f0aq&1qsN^Aji$lx4e$ePu+gJeRLLtSe=Sl> zRD?F>0sOfLPxDuJjSwXp{Kzqr^bq~;jJ<J`JenC%<e9~zxe`U5Sv-kB_ZQ?v6Y;R- z{;8XY5OqTiC@gmcoM@PScWe+x$kb;t^+Kjz#MEaov<!K2<Rb(Fws;&K;rFYNBMfkG z$&q6W$fUr2)Iep4u=h>D+u$sbfui5lXiFseoJa4-2#|1zzp&CYO%mGX&``}(`&mI0 z4%6FmTt$b1R5*}~!diValj3nTpe=`la)&(LKq{(I#TRr#u26?!L)=5M-i6GYq@CoQ z7d)PljW@1|NWt@Q?ukD$1EAu`B!58>{}NH;dmFyms50p^MzvJWs0C{mW#7@-McHhP zR;FfCKGMsPlxp-co=T?I6GbwPOsCW6`Z%JmNAac#za_x0m{U5G@*o262k|kF94Vnu zSDNGsD9~mJrTBCzfRYd?L_e4ClRmfEN=EbDqIukVByFH-NpBzt?QsJ#yl?1(xHfwI zWPozlW4)y7d+KGna+%JgCR8$$K~o}Ar@TnNzD)Nb=3a`{Yc)DUr+WN#%1e<$ZbRe) znY)pjBmG{pThpPqPqY~JG11cQ0eBcFnz#ciI$4pdlB**fvIbuV9-yK}g-mSHYa}Hy zgQ>`<ls+!X$(IpwB4>cCK$txsJ5NdsD9Diz10*?wI8!Rg&k+lSe2GS_mPrgsbrBD* z6?$@E&7^l}v=WU}W<mt0)<_tX8g;iwnL$=*NQtDyN;RRYN{&+Fwox)I>MfSmsEhjO z4TQm{H>ou`6;TSbMxCsCtki@l?~P<>E=+`&rY^0zCt4BVSfe(9KK;GRe-TTRnVAd! z8dip<AtNej9+H?I&m%isn4Xy~%+1WpWg~fedg4E@4zkF<?v?BcR5<ZzrVptNAMMow zL01sPWGKY&k>+3`(iv<e!4$mplQ_+ICrA3b(We`5a>8#<b~6RS_Hz3DFTXv>HX##A zm1tC29WoF+A+@ozw3o67O&N(&ZBi-qD#9R9JS0;S;!>H(h!!0QMk!MpOcI&Spp+{m zgh7@{m&X-obsB?4qA4{+CY&Ns5IQ|l45LAA%IBcJ1M+!Z`W<->6;Wvt8|8A;hhAAN zOO@#*gcgmDn!t+OMU-k4gc!As73sOO`f+Jydd0?55C|s}M2W0>tcWN@jNKp0qCV6` zsCW8+POp_o`nHp5wWW9hSQJviKqTQMmzmVEik{I_k;b4Tu)21mOLQ_qT9r(vX_dJr z#)C;AD=pO^k}{)AZ@9a?JR(A<jw_Cs6cqP~qiz@MgSQZu$f``TN_tWccG)I6e(%nF zNi}NO16?;kJuBr^58lFY{Aj(}YvoF{Qm>Fndn;s>CONhjgQ-+gA=60+y)3@asMXSi zPm0l>+-#GUkix1G5(AoyR3;~krD&azN|q`KwM6z<mvlP0B#wsh%w1xoL2uGxCBzy+ z=p{;JpN?flwGt~1&q+2|(Y~IGcIbGIYjyeqO^K)w&X*HWDV;#Z1&I_~6+5X;!S(Gq zOccu{<3FZ)j|m%Ok~Ml{&4Xflkbc$%yF*Q^Rbs$wCrxl16ZE54!)r;ze?%xpC(!tG z0u6l+A(6<m1~jRNxtWmWs<ejtyL{!Vh3~rf$i(F?H%5VEFaGEM@Ow(+xq8xHbrKfj zdJwFj5{@gNGFww^jmXEitf+u))&oTZ$f?Bg^j8M4lg}P>HulL{lEvkqkBjft=?TQ3 zvK%WERvZN~6`Ahg0r-DnS&>pLKoo)qg#b}N%`MI38Vy?W2cc4?mUh{w4>U50k@dz6 zbzj_2bmeUu(1Y9?8>AC?uXy92JHJ1!^SIhu$?2~9#=7oI46kVsJMA(|jg~}(3?z7I z(WZw!hfN_H;P6iD@|yjP_ZUj`fZe7Fr9nZb@E`ommI=?ay;vp9>7o>w@x2{Nb&_n8 zP?V3!yRJYj`gW*LN@auzZJloI3&C}?;=v9TS-3`qy*6EYKdzOc273K1Ed`zHZr8Z0 zVFjJ*IyZiRCXjVG^dq-a-nMyHgFkXh<!zdq%wEcSi}-J?ytVJR$}ankjf^%M&~K#j z;m$(6P5tfK3vQ9j?Fjy)hqwuJBDj@*w4J{r|6SDIu6@`1cSZ0gJ#^wfj(}@I`%Yhg zM{g8-T>3jt{7FFETia9z{z4zV%coyqv$0j;HuwM-+7OZ?1)*0!gv3JJaOr7t>Dloj zli)>o<waOZ(Hl^JqKZU2$>LHJo<}lVtYg`kyojptbrKQqjEW)(mW-ZRQC&(xne0rQ zN{m`MFS1m;$dox|w}T3Fqo)QtW&B!9*6H;!ly!c?49BwgxHT#ERV*6^U>pdqdeLsq z6mfqTKvU_jmfJrONrsyVCi<JVULkMvn|V{@CP8<L*=F*?r&cE9e0xSV`zQ)F+08CD zY3mWGq|i3X?xw<h5Jmfkr9c$&lRaZ!;6+P%;Ev-y-@Yg04TOByWs)7r?}|$!M(WW{ z&(@&~aqRMUDvSI}xIOXf5Qo{_9^LaJH72V$1$+NMz+@x6F4BfiG0@i9MBhD$%#zNb z4PPK+M?K?D$6}v*c8_jOB27YQCy9FXT1h07R4;{i^^y)GR`~vlDHSuN<xHuHBUYN^ zTBf|5DHk&3B6hV*r(vOhlI6A3Cn53Sw>D&e<Mz2-9;U;}M%~)FV=I!WCAumt4zYU+ z(?#j|*=Qdm%*x3wKwBd8fA^@Qq6h~5DX|C<=loUT?0fN-6@QgFCk69Clon}psDY|i zClXP*C<Tvw2Z9`yR8RDb6=!Cmzi8W+B8?&mX6KgrtW;#Vk+?){REcG}4kWoKE$j{x zi4C11OwUReqNbRu+-@i?R^go-+OYEw7r0Lz+UCaPvy&wnquQXWiYLlVLD)c)Dh(Y< z^Z1c{PkT{*PmQjKP%EqHZ4$0Yq-%tkDX0oZ+J+9rLXJUcD2-)BT>K`{8@xOAc0yxU zqi`m&^X=)v*I`in8^Y!buQS4avkygF$tL>qXuR7EB%5eOhfI(5FR39q>Pgob;$G9z zCEb-?7kvEZhlY@q#`@vY&;=j$V9dqmS&?skxb*iv_<@v%k6#5xZXaO>fDx%%TwK?^ zV|1B<vwF0xCJaWMj4QR9@Np`B`D}3oc_?7UNtVwl<OZprp9-R+h^5yG(GNef&@UVP za?mdq{qoQ+pIM@1mFP3kPl$dZ^vmMXr(}E@<*}pS>BX8#lfHzlHR-FFY757tw@?j< zCZ)F?C%Ty`gUCunap^3a=0(aQ&w|KP9C=D3PZ`Igv*J%Zag#`w{PrIC?HzLIQu<Lq zKg9GyN<U<5+*KlMCa`ck4e5B1x_KH*QGDuVamCWzBbP!O=xk3}B!OQF`0ZvZiH|?= z1vrb@TKiD!L&0>DiSo*EQ)TR%O&c3uv`c|Ib^_9iPvLCI4IX#oBg2dgc42;YR*Da< z#tH1mRTK&$cTxk5fgtHgjDEDz9e+;Jeub__{IG7y(JS)xzfOs-b5LG<Q*}^`=6ONS zczVEMZqV#U3in9lfT{g(iO^@!2TYa_+LO7wy*pe_Eu`-lKHjmg!<|+9MY-AKCH)U~ zBm()F!8Ox__urwD2jXw$7IwN>*DZE0QE5wzem2iSv*;G2;1dA}+J!%Ak6d-_*Z)O7 z84UTo0vlxwVHilK@6f%(_Jsn@d%IXc7!SWn!tar&P_XAMyx-mRlzEnr1ub%bPdhoh z;He#N9~KIjg@PV~<Ih|Nzr=8~mtgOXN-}sD$r?2FBsibLaKLz;QHVCuYO|V|p20=Y zou3GHgV)eBB`>m;iTD{n31%Vs|3Y5md^ia%Tfsd;tZ9sNHo`%YV>i14BnM5#73@U= zS-ciK^+LbF;k5pRgEIza^c(cT;EclO)B5)->^In!*1!LA{RU^G{~bS`!;i-~aNnh` zzDjK%D)9kkp}^&ZTX=$y-yKtJ(7!-8FheiGBzGVQrRk++g#!WFQjy3XMj1ia6?6*V zwwAyZ5TF=L5DS62MNWiqG{=h^ohB+Z^wDW|y-b2;f_x5SAByJ+mBXiOh&=)R;=Nyg zE`AgWQlk9A=V*yhIwskG-%R0exs&jRa*=B_I9^JVqfkv?jlD+~SQ-T{X1-J?z><9C zMd3>V`eNxbFN$8`(o4*PJ_`k02rF$V(R+vV?NnTdKSqbj_V@GM?I?h2zh|BV;*#L6 z|KPiD)Iy-wmu4W3aWBG*0WqZ;5PfA(C}2FruAy9BW(vPok<5U(6etV^E&gvkr0~1p zTXBT~#Bd6~x6$YDn{5b;Vjo1O@cXp$f&@qtr0_cphTs+TAwqqm(ARcRE(_)E??@Io zBQ6vOvk)bHtrt;dBTCkbIWMI~uDuHAYp8|%Cy_bfm5+#75TpdCGXdS3fR?Jj929gD z^o!gWEflbW2*DZsX>G%I0z4rvTr@zE0-frzhn|9d9qyPSbM4R9dQeP`)Xq-|f=)6@ zjkM7p@_KQ9C~g-3a0qB=3E~hP(;;_*Zvwg-1z?G`p8m+q!PpFuS1%WMLTJtwQjnSc z1blWbz@b2NIH<%zKm_=MbJCWc6S>|bpzMN(DG5L+V?7}#3Lu`%UV%`QD}eh8>A5=k zhP4gaxV#|<zUhWf3Bpd7)fu%kcwvVT6AkwFn9(2-G%Yf6%>hK>k9LWzBYi+@1PdG? zL20M1V@90LmBH-^zF1j=0%xg`P$&A331Ft~j`bU{3YyElvHl}F6#@NSexZN^X2C-3 zlcHCc{p`X1!QLJ(U{xQCbzLCL6bkT`O%PoLWcLW>^a$nz{Sc5n{x^EphSdz9NYH7y z1o*aGp@0{ntsp68B|W3?5i=lOB*kw>h~816c!T1%XL_UE(|}l)1L*VDYy*;V%z&ag z5%7`b+JJy^+v4h?4)G~L|E?vDAqw?BATmHrARU=aP=JjNVN9Tlt^^9Sl}2()7Ys0< z<@A!s#|k2M{;=}JTZLU<j^2<-zuN}4%8}zG1KuZt0`L#WGzE`5Be=WcG^0cHjg8r< z(UbfqX`S>6o@6}PSC8;IqU`t+VYF71z>Pzi`{R`~K;I3d3DIi!=#PB4^g5={b`-Iq z0kqB06auorN|LsKAX`+BU67k6D#%F}h|#=oZzv*ogpt5j;fwn8t#1z6)l+^>09ifB z5HOM$DGWX%nK}4H=HQq3*i&PqCW6V>pe2zk2md#98u{+_e-<P5Pi7R-@=tU}t_btd z4p(CKp&*95i@uWH;RbPH#P=cmivl{I-)*q}xxrre^Eh%mX1~?Rr9%nk3!;Z*5mcm= z4qGt=t4KTALEWAgpq0hyA{fMazEFUoEEoQ=xG@!RQh}6Chc8XzFS}CcTd@NA%kv)e zwO9mv^xd@{5v_>kQ)<aZ0epP`?+EeHGY7t0=K)MBr5yMm;qJJ9H>I%sA0By7)__kV zkmci}F#&zKEwW&Rg1*M~!x5;jk9wIW+uKDhr$i_^_0ZjE4rsz&e3nM~gN;vg?l3}* zqdh!HynjT9Vf4nqqyR}Z3QDo#909%*@rY0vvHSLNh%y9r)W!@0$l%5%kYlqNaLdI& za3=JnSmaDX@gqMn;FfEaccShS<tBQ~5ZReE{h2?xblKS}0Pj+T0DsL9NaqFU11+Ry z_@I%69Ps^WN&w$*2VDpd1oZ70)GFk)r9auJuKh$z;30!f$`+w}N=<nO;5RcgI|9Uy z;C2k9-*b!V(Em_PGJLHZiRXI{8vMfG!Gi}po<3;s^Z)GE|GBgmUwWQ3`0>9n2mhTj z=!Nv>)0hqyn`|JhF)>-;Q+a_GaHQK)g6J8bps$3EP3VvM1#u^A58;<)@ddza^k3k3 zq@S9?plh}@V&kK4M+)fkNddke`4n=S9<-HA?`1&P7ND??M$*Q<^bwZXV(n)xC@9Dx zvvRZZGIR5Vxn_$wixlPL3G?!EGOeOqYk}34Y0EFLagmv$;Zrk((f{@nA(lvG^0)%j zosF~)q|1vG%Honzm0F`M)9DSy@`}o;YMQ{|bk)?lJzmOJ=MMx!_2GubruI(KGP-Lz z+<@do-nnw=+u%%#Sr`$P-azOKSmV*8%+a6;#VKel0rO*5E7Ox{=%NoqQZfb&l2)k+ zl~VHj^BE{4#xD({|D;hes#L1*HzTMnDYhYI3}!sXc#M^vo{@|o)NDZz9(0ibCKX_* z%+?3-)m(i0A_FTJgnp!eHY9<BC{@Yx8zfCcvDl$57ZX(m!d-4q>4~tyQ6()eD=U#T z*!0FsnL}43&#cmm4b>HDq1EHgB=up3z9Oflv_=+ENy0^h&?w87G^pH0QF)E6ve>PZ ztKC*_wcF|vORFo3GtCv%zABL%JqtognOtZqa@IpfWzRhGz#ZNO@{uf*617w!A<Qy~ zj3^0Kdvi28dk$GH%Fp+R-DH-P2usVVic6?!CF)a6l*uIGG9p~rBr8=BwMBdaGbeVc zB<1C*2BnlB>B&+Cxv<z!rWebqDs@iGZ&g-#qq$O-Qz{V`TRf_eN}nArC90%+0*Rtj z)>s~yzR`hZ*=X~WH!5X#mO5Xx$6bSZ@mWOKj<U+~Omk5I^rRAJS4s`FnP?UUSy*Er z!YV2H$thGSLupx9-K1<%8)}7`aw^7gSN!sc|AynP_~jFSiDPAvMMY#5N%VC^dZi^x zS|$?{Wk!O?E-FTnluE?Tvf?zNxKu+1ib9U29CIDzk(TF%-9%-T%5ToqluGTI+@|vE zYI&J7r%Fc{t)7gUaE;4FDV2O8(`hbJ3oCsAw0reeR#T2@N>0_*7wa3#JxX_Vz(tVS zf^2!UrK(I(T}c?Ml~rk;ESoA@C@C)YIZ+QGoyc3FqYS0Bl&+#WOv-KL#pM7wK*qn4 zh9-^2BMDSnbG${uaCK&lJ4>Hc(J0QzBOEF*LC9h{!YAUiL~3`4-C31+&OoR!oL%F0 z<SFDu9%rpCq}D2^(lTk8q)MW7c&ypFlBUdZzet^1k;Nyf$~2;ihH9TCzgi|C8kNRy zgWsL$RcLD}t(H<xUR|)j8fv2IYef=KoiV7+cS*CF$_p~86(LO)A-3`hgc-i9#+urS zii&(qZcrn1WEH3l#^MT_-%wl`He_qGHU3O@fv_@Pn3tic&^Q!L`9|WIXZUv)8Ds)U zqlNSv@ISO6z&BgLl4ZtQEm`LX%cGVYE>M)qjE*u<L8BEdc4YAz6jH)Ww?QhJX*25d zVrg%(qS$S9>l$s9>P)4)xZaXg_6#;eaas*B#0r&!a1_;aRueH{B<{oon9+a5M`t$p z1Y#tta9Y(GLseK+qij;usKP2_Ru!mBC5@E{v8h$mh~=uXKv+@+5-5^|i;)=_n`Big zaTOSu#HrHiggQAgih8S8i~xxSD3vZ8E~4col{KhoezV*z1Y|}&VfB_b*(74UMO2W9 z%*B2ib5Ubsag~*b9c7a2oZ7TpQI0d;TG@zZp)0i;q?wlLYE6Y9Y%I|<<oSwg48|ZC zB1`K@qg++)Eeh!KOQhP6-Pe@nl2n*w<<9J^5?x4<TbZrm6OFz)yRR}sD6iJn2+hSs zD7>Q#YFU=NteW!YWn~9Swc!$bMR|pzwxX)iTB3AS8V#8hbq#r0Zg(&vhffGgTpA>t z+ORlRSDaZVw3L~NswS_Da%X1~VFWv6gt)5}Ml%x=;*ur=C)R4af|NVUs}RB}<)vhk zOsf=Wy;{3Unr1H()s@y)XXjQ_8b}HSZPE&}S0l~|NQ$WfZ9{fRg_}<(Ye;idez8(n zYOq*CT4h1donP-Ssy7x;#-OAqTNtdU$j->oNae_<`Ftg5#!_2NQJT1}Qjt+W_!{~B zEfqqU)R)y{Q|Kw7L06nxo=fIhbh+7Nxml527WS$&cAvhoiho}xvng^@7PA|yr&Oi* z-%O{tiEz_x0tQ7bt+J&Zbyi((QIvyDSsP;>vqmM6f&LK<Dsx2ta;gwGmn({e)~qsz zPSj9e<&g&x<84n;fo;zUw&(8B_7wlC?R7Pg&htRF_x~`H&erP)YoxL!<O~}%QR|JC zwOg<7f3#k`HC$X4ae@swU0bigB5FWEh`Zk6RRxqXcc`i{yFuARIHMR;)Ri1$wg8%i zsMu-w@9e)^nky0I%O%!ag<ruZT&gUaJe(a0Aa7pOl&LB383>oppb!UKW$t`ecD2}- zYxjinjcKy15{24f&nu}khDp0I!yT+BHt6|;AK8Yhh$M1CqDHs1piyP5b1M`jxs^nz zw^~-}O*7X9gf*1fuM|q^TxDVnX|`&^Me_2nu*#WPnT;HAc12ySsMh1G6e}Ile7)Ri z%L$6}<)uwQm#6Gc?7t|>&nJq)USDO<trLl?t}1g`omS?|%FY$%`+_-5l(S6gG-Tz~ zg|ovARjLYAw#*<Y&dF2D^6X_QXQnXEUBD-@v^r6FrN~kxEUU`3OY8E5jSZrzphV&K z5^iH|Zd$O?W!IKdRhBeO9#K(QDGY^$xf;1zRaWZ@XKMNV-PPLq#vDVH*O_LkRF(%D za_d5dhJ07hDk}F|>by-_k6)7I%i!N@aGJwthoVJ$X3C2!Uu+Jztl&76iqcMsW-c?x z%2i?&t!84Cs$7JWrl`Epf}+`~O0ip|Q-)=9oTZS3-B6~K>*`Bw$eN03h^lCaDJ$1e zRh72PYNSaPQBISsq5<h>j#s63go_<f9WA&uL`K0~DT<#_L3xu>rt{f6?j~BfWno1) z>YAKJbCEpDR)iuv#GO@C5UQ#OJIXwAM5}U?Wy%^#q=ZN8sIAL%*1C!c!kJ=8nXIA+ zv1|1rY(kb<T2bt@irh72iYTW!H5O4OK#o*tK#)V#IgmpT6Atu~5^CIAv@@ctOpevO zu~Xz)Q7dcU6LJG#patPDNaZ%SHB0BVdQiM6l837eGChjR=oSsCVkpy#jg~AMa=#SX zQN%#2K^~hW0<MrXt2}@VH&opuqg0Xz6f~%i!$C25k=tXgP&>h}U19^DK!O#a61TNc zEHhVD`_NSDEj2QyN}L}BuB!4Pr_+XbQ_9=LqLk5MK~74h7DtvVZjVtbmOI^H)O!<J zRs;t^S5>9jSCuKwjPM$&5)}k-{Ld%Q4nTxseN~6}Uy1T4STVH6{|;-Q6M*}~DxERR zN6{;+xKb8wsH{dngvLd?D7DjCWNeVt5M^`=t3hIfGDmq)K^B^&StWMBEcpZiOI>W9 z2rw;Pwa}tK@b9rkbR+`*nYZo_qPquoO?9OTZ3W#TGny-Q97+RWj{&AUrs`$wauG{F zE{$Ten#ihP#o{2Pn9x;KTibOvSOvPPCTtO2OFf8JgGB6YBGhOP)Is;q=9|!nYtUZL zQB^B(RLO|?(nf<(ltmb2X%abEt#nH(JQe<&`m%aTt4qs4k-M!Dg%C!M1%DGnstqMt zVNsL0ETpqJ(M~nTR@WdeGU(GJ>bxehoGNQji)!@9GRR_g8X?rkZ6$mntSwVoHMRLV zMX=27DKgh;R5h8|4xP?r7fQlbO{gGCXKy4*T&3;`Nn^Ivq4qaq=Vbfx8uSH{T0<TJ zPGO)b(~@6P+lV#~2uBFB%AgXX9SUA$U8aF3Q)D2h4Wl}7M$@ephxTD;-i|UNnC)}> z15#nG%Vw*ld}U%qW4%pQ8#ILLgc)^pR-&?4P9SGoqA979S&?UO5NU2w*o1hn=9>$I z**<3_p{*&^laxG7B6Au`t(Cs|qF`QeFqEaQ&yi-7`trzdwp^4(DphW`y42z*E65Rx zgj#-1fwZ=~!mSjO1=)?l+RW?%XR%f6mfFSTp#r%q<jiXH<Z3dD)CD4S1!c>31<+2P ze_v-9*7`}0E98j)vG)HAh`Yv4RajXT#ZF(B*lAQ0yDb%Np#=dp8oW*=aTH~DQdmp8 z!djyZj2%rM@UsNtr?2Es@w2H*{JafzbqKn;#4fqGvARO&L)u=ENqd1Bf{Gs(oIqBb z6R4zH*eZk_EDr@c27;7_Gjr56Xf%vr7{8=qGb(FD`9@U}{uy)B2DiDZ1q7@ApO!G{ zO0m`dzd2Kc;q+<L|Ev*64xM!3zf}o70si1Gg^SYS>Yz7UoN3U8s=}fC5=Ff%vm6DU zVp|4T+)%Ak*UEI3qHtcGJ-f`VD<N~LgAz}DjzeWtdxBZ2TzyH7CR3`+aw}azsl`&A zpHVCm*(#LCj|7DxgI#IPD9W!<c>N71l9d}A*0Q{YYDuOHc?6@M%&zkntL*u9LLttt z4&;{=m$`D&LfJ&F5qsonmsKY>gz73XN~*F9)sjH4PHBsTi&TRuEHe;Vyj}_&^6vJ0 znb?4I9^qMZ)_mgDV6s-^vRgAtYSfLTqRc#@q@*!alxA))RC}b(Kt5%W3oAmF0<}$> zU8hx|@Y#}G?yU)C`$MF%G9ayU<P*6rqS$ZGZzwCb`0JI<^13RA)|*l9EwgFu+BB7< zQm1axdDH638%TrL$$zG#vXsb1K|`9*Qmd(R*5??CtBt{+utYAdmTDcPMWj9;GN)D4 z=nKk3VY@WfndVoeNrfd%MaBI4J7bvKPvU>#!yN%J-T%)ZRu)#q&L)Cgs;tgi>_+D7 zG~-|pn|~AG%kOOe3PpSnSdHwzJXlrX4s;m^mI9EO{}jkF4gUp@)v6@QD3DpY4#H@* zknteK=s+wGE-C9Ah&99qVjiof9GRcoXOZ1ve)MtdUkk=;vJSx*a=MXl?0*f$_(U`u z`(J}Gy!XtG96sL~j1@}>S1cG4b{UKz^NUGG0$}D>NoqTTnKd5Fs$;s;0ImT!l}=&V zp9N*YB5$RzRAQ`_qwSf|jsRUMFB0W?OLPIHj<oAD)iqfGeO9*7ZPPn5HMPn>RU>J4 zR|ZS-va+k44WgplI;|~a&8?^w`z7+C92Cy_6-}O+G^bjcC9{`FT^^!Dqad}G9JjKX z^pk#~(rfXHbo!dgikw_K(dgC-%QM8zTAw{9*IXPduj3P;CP%HLsIICZPhqBVT)GU@ zB&XajQ<T>JrJ$^HFj^+3gEFZiuednPP+uc-*Ohy#Ty?eivP@l-T3laKA+D-2hdm*o zQdmjUXpuf>LzLcUE>VbzCCcn@r8GmITU{Kk^vkTitQ=8QWw1C<rI1^xx+W`0YP}YR zSfR)>s!KEuiI>zPjmpqzGkrmKe!yO1t!z|zB-KTPwSX`d734_ks#Na$kl9+=P+5{u zXZE`sHlZurWHVOj{5jehU6Yq|@i`70mZe4@E$+pTQX%_4-4!bggeCUkMc%a|R~D&j z{>+(|-qx4vYQyr_&RE<IjP37g5PVdHi-~sl=ttv+tqCJkb)_;`g^3lNwjW4B0D`h6 z1cKhib`Ugl4T25Q|J&VgX1FNaz8ltcy&G;6*Hrz<ju^oQ+7sUfjH^{*YZQ#j+QGQ} zEgo8Z4^ml4S!cMYv_>RcS^;p?)X9Uy4(v5j0?C1nAR~u<II65sHPDBC#+-_puB8_* zI}9KHz4YQ`hal{KkY2p(5RCp0(u<cJn#Es~o})Uy^ZQLPesjeq995Y}FN=x^3Pn+B zO|7vgPnjXC_U8l&Y{3wvGAqgzRGnWUqRQMEB{i8kp~CF67#+4^eUZXZAmkH`WUaEw zZY;7G#38w^R+3v@N>l}dYEN~Z%j{^7X;fKh^&xewq*5x+@XP%rma2Na)q!F(tE^Zj zspJ!-X=ItjN>F7!Z>2$|CBqp;r=?tPue7SGvaQluQF$mFQa5Ij{~vE}*5fwUt%=<v z7cC$_CM0N}5qtsqAUROyh~iA3zYESY#d*3DNr|&4ilR9D`jD!@WxM>^eto(Pxa^`N zid<a#S<f0bfrE!m-iNtp?VIH;o>@7*SUBlSnkxB>#w6?tu7km;<dZ#qd*AGO->XTX zcGt!AnXxJ!7n6oH9%EbVP;|Gj<q3)JqoZ@u(wLvvz~nQ&%S63eTvKQ3>PKF=5BIzl zuKWBBigt;f79U0Ho0*Y--ov%AKg=fi2=jV-l*2yc=@bJ_8YxTHqpo>-WLlOH-j*#4 zU7h5T+w>T2{_i$vk?!I|M5#;|ex7)}mL9Uc%X77l+4!Gd0+6o`?^jTJ_;-WaAA34K zh#P+KckNdYe2*K#-?@zMZX$4hQ`=AJqO>CAmHsJG&c$a)`4=elqqyDMWqqCL{$bp1 zAwl<}@7@*vb%mB+JMSx1d!vf!FU0NO@8WjL8U8+1OjN3Sty@^AqVof)m<FKw2XygA zvAZ|fteUi@OWyJ8^Vr?H{_J0ksPtU~;n!#)Pz2zydH6%spTLHrf(D1NvRufXn?#F; zIz*Kj3fP#5ntinh`rc9vjw#!#2cuiQ2w|T<&_%)yZJ%}ueH1e%$s>S28w=S>L5s@F zc`xZoH8tdg8xM>!qnxYYcxm=v#>r8dpOpzJw#Np|E(g%8v|tiT?h&Zwat1-&wdqMC zSV=<wIuV%ow_<l2@@tw%))qZ@Lsg(C6#|Uhw*KDO;rFw2;;EBg!%#3ukaj^Q4mW`Q z?5uSQ=UF1{VrNI##FY|*VE3c>cu-@;o^hugWHyop+@#OZjJEr#-$#j=Q$leSo$v@t z>gJzUa<CTsDR0GY9Du5-o0*#!xVj@&*dyW;8|NXbu)9N_YUUUe+vuTO)7UZ6g(Utj zY}@DO<M>uZ^ej4nKS?=~1M{9aO}N;17meg#)dB{2c&kk@-;cl5vHW9YgCE)6@;jCI zW)nfuH4)0!jKo?A)YNat!fFWy%-?C3v!Dxg_%wZJs3iR6gbAk4?U%31P7Hfj>F&+; zm+!}E>b-`>ctMtTM&Nu_=>so#F{uR$Y7Dzq-DjQ8SH~7ySP;W5zOVFQnN=}f2r>n@ zW8zhH;HRTvlef1OF0t;oM)jVrr0=y>U)S_hU)5KAvicTe`DFbKE;xF8ECxNA);Vyd zdC=teJfJgd3#R6Sh0R=yb@QyE=f(Ls(eQ#F2kkKD`PE@Vc^;2@?My|w62kP(Nae%s zzL<^EoRO5N@ASvOXnUrwx*Ke6$pu-Y(sdEl!;P(fyq)PIt;RFE4^??Ig`$7RMAQd> zi;YQjb~L4MNF2=%ryZw5>4=r|au?6_cc2$kAJUZNRN=`16Jao~DjyHwkt!OVFa4QH zzSV_Q6P9Q32fDCo!iM}*7Ytya-)!w*GEO;+D%HF&9MV|uf~S~ywIk`UArFi=sdfOU zwljJKsXU`+2F@<HyxdvvVzo`6pr~}<8q%>2VY9U!R(JJ%lStPL6-DVPk{cJo7zE?_ z?gK>yEXNl0Q7>=6O?LOdIS{Ai%jZK-N;^j-hgZh6<`drd7XNN5LxKVpo<%NqGIpF> zsJ+TBHZYCI?~=lXvpmgbB#$c&^67>X1iGJ78R0OG1ENa-OTH@E>_VZAR&?2dWqmj~ zl5f`au4EIW$UHPySX^7|ysog_NUCG^d>Uxsrd#br1WJk$L;AFv`!gzf&dt4qghqmM zS9^-sBs17^LXvIv`$yA;`6*@2?IqsNZLIT-=(()FyW!YWHSOYQpH2jV0o#s(VtDhf z%MGrL>i_tEyFU82|MCyAUTfel<>}V_gY3G?zy0f%@~VYrf1*S0t9T)4w8<{3cww{e zR=rBH7|a%1odB!F<z~`dUsKj4&XgQ&SO{kTgtnIN*`@le3riU#+nbK2+ty39!3(8% z#{b?a&3}I3G;S91`)gEE<<UzP#+Lvug$^G`+5H*A1K*(2BsKD6NF+(Jp+&BLjOUMU zNjJ9^Q?AZN`90Ks5y(PuB-WVO)fm1xVzLWz^Ko^rAaf-G1?#Jn^S0m)h9gl*=`uC| ztQ8msV}eKCCjIv+F!qat-s?x6<n$%d^))x;Jg!`bNN?~95JKAW_oZgzB~ta0#8?)a zO+pKG8?{+o=$p2tF^1<crhGs4SC9IYl>NrT{`OffF4G@8?e$@A$e%p!6%6yw(+ncT z49};TvNG8$dSK8aFbe6(bs1{N)M;>O@*rv{5D<uTK<{Z}Gj2`jop3O~n96g$!LV7~ zkbtbH9p`xtY}S;)98I<+LC%n`md96zec?WcA(9UnpGfedPFs_MA2U7#etE(VH*S%n zZel}k4S)}ixJ{<li$l5SJQC>wAGm&!(*ezF#Xzr-cJ^}F$EZ3Q47b^xj!+x&&LYW! z30t&rl*|%E5idR%tvmzrgj2KY@9sCu%e>QEc5#sp*!6G)pgW`=?jwnou$6_~hRm&- z3>K<=iY2!=fN{T56mMi;rDpeIUa9h(Yy*O;yvT;<^93M93v>aQyV&vq!q?>*wmSkg zqKSyD0_YJM-ys38m__c=a=g4~%A2Fh6{Mq5;zu`ERLNj9#%Fv1`t-AP$1Klj`Ly|v zf~Z8U!R4-1^q?^ksc<`&L4~dc$_?3CxO{$qrKvEmOP&ZG0h8%+Hzc7mJ)kbGH{41z zPr76Aqvnl@Q<4nzi8SROc6i8d8}eb^Jm-8P@7x~ZFGNXm2r{Owiu<?3_Mrn(W|*r< zS=k64PXr`RqNfE+Q#&XG@e+g^a==d~9u1+Bo}KG`Wa7H$-$XR=PQiIsoj}mVGCw3Z zjOQBO5wb2P>s{3hNp9~9LBu!YDI{>Q%x`7~MI9V;1%`7*qEql_7ls+vfG}NRX9!WY z(c>x|&!;gxwQ4RaKq&F#ZbKa4WFGiOIiV=jPA=0`&i#aTJacgq?cgRc2%fr&OJ3cJ zWY64~YqfaZ8~@z=KhJ^p$Lb&J1_sBq4e1{*WrB3`Z*Qf7H2IJHLU@Bu>gC@vGBP{f z*=03e6Mjv@E+_;dSt7X88jgCcJY%8-=higY|Nh46)|z*BS?nU~jGv{AS?xl)Rq-jw z<v%64@0pbaXQRdGyDYS1jsM%rwe<@{e3F>A8a)HV7c6>pVHR?}mYZa(vo{&TqB!*x zI}dBJ*CrRgi5=A9iJaq?W5)B?n{?h%wtrtf%Qd_v62BGDg3iegQmD$aJbidp+@*`P z5c%so8z)JFwpm`NP@_J!F6J0_rAc_IW|5Q^<Z1z~>Y=(GV)-IT3z&WNBvzZOMW+j* zW-M_VMxkT(fQl()b;y}B)$g<2uIGuZa?j?oCJa(t<L85k3K>qS_iS>EOYBw4v#>6R zWS_vi!C=}Z8JTR0o;1WUBne9~H6SUD`_s+1$Hwxc(S^z`vRc-Bx>`}%qt|pP$@5!w zMQUT==psqYRP&Nx#o557&MF6{-r_@Exp;8x@7;<G`6VyZ*gUXUN3a5(iEXgDL^TJz zv}?RoIBE~2VBZ3AD*;{{Pm|LeW9<U5*^#kdDC%mVDOR)WMIYoBifSx|cn}*>SMBmE zeOU<Rq8+J$uO$QUYJXq;B`0;?yijEm(3e|W2qw$FhIkTjFani2k{o|sEcf}!UCk`D z=~RNQZWRSmS~0Bxs}%5jH<KD3MVal%N6n)X1nkhz3F?L(NC%RE<_l6YYXa_>n*NfO zrrxt(;MqtPtTj}XY$A#a0Hs*olUl|nscYjkbxkkkKxh(3WchnqRb6uyg*^%=+Y2i^ zM0|)kb|GhS3Lx7Os}|dDbSFz{saTU5IDQnwaSXQN0j2YqZOi${iEu9Kq}BlV;@<-h z9?AAXT4kGQYO)ic?dG2;PgIea&K3NikMN}r7)Y$Q#7>^2JVtayKWG?y31KsuvU0Gn z?24lvprHeNt$r2fpwFOb`W%{S%$|Q4n!W;-;sdZO^y^@=kXnRafaQ72cpm!#EI)vS z=@(&Pw;@Xy_sbri*|E5>0&(DOyB*cwZ5EBEt!34cn1dvCriMMvcm(&jT@V_rgQDjT zjQi-Cg?5}+ILGY1C53FxCNQU)+-!X8f+j=j#Ey<*r|?WS@j20@dQu<iagQx@O4<>k ze1VoT*oLl-m}HGvypYI#Zzm6N)FXzOj229%1VOkcRYB+SLs<B-B*oublCP3pmt^>H zNiH;)MN~VUwCWd@B!w>iw{2xr%FL4Cp`B(S$BDrDEmK8bFG=H+mOv9OoMCdf{>C`H z*F8?yBV87?H~+gYN%o|r=sw5q{ctIroQmpvpQ?w0pj<AG5ksyAux`DD+NFRH3g(6! zlQTf)j;{MTbxF5lh+Bru>xti<^ZvTq698DLejm%$UftEk7E?ld*S8TCDA8iNl+8aW zsOp7#8m5qbB5cgKC)mXTGh%UQTJgYb8zEyaX|b>Mi*O#hXrBftS>@S)ip;MrNpST* zuivbweLS<8N_KA^DC;g}{>XfBvlb`p;DB7Wm{m3K?rWrI4W*6BpW1{s_45|bMOmrs zoBlD*vL;`P<KN!!5^4V0U1Mu4`^;7QAKtUA;2P=cNSZYmLnvuw@lEOT(@z!GAJk`Y zh&LH+$Ul|RhFs<JCt_Nz;Tu8yuAJ7p__3s-kHl>G^ztvP+3t%=yZo*e>9|IhUXK1< z?OEvLC;Ia{a(Pxz|D^)`OfJhAenW$z)k0a~SMJm^;z_+aR64n8meJ5p=Sov}=@wnC zHbjkl;W|8Fj~*ehLk-`iQ7lZeZ5D`Fu<CKKm5cKL=`06A^|50ir-jcIt)4;z5qC)h z-K5BHhp-FvL}H610Yn~v$cDI<`(9p<hN=@n5f3{m?D~l1%0@RcYg~8?!nsE#Y=<;M zYNk0r_Efu+dDKWoiFoqS>Vb2k7LA%6O^!t*$U;A}^Vw4z?y+y|%Oqv&RGuS+wyL8Q z=|?QR1f32{e!K8jzz$)#H!XzQkcf{=I_xDCFB(WOnayzzkq6Sn^aGm;5?a(qTH$f& znIw?zAKCS2Uhh@B7lbSv@9JaGsEc;NuC`23Di@^TAYwXjJ|G<E8Oq{pwpUg^NS(>D z^#nuyYcY-eeI~?NBhbrpV9BO9O*(kN_Hc-}Y&ll8dcz4;;@Bz=#ADS_Lhh(yLRIlr zIi>T43>7qL+x>YFuTzI!o+CFYxXAnC=@xo&x5rE`H;5znAQL3?%=w%o$aHzQUM29b zt1gnP8UUVA)xR;-cpL;K->Z1Az4WJ;DIZsH3WatSkdqVKpmuRp-Rj)?_$7S=b4VTz z+Hx1n2qibkml2>hP8g@OiOr$N?bzPEqM}T`uz)i@-@J0Zf%@fwqle>=oS=U3U59ZG zwi_~a?!08qLPYsR<-mGdh)_i~z9flu#uZio;x}UPK7zEfIgHeta^CK0B8IPAFPy=0 z^RK}@X|@f5+Jq>AS3Slh(LCLmIKHZCjA(i06$Y_(jsBvQf&U#1gZMMUApVwN_<>EJ zpy(O4WS>8NViUf(zTdsPU&iB6ofp@pFhJD~Kj3jZt*q<i=-<cVpGo~^clB3TIPP(A zCX?`Z9C-08J<co~-ddOoXAik(q+>O-5-mLbN;F4`N|Ca>!VN6YgrA^}Vh%f`y^Y;j zjv7rjt_3Egk0ZoGUK>`fQ=hLgd9a{^w>%3fY;^8i-8^6N@JEB}<ZFYa_?Z<KWR)P4 zL!u<@_1g3y(t;^g*a96a+C=G2mdlsbf@Gd_g${mCax$R~{XH>c?h2ZQ5<4MpGo*BX zNWc#0>*GGW8+UEb@_puHjF(JMdyp505b6&49?P?!4I773%6aB5mFOQYKrnIpsKWbu zt((fuAo>7<&iVxNnx+W6!!|W=GPa95=hBZzhI5U^+#`rG{iTMJ+7-dHyoS7*_vgEi zG@hE3wn8V}FJR%_4i5f_%Ve%G!<gKqgNE1<++5R6AS;=h&$$UuRCo89)Eg&bLxMJm zk7a(!E2P{WxiQ(|tTf^Rx;1J7(tyb^0=|xHm_0m7`(aMg93-593M?e=`C_q+A~Ynd zgmCCNAfq<Y_%op^bgjDp#3qQmg%{=A?)Pj~2U)9>Q&UAQ8cfWYD|e&LD$BD#pzd-5 zW1fV!Ls22J5;KfZC}V#PNjovv!>k5oNbY5POoRunFOmk>WLD=gnptGQ!aYzktPyQd zSPIlxQgQCs3D@D_Ua#2TH1YMkceM_~s}ce_o$2o2L}L~{){dmMk*IC{wWr(!Pqye3 zxyyZ7K{p*26fB5qKi#UFzn^glzOQ*~>}@2*Kc3rg{bT=+ms#=uShr7Hr+IzZ2Vfo5 zLiYFUIU4BmVHsvTaK>A<8}UM4x<0Woe{Yh7RK|H*sl5R%J|Co}xN1>G;z`}wsOJdM zHS)OWWc_?t#<LYzUpdY(SaDG6FNB9{yYj)=Td|$yxV^gHdBW*}gRfWrO>pjST>pk# z@8ECUz<VO**-uiGU-gq57!rQzDS7QD`3rf!H8lUa#J|1XwWs4(uK35*D;x4-{v0vu zU3ApEzyd={5WtW?k>b=tm*z;)SN5<i(&`2r#k6<lR;D9~1D3TX;A0h~(*STsmKRS) z>e?BNub=N+iX1_VA~N4?<#g9(B45~icg9Fy592@xr_yX1i~kCj3wx}~^NEy6@IDHq zRbyIL6~5}H`w0|IC=aPO3R9J)Rc5}$TI~YfzhKd~h0Nk>g$kwWSgwq9amGq@W-91< z?YhO2-J|MmYw<e+yd}9S7Gbj?S#QLRZk{hR#!@rxbR?ng5ZX!}m;Axrt_2RJDS3lN z>oTUfl=O;_q}E~KeU57u)S?gEqi89ocg`b33u;l^cZOjwgFWo=v$M@aL_7i~l-+ki zSM|5TF4}H)kN{F6=>R9Vaq~Y`<R@zz{Kd76tNEWS$LDw&hh}LY+Odsa91kYCJ@1I% z&OOrZH^$R^l{KuwYWbRH%>I4AiyaTzTW|Y>FcHtqYcVQ0i5#^L>TzwgG?q`@17AXZ zaXbyU7Ui<j*W+nK_@(siugBB8UGK|ynqR%*&#rev{?ZlyMFZ8#c$(Lad(dn8g7KOn zg{j*-*8)MawmO8WbX4xHDf{E`ME2*F87w;8=D5rF(*kS(f8U<08KqJjy*+{`!UStK z;f+&mnX~g?Pq#B1W?RzLCunGp(8^5Tyz|ErL#Wa?V2dILwCx`r3ua`UKBKesn0FR9 z6nmGZrP2tCTxa4_6H08%UM}8lk^@rsFmGcanco2SGpr>xk-Te=!MA<?o`#_;1y&9~ zFrx_!I^PNO774wjP}P`jND04kP6vC~jPv2)<Q#=DRY%7>9=A!*fCafXZ`T`EVW|@Y zq_r~|h5+Qe5mFW-P%Yk&H&l<2{78!P1e<~l(Jdn7AP3L1wadl!!t~dfcLc&V^dZ-T z>Lh}dT<*YX0Q>~M1>Y@1=$9Ab^zX3{1sX*tzyPfhxEF<d8g#Y4;Xhf3i(1Zqwv2vr z5zOB&f{$H*K3AXctd~jC0$2@a4ee35DAAe@t!Lz*0Ja{2rl4b~6bO?K$!)fHw$$;~ zks|@07q_qv8?wzcV&6_&Q)}D^iI-V7)$!tvKyi84fHEWrE|==@AoAOQ+2!5B2lvDv z+sASWqDvPx>9jvo*(oL-7x1{tO-?07kUIf-bKy#xcly-Jxa-eVSh=fg{#cl0K-!~W zL&pf)k&T&&^DI;2rOXNZ-uWoBV-|~2MMF}6L)UVG8WVXraQa~1gL)7G%7uB$&xH|) zgdIYVnWyfr+*V@+X$VcGmV9lB*B)tN@qXnHJIaRX%w<2)rG6$Sa=^y<Q8bgwt4J-M zk?W2;N6B<iOYl(lXS*$S3sWsO|MhS3#DDyM|KQhftw4QJMe<bzj9a_Y(Ot8k@cRrB zogvY;1cwS}UEpi8A~Ni<7~DMKt75HoK6?8szLe=FJqjO7CR4?t7Guhy3zWa{p;3`} z^}3u8IoN6X$rY?0;v+~*gLf6bRv}I8W6((>5a~`fdwvb^v;TQHqxaz??@495c7X(3 zK0LgJbIEmjP2JHfI}2#r2Kq&?%aMU~yOqjN{Iu#QjoH6PS#gzV-fnpQL&|EAcaeR% z?shXA-iE8qY!ggn`b?|P)c4nrub&DZaZY7#4`f50YPS>xcMdJ;5!EB2n)pk>-m}*! z{BV}{Z1^GcO=$V_?R}KQVwauq!2GqXev{m{a$D@vZ%Xc4xoybbmE5;-TdeGFO72^^ zt;O}fA-PXD!x{wnZc0xJDD9TZ7cC}i8A!KyQV4RTrAY~o9(kL?XuLk0BJ2}4kk_(w z{cON-#(JK7ZnlJ&AUCzZql~d!D%?}rskcxhO4_R2k*;>Ho!uQdiLG~Ks6Mvp<ypi# z(LD9FAE2@Rph`(NR`ao2fH4YAdoGLGaF^jle&0HaE6$5(T9J>-!P33QWo^1%@8a`B z$P;koN*@MD2r;;uh`O1r{T_jyz0(s4VK<)!mX(ze;Vtqg964fKNvo>Q-i#Z@B{+Tp z>H8z)LREu|13p4W`JU|c0qN`{N~z%xNgmi&8?qNK@lCdqxZfX|bD}@U$VR)YTpWG6 za`-s(wEz$4qt3TutzOs+TTGC*=inO7^&N_D{sS{}F{&%!caRJI7UYVI`0s|2FPOEq zvDCxjX48lQGHNbuvr`#JJ$D}>v3-0VZhXQ0uToHd3;9<@tj&LNQ){^K>)ZNW?B9@o zh5kPU|Cu~3TK)`*lKZvX`7ho5A6U$_8Rz$H=0~}$10eoP9X8~jTFnLhe@k+|TDE_M zQ%dxW4jCH;mx`>K2VFFZ@uKxU8ZMkST_lMiXQwOF4JqV<<>nJidSy3D<@pxT-tAJd zc&0ACMIZEO+M%k<veVQaPo+etrz_c-^g{A=>s~Bc&9n-pdb5Po+fW+Ld*orNCkeTu z%Uy48H9xub)OFTl&jzvAm9DDeTBJL7riKn@D1Iy3w?8Qvc?mmA%A?u=XKvi$)`{rc zlM`IYTFIKN)Ws=L<Qwu5K|Aay)M4K|=(8>Fw{0aOJ}B`tZ#zbxqCTq}8T)q0t`Vdh zBN-NP{$Od_BQ`SRqER>htXj6$&P1*Q5eo5AD8%5^5eu_aqQI;7RdnaL@NiQVPB$zF zJf(yTOd*Gk=0&`0{-=VHu+g!A+Zs~*uYc3qdBZLB-x;^wxRwBgyY#HL6bi9Ib=jgP zNmGNLaIL4spZ<g?{Vk3$edHLQ3`J4>J8Syxcm?E#-FsKHHi@ui=mXa{safv@ov=7j zw6iGE;SG5`04i?A`e9dJwwCV?@s<sC5+2~&+}tU=Hy;imOR`6mCwtF`P5&%_Jhrc_ z5_b+Mup#$QdTy2L-9UJ^09@a{VB8jOOX~1wt2uF0ev6UL!f$XWc9DB|^aQvbfjD=F zI2?XzojFdMBP5=XG`vvWMA{Xslz<*W)KJHP;a#nA;ab4niLCy?xO*}OZF|4PPHw%p zKWM}XCY5P-wUg$#k&PN*JrkG}cfO3H*hXVUp0H)DaA=^y04W{@?1BR3EMITqF;pJ0 zX)}l8I9?G}iniuR-43Yd#`Vcyz%2>p>P$9!`5J8gLnDF7F%*dJv>g6TEjRufX*nZ{ z#r0+pr_MHa*>0psNIIv#pye9<-&@N+e&eSc6vYp=zKyLub1j70pOFH0cSlNqN9r_~ z+FYoD?#i{;_8PO|`2lD=2qlj9X)Y~WHarpEks|dN2Tu(Ru7VbfY(XM0lg=(^9_Ls{ zt{9=^WeVc%3yW<#KE3nUP1(Bs7%k*aU?Q&svm)CP<hTn+!qRQ28XpIlp1Go_nU)9O z0LBOMD*Rl_MNefr{?faod+NIFP05%=BU;F`&^+L_q&+OwIktFFft5JeHMDsgIWLJ@ zk2Rw^vZW4))aWM>l5`h~L;}>pKx(-<>0`KPA=^uLz_f;L&cV?}m_$GJV^ANFy@sRy z1GIKJ6$bjL2-;W|bM4?>@V3D7;wVsIMN@G(-^JS0H~+!LTe6IT>*jxbNr$hA@-N*# zT>P~=RZ854_6?aGDIwyCJn+V6+(+aQQ1P|wLocMl4#_hQ`(K`Y9cDN1?}MkLuRr`U z^mXlYd2yYSHQQ6YH|{88`YeMY_m>>1NawRI`N91zylj-thxVGrX4#~Bjxyz5C`fb( z?lH)I=tJymYX7nAhM&psy*a1+`qNHLJd)=~nYS!BiKx%4_u6jqO8G{OA)j?g>(TYt zugY~D0Q7Uo{*_?YPQ9;0`_m?wFD)|6k?D_XIe3f~FtC-+kH?t9YIWRRMPbLbol?83 zVG%g5n{b`f^EfB-co*d@s^tN?AY)C2$H#R;juT)_XcyZ8DKs`9t193Y&JKnWOLZ2{ z727lYNMq3MP+9nuBVZnKHJd*8r8eWGu$*D<-HNU$QMV}JXbA9}uOhjGxa;16Bk`iv zyAq#zpUBUSrArcA-J>|+QQPDNvsnJOA-jun$oe$Q@9sU)T$sChIU1+(;qaIwO4H-L zWwWq@^4M_F6i=ao2}$671V=nAkDatugrUZK>7YcZ1`}a($5Xx25gl(OThnS9$^^%^ zW#NVN%}-~0r_eVlqAJEpPaHyM_=Y5MuEDmq$+;4{5@GbWCK;ok-g+_<=A}vIrRcpN zlb60onB`K#S@c1;*L>+{72Zk?O%Rj{0Ij~mcTV?9a}-zDGTyW9x3)d+l(RLB#Q9*A z4KKmK<c)eX-iA<?hJ_Q@+4Y{v64orL9-NI9+mJ{^C%PR)#o(H|p&aFGm*oEG#7sz6 z8TLKvdVplMELno&F>mfCvhSm*&r`r+1blC7$g~{F8wNqw$QD;5LxALD1u#@S&|^+I zC>S%59Gq)D42A35)`i&N$qwNIG_k>atzNwe3qF>QdH|D9!45KW>$=CWZjH;ZAChdh zKgQ#sL<(cBAGER%2649Q#R6!0pl4IL+{Go9JWtA?VHZa?{g`YSw7u?44%dBTZ*$eI zz6Te9pM`s3p@r!0SSl6b3Xv2AjC8Qs{7aakG!zyJ*h#ECq3LO-K*wnW*WnxaB`@G% zp~(N|_prL~0uxkJcKEO>LuYfq50GUwyqq|xraG|iruX_4bpEFgFEZc0_{7qFB@OL+ z{klTEKq<w*L5-xqr=-DV*2-)6t@&?M{kO5!uOX**^bu(wbqeuAV_;*`ACPiGE_d|d zRrRY^m)D-Na#SW*5R%<LgL?0ia@gmw6-!@_7L@v7s}6-7lOLIpl|xy}r=QK4?{g*O zukNDw(z>}|^f#}4T~YbIgTm)kL~^7{B)U~<yr~_R9_4N;x>iIJ+Ab3ql)dT7hJ3)u zdAw|S022pai-_Bq!=Qr?2T~xF8M6mC{j`Pd+ZfI}r^zs@jb!T>o?7{&dKtgAi#5va z(ahHlojl1n`PQ{;sc8!cm(wm!w|nw3MAfa}db{Z!T4433dw4!62GxXTzpUB~dAF-m z&e+{UNL5<O*+?!l3$xO*F(USvUI<B-O?S8RASi3-nr+S(yJ9bj#LK3r%*lqN2|t|m zODhe{Ek*?XV)osnJ{nkN00p5H``iy&_5gXm<rXZT0`nmH(r#JySF?hz#|=5R+uQzp zYU$%OeXMh+2x4kI()Lr5nw%S|qjpOTYDP9FJ*7Hb!}#mmi34?6Xyb;g?H7A382HP? z#n-;^AE4k@I9hMld5SI=$3jQ7)Ex=$QgerhA<S}k(9-7_&J>Lg>lA`T2B`!G)lo2k zm}wpgs7(=#To#7#qIdH%=T#$BLKD-vCKO+slzMp=^D=8huvC_3NH1NM44fu@I1Z<Q zZx)?|sv4+s=nPRcuN0pK2cAe>2b`UaV^hIL6FX3H&l*Y#^WBD&fSor)X>36G%t$vJ z%?CWH*~oUfD-q(HU`14as2P%%xO-cQC01WmpbP3wfipNeY)FvUj~aEOT<yy1TgH~# z``x9L{h?Mgk>8?naPgr~=z;x|>n#@<Xa9_NCZn7#?&0{bHe_?(LOH>ZDOJX1Ox}w_ z0V_unaLWrvMXt^zNN)-$lA+nQ-)RUh@fg8xli^IOS}-PSGw6efqRYeN_lQ|=Tl#^n zrC&P?z4xMO8k4(C?6C6-a<0g@P8UCMc6M#$C}uAiy9Li}ssMJb5*Bqmq7<t`O%ZIz zy+ok5Qx)UsNtIIez8ENRY<B`eA6TknAE#4#;%#LOX8dpNvC3Ch?mh42KKtLX4~={R zJKnwJiC>@I8%;g`AM$VR(xL%Evbam#w*?K^;{7qo>R)|HjXHz+J{DyBc+$5t>A1Ly zkJW8le6w%9h}$b)Ds{e|4)m_2Py!_r0<Ir?j(2-$l775m=U1)O|B1`EYgC61LwH_l z@x0OxGvH`s4GCA|s*9gHm{IqkIzG1f{mVVCCtAah&)>?oEb^Cd-?!mmt~)ww*4cOu za%wg?za_#tTj9Q(cd<OLAx}1Scw%ubHRRiGGCl-!P2`(tP)#D3!y(5Xi=-gs^3APV z_^W;XShM)7g!QUEtO+t%ul6~;%;-Oi*<AR4FlqK;u?rPAyzF`OJhmY}9Q{j){YYNR zUHqcNek8BA;QG%c_9J<1$X}J%kL0yZS9+D$ugwJxyg)+pDEIm{cr4czY)GX|9Dxjb z;?QcAGK>{<9&iA~yIZ<1FUJ9py^2g?a#=(<ZxrxuWQ!Vy1tKD@d>(BVyy!3H838(j z+J{FTt+!l0T+CJn=G_QUC7&MC1I*l#iPJdRh-wC6m~BbdOfPwqfiKr!pBHE8&dsiG zX~{)Sg%C&v4cNs$+D76%1V%Nwb6M7fLJfSSZQVfFdZ+QcwITi<szs6z%ENR0M&ITK z%4#w^JzMu<V(qTec<-es850t{DBiNN@UBU1g&~D9z_`-S;|qVxka0SLP$D~(>0LNW z?n2n{WXbD7toJbX;qD0Zs5!AdZ2slqTTpAG>Giw?Ekj-SwW4A^Ps_+fKitlH#aG1d zun}Cb@i*aY`YSm5$pRODVS)eF@?0HpyWDGEFV8PW|K{@i)vNvS;`+^{aX4HA896>g zFO=Lp`dHL5Au$BIUqIJM!qABil<UVRl7c!n+J2%A^IUWfhnFzRrD@B&(G@zMwY{Y{ zbG_F>VncdWeGL;+Vke-q&C!T3pUPYV+g*eb*pKu!VA9fTWN$9PaHsBEe~5I<Jcoh` zz?fk@X>PgI8f!Pl3mw&%eoJ6cj$Dc1Crg95?-toT7H3<sLdz5314SrX*%$TpM5pKA zHf{@U?o*T%6@@41gB^j5Cv3BmLGSx*t=#}2WM+FEejc!4!$^{>+iX!qu2blQqq*DK z8yG~b)SY_oTJShqIj<s-qpAgLbluoB$#ksQf!_R!x6==Oh*oim$oCn?kt}Ss3U*J} z3HJy$7XUrk{2%KEgD;-lhaDmJzq>l-KUf{=e~Z<z?kKkZZgspI{iD^P{`XrQMa}V^ za46i)I8w*z%&ced>=xgiu8(=<D#cSA@T@sjqok%r0kwRfFZ*OZU+(BVf#z`Jq*{km z*}_jrT3*kK0R`D2e&8%EszG^OY_Q5*!6@QloPbW3j2fGu5Ip2gMR7ayBJ5%<Fv~b0 zP~5nWjdod~l9XPrKowpS2)+Ya9-MU>zEiILv(>>JF|x|p#ho6~4jOmS2`FbG(Jq&> z1<)BiPqEX%SE-Ht^nzb##<;878Lb>b7lJZ7s(@iD7W$Aj<OT40U2LzNz|MYLa4xZZ zw2U{O;aWgXX;t2`UFNs_{x+PnecFb2ZO&6m={>|Bk{!AE7d@+EJ|1j4kf?hwpI~O6 zE+kPmedN|$wnaht{T~&RL2EIY{CA4Ugjav>8CQ9&OH^Kh18*%{U#R+_AmR5cx|J2O zMUIDMB`n(cT|wg-=rC(yQbhheHG+novG{aaCGtcc3FT<o6!RA6&_A5yE57<6BLw_b zO6aw)jOEwp#2~Ik8j1(GUdCA*Juv!h&cyk~mp>@dkpEth#<vn(2dcl`)!!89%Rs1) zLj7HtzQ(qHAk|+H>a*MVMXd8{{Qh-+(hmh2^8$R3Y6l++Hr}=J*^lu@difTi|NDx$ zA^${*FVEsnq`1m(d74ite&2)q<E}f9-77V)J)Q%6FyRG~G?;++!RGE23R$KZc8nlZ zrFJ6&ak$#1I_`#acFs}u5TW+ka}MH2+Z`L3w~pSfJ%TuERW6>89)%X>NQ@ZIfKh0R zJaf74N-=PkpEg?>Cl;oX!Y$Y$O2XSfQOvP$y~5p)O%X^pxyz)=;BWR$<_>f>wE!=K ze!i8U5$2iQa3S_GFstAL6>3Xp`v<&rwX@~<MYJJx9@Coz!ueZzX-?KQL?XFSz4@X+ zQW9b}Rl1%V913rjG}I^k>TBKgaooETb$1G02R9@tN9{PrmM^PUAyq<f#Id^;>u!0X zIKz~0uzCSvH^@~XA=>uRjK%RNU+=6dWDn52Es{h+5U>IIFG^0HQKg0cXnX8{jz|k4 zm2Xhz#jFPV<A!YSb!+B<9-qMK=4xJ$a)Mmi*IhvX^mvH+v#IfzV)~d20!<co$WEIG z>jHYTn=!ZLX%2Rr1o7?CseV-^SLfOeV|qk=r~|~CQjir)?V^iZB4BA(o^u{072o-_ zPHD#0-rk_)E;>vLp-~SFWmg8S6XWNd_Bip%X*T?Fw_`(M5!w;vADqfqVFi~KDRigv zqD2jTzb&TvYSGK;%GVwFvLlWQ#<^vR+W<om*|QOM>q<qfZ!==i5@Kbg-etp96zuTW z9kzI$EqB50L&OI62AcK5-93#O7zW}_j~N1&Gu(b;sqY>rGp&!B0;NYUw&n@Mg7fu& z@37BGWWe7tfPZFYzOJBqu^MYmbzo_h=Y|QWRUgSX`E(kt_sZ~~<_E<C&EG3W{;uq6 z!MYz7e*LImul%a)Y(=VHE!XBal~v#3V))xxp1Fwhz5M2H^gnIL*J6nIoiwj4L|?TL zsi4ZlqgH$@O>W+EzH7(C-<RT-yZF0OTxGbHBmaFVuHU{P|3r$b4A-Z*NKq6ivG}yt za6u{jxdAhs$}btqq0xrB(}Ca#QBIz-kZA&}9z8wS!FCSZ0N-C`hERcR*7bRQP%=<9 zuVdur51g;fJ#{jRTG@w3^zPx#V`LVqA&rL<<(wkbZ)}j-g}@*}ISO><jy|Nu@xs`o zitb{ZnaBhZBpz!Wdg30;<c3#UbrwQDgd9c_j!jBEj^Uc9mtiW3PP-3ja1Q!(W|ZW< z+mmi283K3gXK@eNah%%6cuabt*^p(^8bX>=?oGZQkD3LjT|Ck%At0BneMr3k#&}x9 zK=mA1+ujAC$cf8VljazpW^e6gZoL|9wq3%6Qz^<3)u_H{mV+N9au*g`uQ<@`o{4s1 zb>X%qm$n?vp3mZu$Xz-xc8gPC-O_FTm$9q0Du4u}%)tk7uNgp@JWgd+-jn*GoKG8a zj0ywc_U9y5Na<0thvK%Vu#YWvctlm0?bIT<4hLP;4*`VQ^_3CMz&I%8eVjyQyXYjA zI{1}Qcl2C$v5?IcQr@7Mp<RV;4tsC$NrGl}WFp~F5}XqDiX*I_7*Q#uno!D%K6D1z zQHv|r3?e7iS}_NQ{TAo$L4Cg_=Y0~hJVHbS4q+IjUesI+?m_BzZt1ru44UF{24RP3 zxqqw@W|Szyhs>Nj0zq+b?R`;;5=DVdDIX&w_j9B4+Fn`<#&f+j?ldpi`sSZ;amhMI zPi1urA^U5`f_JIg?FwF)q<q$r8>9Qz-Q{?we95C2r0t30y(1Khz}r76s{B)np?+d9 zzW5o@ISr*|M)E%K`;yH@`9%1C?PvVe>3@x(|H-HSCNoug^uj6liNcRUjW3)|gAT-2 zt!@=14)%@&&7E(Q#%<5xc@@O0XRr{K*SvxFK{}W$e_ozj)JA*O;Sbv!TyRdVx$w%z zwXHGDgOj3|CRtD8(CKJiAEN4qGQj27Y<McF6}o@JSxKT1(3ik?ih6!o8Rh82u`;B# zg$K}87EylU+>u=v6;?<Y=QO3~xq&XL;-!JKyuhrM)FA>1-FVAcRK56o^wur0ZEH^3 z#=M>fwpX@8+T5H|2qL$Hzi&nD+D**>jG-G`YLwGr;m{^HFM2e3UhKa=%mdU(;N0$@ z<3mA><1tr9TSEM60U$`O5gr$Y@ByjRGNJA#!IW$dU&tY>Nr%x}?jo;q#6{wtGE>&H zyy4hev>xviyq<QwdAxx3f-&I4>Djdek|Ok>gme|$yPrqrtbx+3-|dT!!#+uL`)&S+ zZZ#?SBXTZ46r*RT^)hui1H)5~dra@eN$jcNp|rlOm{^w#$*DTx!viQ4!b-qP9uszd zZ%8lCcj<ix_nDeF;*s8vy8s@Fsp2~dCbXo1gDeQF7OL+R*Fi0jqk~`wxxGC#xW#8u z)9d%THVwygryfC(MXNcOy?Mo1^79)o_HyQPR&=cla^5lH5T62-YA`K}q6Wm;-4UFI z`|UxNv7L9!*v<T+QT8}zHZnb!VM+)n)j-|YyA5-^gMeQkz$x|YY}dJ5-|la{aYgNt zP_hG};EU8);}d2I|98Y0<F9ar=^FW@L>IRu<K04khDXI=zlAf#AK{Gg-wS7CNV%Ha zb*$<e9&Gm;k~au%?xwvkG36x>Dr-y4VNiw8on{c-(eF`z$Njkx&vS{AbAM)?ocE8` zojO1wx7Mg_Yrb!Fd2QIG<$H7ri^!@61!yHHJxk;DF5;!Im?`HGOc$05gj^<O$IUAv z>j6GWHl%|pDo!!6D;yL`=6H41Vh*5@O9|OHhPVqy81n!*o~70kuOyb!{sdG9kxU2W zq$>-~Xk)St07pQ$zYG=-k-A^+xu^7Lx25>`K<?}7_0dk27J)F9<KyALSLMA;l>w72 zA-vC&zVjD8)t~Jy?IKGLodk&~JyS1X0~ife-+c2p4@dC!u&W)+2IA?!t3X&Wj(*kV zZNV^M3$4>GC(y?R^`|&f?3(Hb5D_Aq2Wju!#LNwKwOyVCeK%zgeI$)b*yEA%53zDE zr+$0rGVg&ap)`_cXDm!eh{t}u*_403*V!->BBLxG!8+#(?Kme8X_DeD$K62;oa9U* zy6X*4iYHd479Q6Xx2O~48xVK$gWakg?kXhHo#jSeBzZ2593;SaIM929xm&PYECTS6 zv4)152^_NMbgeI{<5j(GSOvL1rFZ*ip625%#9}O7&{A`7BLn=wloHb}n1in4nT0ZD znNRzPI+w*!N1#%#IW$Cd#<(hvy@LVmT-C!3L>5U#%EJ;;dJs<#Y<M!=C;2ogciz#} zaSU*(73>?~rx&n4Lt&}Umqy(#H^gWjZO*F){GZs%eVw`WZ<o{kZ4Z^Nc7b9(3Gu$o z3|~WOis>P5bKd*VH^#$EBJY8*%d^a(iZ14)yv(F}*5NP?r!M|DQY`CpP;x3?{E;d2 zf?lMv#ZTEUYgSE{HJ$}Id{r%H5a04M<M3>8Ri2%i7R9Y+{&HeUeSVmC%MbmX3_Ek* zzVCh0jCCWT|NOc$(_i+}KVfhk^+mD#mqD|@%`!gS=!K?+ZzfZ0$meVdhP2*`@Bp_@ zUYx`}TAW1H?uNr#|L8112^9C?2kEYRmKqOZPuU`0t(lf7rr|VL;4iM-TFJ>~5!rpY z*jePqYv75!Y_pY0t8Xr6{IO%KOuu<@sr~ZlDrCMms*77Wn%`c-mrY(iY$o?BBX$lp zxfTl+&f<KNi1u25y&iozraq7TOzWN-$N#e4eWrG6`S{;cyf?jD&hUqtx0WowZvy$V z`68c=5#D?=NSM)NQ3YJ8m+)cx&=7Xqq4*f6b%QFRaXf%$1W`fUxRyO|-cwY(0Eh@A z+GxdLY6Yq*h+0lVp@1~j1)JtLIgq?f2;rqyHLI|C<93#`Hr`hhlKR&pFrT5ty)JKo z9Ex?gxOg=`prm-)Ytp^i9-xBNcSO%t0VFf0w5V=r#Cp8PBd!wF#_%#mc)5!t9@12> z-v$YPKV5t=%VznyjgGba|5*sHE_!lmL}76OmY=~@6x`*A^>^^<O9|7@H0MWpvnDHk zO>=&vH(&Mg{}i)6)0>yA)qc9^&ss2vl2w*#Uzb?bZol5e7d%n`&9Z6YDW%tf$=l0d zWRhL;%PEOJQywbgOq$JtDpLZ>U2yH|MndJF+2USZH`%m};a9d1!l@)*!@3U9?Qogo zADAOrKEB=O0M&fGjgU_IL8iHSr0?jIx^4F~TkZ6Fa3|M2jX!K8^uCRd@$EK3!>~L& z)-8;Fc@IrV<oCtI+}q}kr8W%Y<60)Fe4$$x7Xtv`&;m0DY$y4X#tt_Ok@7tT&+urj z1FQ%{-r}+D?nAewl&+ie01V@F9Q$*xZ#6#&Oq4wlyge2GfaF*!##4V$K>Z*R7a~qJ z{{&+#HQ+H|a@kWrxlpJrdNce-J4<keQcgV9d!O_Xr$FwC<0!<?+UP<Qw~_eI&Hwd@ zVSnEo{Kd3Ozq?c{hT>lrilak6Fzw44SPz~JfIC|rQb*HYte(YAMP3EO6Sb@&E>{Y+ zQa&22Uo=;<%Dh*Jw;$j4U)DDo;CNl_=r5Ug%w9}9_r9q(dms-cR(!rw<YfQ0iFeY$ ze65Y=7~)4J{`G==K%;zMEeXN@!t7ID8Lkbv0MIDeWbwWOBmE&e$$ou3BKz_H7AJ9? z4?4cZzg1f<zkR2+e9v->{<*1tZLeRmwYIo?uL!8~o2h?0<~)!6NDJ5Y`rp^Xk7~Hq zfB%^xuKNoAnI>+?Z{PW=s`zH|znNO}-QEF=c(_(4)jXeXbn+;LN6DWFu9<Bb#QDbF zQO$-#8=JU5E;8f1?1a{1+{cUwE+jt?Bkz9QX;3Eg^C@7j(P7Lq0wY9K<@oC%MBJ_3 zj~f!17xRtDsQy@7Z@a^?sBIH^-s&^g-w@u7(#WBJy`pfTz>}s0rm#_BL{UnQ41(vy zS0Y=xw)Ys6zZ!k=w3=`s{!5c@iSH(VIfMD!Pj5Bx<znu$<?m@R`O)%w@GCgB78m`V z27RkPi$?vX27RkP3*GtyEL-3A%<BA_27PM!r{xTCOEy^^erNnYG5xPH_}ct$$nOY% zy>?Kth*}u)2lGEZ2Wi;lq%rDO=KqN>pAvvk?_=5T{n?qk-2sU@ybTkvchPeA%KR?~ zquSVouCNN<tJP!fJxrHt-%_g6Rs9w7zxhi7@a^8tI*Q78lGNnjo%F}871$%TA+KDo zpQOgJqzghY{)u4%fvh{@(3DOZ4&(JQO-3ZO$PiPF^FS_>gH>G{4l|lL<~%Rtc|uqM zqb_FKT8RBP3RLMF7{-NLag;1<#8?N$&MEJa+8o?SLk$S>0otk0V4OYfj+_6;mio>< z1iaMd5r4e0x^SHQ!|>0|zkVbDZyP*=0E~a;=JSAR8Uy>jL+br&^2u<d%DnDMIU3Lp zG{$iD_@t<e0sme9^jnZs{>~ctQ7rI(ehUW5j5ugadVSh^e~tM8))#xvACR#4+@-%W zU*pPm_&yp8xqZVG4f>HQp`F!&1P(N{QubPV#20eG4+&X}VHM_ZDu0V5dHF+&zQ#s> zZqt8Y(VqyiaE>2fh)Ac?`Z(ox_nloQh>zOrEYzNRv*<4q#K*%b*5&Bm(WVXgBYpZ+ zby~Zzf38q}rB5$Ch`*yz-zwCG{H<)cvsYSE=DWTvL^5`{+E^Ha3_Q%oaF^`>yQjjj z3b6z>RBAT5tQoYsEff1wFB}6cJShBikF#@g5cWMJbh}ETjD>r30e>xg5T5zFLhe|a z(WcwgAxlyqfJ){Qny%-ZeC#vml(;ce4iM%aYwMPo7GJsjI@+kB9(tUkVLBH(1%$1b z19PVgcQM^Og1QK&9e=^bk@sj=IhEZMQeb%L9k?aneeK#C(zj>tqA0p-=!k-vMt3*G zIdvvCxDj1#s!VSM6y!%|VvEq6n`Sn7-THU&%GW$RJG;fkG+}bOH16YslSSs_E)|m{ zeY77WA{`}uw`I@vC3NY*Kr{%%>ls%A(UWQeV!VA!3L6rB&6YQiF~r5%-CCttnNry! zqo3Hm+8{Fi&IY-QNe4`Ru@H}YF7-xUsOLJ9$kOY5@@prQ^UE}0%~ZjHJSxRzUPC8_ z#NDc4tqXZ)Z*O}Bt@}JjG#BiZf<!eM9v0MLOXkNB@D1rXr%ntknZxTichyth&>Y_d zxLu&*MG5ioDkVH`&@iF$i{p}=dA*PkU?d?51X^|Be~oTob*;^q2UYU@ap&anHWq;i z(2pAh_Z4_WVm{n^?89~BIOVWNwB!Z?$nmj55YbqiVT?rY@gbq?*lO@Iuq<cy%9LKc zS5XlW##WT`W3%X|U((`jYOnWW4`HO^W4QomdwfHlp~vnv&N_1Mgqw>+SqK^O0L<WE zn@Cxa>~sgWGj_d*rOw50(1|WAI>gN$FjTA0>pbY4vZFO7hwk-GZntm-2boUV@*Odo zGmhhESojBh*9}@_;54FmGQvj$*$*Idj4OBA{Ktnga+jJ%AFxH0scr&wg^z2l?LSh- z>Ymrlx_$T*oo+9iP^?`8ipn+L%z~&m)f5Moe8aYi4!m(K5?;I?=R+5DXVayI1hqT8 z)&Y&c0u1T<=Jt-1MZyBP9<lB-$ecW>imTT$)0Zac*Et){-oEX-efs}td$S(Lv8_w& z#eV1j4A_9twgESAZTDw1EPNIR4J6P7H0MDiCC(z>D3anlh=Vx%dLK#=krA1ZSy{KP zAF?Ww$z&WJ?z7h(*IsxwdgII4X5M#pun6$UV02*rydjpT7HkVNSO+v${-G^hVut$D z5@cNfR`_cLEzOLd-ZE0rJOMu78>!)=)BIU&wNzP|i!HcOXy;LFwa`Nf@bl`U-fFoD ztijKovu=XDO9}PeZ~m}70H2I+ec(W=A3ptjnn}PpezOMLk8cPN?(qe{TfEj{J^J?P zzmf%%nSMGnFGNAZ=N;uvMPqGYs!TRvVnq@+Y~cQQm<rETR`EGR!2VkuGDZDd91`cg zgA%3EcXJA!Ah;-@>Md!Oy%6BCpJL&zcM{l(%F<s&ntHfuvs@>sj^^pm7p8KkOj)jW zce@dt@Br3OJO0fMH>Y8?@O7>;)0*ONYw%ChX&|Ko+4kNhC=W7Pqwqa#`zu>%j3_X; zL<~2*2YR`nSKZBF*OC#Ca7Mvw<`mosuI*El5hH%S2wZqSvb+9n61!~})7$HMJa^t@ z1{mrtv*vvqH8XUcT%zp_G@o?EIE`3VP^?dGv-<8kyZtff<FsyaR7>SOTyZo%gaVA* z*Au{}G>2~SEl@ILD7o}?7LTVam2982>(LHugQ7Gzm77x~ShUB5^OF4|8Fct+Z0&Ps zt^DL*(V)|_%lvg(+=l=<kjYO~Qxr?H$Z^Zzn#Txir67A_^7EKD;y7a9Qs57povN~{ zyLZqFBs6g3g}~#?5cZKu-E*f_wAhy*#<43siLVybpzNI{F}TlU(Lp$sS-0~+Ssi5u zWz2Pl+RGHIG@JEyV4;1ph0jqSa3wyhN7s*KavNVFc70y0T#ZX<jG($xtSfWIz+N_n zak8$c26(hgU7{5PKzJTQ+(p99Pr@s;7iw`~G1a8Y6&@~j>p6Qk+r}$&zSC1V?e^UM zI+;QY{Ew`alFjHTMJiCY&Bj5)Ol89DO7VKNnKwhuH>x}%rK_&twXh4F7Uwl;AkUXJ z=nP4J`8N$0Hd05gqF|W3cn9y(*;*e%lGjRFWTcxj7xl~k$=v$n>Wy!%$LnyPknG!} z4BPfeXH<Q`4m&L4-(P8W6xKgmz~N;#OWw&r9}`-iEnGVE94@#ht4U*_FK&G0p%^09 z1iAppfn}%63qOWrJSmN+Y&rPX;!V|(Kc=SDuhi@kZt$0YRrRK34eiXrp6$lrhp7E) z!DuH_?VGa7KHdk^-0PihG#3JAQ9v2jz#N`|D?qC{G=A&^18z6;fp7OgY4qvp4{CM^ zPx3V3z0)m0))?4%;hdvKP7fUKC9C(^5V6oJyy2C|e7^H}-z!ZBMdm6{#h$&*pB!c? z1m5!(Ln=OUMfSaa&ImDY4b3_+CG|p>!TDO_44UG68J!i<ET~KAF=w$%x^d4}Vy#YH zu$fb%x+Cj@7v?q4wu-;I_6L9Lz_-|9$92Tb%I#{{2aHBnZgF#Vhfy1Z6MWRPV=Q17 zq4q368xV|VGZ~*WzprecR^%-N%+G3jX<|c;d3qC`3bGICv5ft*Fd)pE!YX7=T>w;c z1Q%fCWupSXc&eMl{CpjS^roQR0}<bwoA+B10mn!6ARV3ble5~T>lO|GV0hf;pa(1M zs*2Yplk1oc%TnajSL?lU_K>*!p#X<?%@2|UO(%rK-NsdYMVeA|RcwIMy+>=Bc&Rx< z9My4;8K`8QH+wl;R~e%SdtF~?8F+%bhsXv)BmX*jGh#3s(Q}}KLTn=Lu%CP$vrRI| z)Q&7H*lZ~d>`r_}#)d|wp3<iK3yj?aN0BwS=4BsqP=CKew=L>HS+fS#1`*!dyKsbW zj6>#je#ott|MZ=*_^2h8Y1gd$H}r%vh;JFT5oZ6(7dO6(^1C3v(JVwu5meZ}>j#ql zQa>1fOFtAcrM~Hh;A{Qxg#vp}^c<IrTe{3KRzFbm5bxI({qUgZ`ErH7vp(Ra|6+x| zT3_To2OR(h_j{AeftGP-B2fWZl{a_pGMqc~8fLrvYDW<8Bvp?VQ%LA?7nU{P&}erG zQhb{wCYoy?I~OdLRm0Y{f%dzsMAa#b?b_n}TVlAlh|q`1KL~-WA8D;kRT7v%_KkMc ze&Y%5d242)Ww++($l%<nKsQo)w)ykiZ0^($IJ9unj_til@5f@-oTqBzL<g{wOnG0W zJBNNF33F32`*|??IUbnT<R=-9K|B}=m$9<njj(0vjhqV_0%x|8@2`WVA}Uw&S7wU8 zn<)*<!H^bpCg@x?bwd<mOeG_48am3=SGe`$v4!njav?^(*jcXpLd@5qHe^C6?kCx` z%R)R~WczZpEm;m`w`jQlf#TpCovvdd970Bi;~Wd6EigUTc%ZGoPLf+bhSimQ;Nb2W z-S#M7-sKb0nmM=W!pK`$c34+s!H=918|8Ezdtwx~5gi4(a8O?Uk2QH+9criP>_btf z2!7p)?rc@Js1eX*hQ_b;!9vxw`Lh97Kk5VHn-o(Yr1&4d7GvS*k_n5QP!->*fKd9> zeeIGRe;haceJkP@CukH?B6|<^nO|b`kG8?|$u^jm(7OsL*0(P4w`(s1xH=f=&nM?R zh;gzX5&unpKHNfS(V)MjK-1sljo#APHXFA(8Ml>kPo`=r*}b!ym<zhvtwJb}52y8T zE)jPLuG~jSI1T#=?wG8N`4BqGV$$iOBt{~wZB?x~&^0(nPLb?mt&M#8a8qTZ$n!Ph z8%y;Ic{-x1u5QV(3Z;T|DpHWP-Wg!s{idM2%*zpaY>Kma3)|7IvYgrZA+{#G?s63_ z&>L^Gw*+e*=_yv`iPxl+R?D0}xE`5S9G9JEGWc^I@xLky%Qpdz<yW%sRr=?t;RA+H zfjiB;S7|Kl0m8hbWffY5f2U5v6gSfDhKIdQ3UyB2UWjRpIs$(yqtf{<L`H?w>UKXj zS)?Ca)obrKLXo?ABO)KuG_SS_F>%oit&F&V(<&=BspXvX0wOmUVv6?q(sJPr)77@J zt9~C)8v^5x!FpfT6mU%;pIMn(jMgM_rLVR7mLIO$)nVL9iMPj&_}pzIIW=4~Njn+2 zGqB?0OrA7XHR~pnhF@T2b2BSF%;1t{FRl(xXC$Z;iKCd(b?d%>)~bZcW?`*3O+Gft z%Rj87_5LB0UD%Y5N^IcOygD}z)(N1VxG#oYeET>+J!!UXDWt4pgY-U)$F;q)5-)~$ zz**BW<8;ZhW6z<dOZ88lf|Ex2sB(9Z{27!^zlC}LGK(5aywVgBj~Z?|TGUpNys0gU z7&l*b6gtL_ieZW3yv=j343c$46JkbbCb~D#6mz%s5oR$HAoncx&wRpmaSjadXJn}Y zSAfgF&OU;tp8b5I53w{Lra5oEMw5V&ePtaamN}(}oC-_fpz+P90asx;mEmPT7%*w# zFAmqYxuvnpSlQCets2Z-rrtaD3aiLxXE6>-E%%G<b0Zhm>si=XJBS&nK>uG5rG785 z`VgrCPL3I<Z>)xL`sb0QMe)7GmNL?p?AyrD!wspxq0U_5G_8v{aOnencggz(sclo_ zGN35v!v=Pt3ZOAC1lb`Xd*0ro9C}=b&0v?R?&%|`Tp<fNh-Zm}CC$&gP@$Zo(Fcmx zMC`>$s5D+7Q1U{obp_K(<#f%pnZHG?WyNp@&9j}^IT1HIq(*7Ymy`4TC}B2HLv3*F zh+$_sCxGs$xwu%@X;l}gkW;IbYNFK+?TYqP?1kvk#aYSCi4-wt+xu5G@$VRyN}aH# zV8Tkbc(smRh}g?z>MU%TuAgxqKLWGO8{^D67ILuD0;@8I<V35Jc8_ywUp4#Llf8T6 zBBlY|I)r&4-~sa6TyN#dhFk(z3zvRWWQUAXF@xPVPC|u}neXEE;uhCu9RPsup%i&_ zXkh3aZzkrAlqUboar-K&w77Xc?Zu1hvtwG<q7zZ-bUGQ<1qdv4>(heh4`gy;CwnPg zEYzS@bZ1<*ejm#R*9lZ*cZPaq>aeu3k=PF;@MRE*@$osy?g+QKCfb-~drY{WFX{`C z2{*GvF>vp$H4M4k+h?C`=VbPF*~vcWWNj9Ae6`(DRN*5HdB2CIrnt&wswA-G3NR^1 z0`a)o8|+(&8cv-`(U3q`d>V+}q6z-k+zwm$7*1URBmDgsm{viqwN47+-P9MrtWWMj zico9m5)p{8z7NvC%QQwL?p1F#**2EcBXxBdyEABuRkq={rLpQdHOv8DK0^5dlPj!_ z`(zS*KUz<hAXwpBjRXfafcB$$yady!t&Y>)P78QG00ZEEx`c*i>P8rR;Kcquh!mW^ zi`OTvrRkQXm`i^26MJ+a<`xSy2dph!wmS4ipz6<zCzJ2>(3(?NrU}%z#aY?+yx6R? zl!%ib^2sKS$MbK+>%qyk=Fjo^k3Z@=7U_@g@y<sJG~8Jpxs-6;-^%{{R_~nD@9}zI z7}5+Rr_7+d#_O54tMlvC9|K9S|2ba&;imxoL^Bte%-%Lk|9ZpN8F+KR0(g5P_Wg|( zS6BVO-u+j2D|E;Q_QMMSRGmd!cB0jDX4&a0yYCJPZ(ba|WHufoI260lPb&yI)uHOZ zu_j!ry`MX)`(zXyetIEx=?1=DqdDv^BaFd!-E47Vf_u7>cN`z16}B#M2A&bI+H*0o z%B|B$5U<ocZ!)tOfP0k|mOGyLwG$PmTWg<Yj*5J!IUi}v@|#tX?}K4?SSQ!m;f#}g zb18QsC%aRwgVX94@GX!N=7OJhNtJP<**<i->UUm1vx|N21W752MvJF0xF^PWy@mFK z982ZCpRen!KLkevoNR5gZ#r3FwzojO!Y;j&60B?XyJ&kY`t_aZ>LV~_qE#wzTYnST zxaiU)7tHLIcBqHT-!L~sz*MsG5a3&w)Vs-hjg}fN=x6#eidgrxUPvbx@F#W2ejCfz zG9!*k05+>C*&3UL?szetH_P7xb5l4B&Z0+oaDIm9s(LR(bY*pS;mQOndJNo3NXX;z zS>QfZLFJDs3XbhVogAFA2!a2{z(|6Zxw;m(n@t_W8Uk>K^XQ>>qJWEYf!8Y6-bBus z9G1>9#}-@gQ|9G9mt!YzZlI+yQ|y3w9bXDHau6K+P5!W9z_q)ZJi7AR<!6Jt(;q4% zd2*rHvK8oI1@Q{BtoHJ#ryQaHiNT!_cq+Tpfq=Jg?}5F|z|)}R&mT?}70d!3<W54) zOYMS0k&ikqe}3l_{iqinbX*eZ#(_OT5~@9zf#65A1GR*qfgU1(o?;X0x1aJtEYEp9 zMSXR3)Fr4r96X||C_58oBL0=m1!wbn4gD#!q27A8tEIHRATk;rQif5u`+*#j#+{#x zy^BP>arXZ5?jJdbubUskQ0%KFAqhT@k)0A=VsG4)uD0@9tH8SY-g~6C#VQ^#SE$CZ zS!Yy0(_ZkEyob{5nu+c^9jfCD_>q&MoX5OoFtJuHjxd@mvkU0ZTVJYScL~wR!v>wR z+Y7LWfnUrYnOr4ncyi}vD2QX17Xh=TF_m^)WlIqI12DmkbMM9lxKS{C205UswszrY z>6V3anVfA{_E?M9gxhAEDuQ4V@HHxOK!_WC3MnPr3UPhO0!77l5j*yg2CIj#zRpYa zP?6i`gKhVmW#;5r<e^pMpi){lkTMgR2vsZK<BwJ#o=)5CNk?47*SCn8BzG@YJ;#6f z_pTsQ&8gH*syXJcB`w5iPrv~^O^j+^u9>Pp1x=lh+imm5%Rh)9c;?roYWR1{%ll^` zTH=T8Ys#H7zk_gv{jQvdbUE!U`An)Q6Ub!yX40Fgtz~cN-v4H?n);EoNid!g^hm)o zZ05c*JAGqVTDZv{*h9RLc`Yhypnj1Tdcb-99h@7Q@qHol$F-@ZF!Ba*d8eHExExk9 z&DUvxk6xwbJI1|_JMfK>cp-a#!t1;9q!{w>aGd5NEsyUJVQ}^RTKe_MKNq_FGb{hN z?zcjMKVSLJMqs>rF$9CM^G=O*n@+q{Op?PKjz`*Orjs%lXMTmdM@Fs7^WEg0QvoTu zyBsuUx7~!3LR{E80o)yQqNXWRo1O!%3u>3$X(d?~lJ)ngSVNW+(|fYQ22$CTFGR;3 zq4l2gn{>JddjLrnyQ%uTKBmx7MziUJQdCyw@x=iyzJQOXohw*lxe<d=a`OqSArMwh z)27>jaFXqAoY9@xuVBe<H7gfR73>yeh)(l(X@&urhK%X%jbd0;CbvW@`tb{KI;*jO zn9yP0ZEWvkZ_duPxM^ZqjZlN^vf_4f3tZR$FFfJo5f(Q3YPS*<8Rd@Lodz!GO7?^5 z2GRL6Z|3RD;Z2mu7dAb+;@Vjg`DDpGzsHrg=yntUAoj6$C+dZGx>5GnohY+wP#)<R zjncW*;Njw0BloYacR)XfWsqe0IR*}%U=Z{p1}<=?HyZZH@rTIKiC;arvWv`}$)!C1 zPo(zGWOi|+e=W6tCbJ89_9C_VSbutJTUOSacpMT1`DcX=U0au)N2w*w~L{t8d* z&7C#4fp$1r<H2RG`9qzUT5yM)?I}$AI|G|>ya_Hx85kC)II*1OX5*8QW%CQMK}GD4 z9KAceUmvKZtJat=>}%Q@^3pK7giO%Q8kquz+&ete;T`a)kbuO_*EIAtva@6og0r-W zW)itx`8H?{wS_bley-LuWHx$j4(Vybtc@@~O6yDOTj^z5HwR>U_V%#8#31W=y@G77 zqxNG{IG6hgMQ4td4k30)Z-~a_*QoG<+=|e)&7<qu)<>qoF8R(Y{mMQPFMrqx8wx&d zSSM<C*_4;t;bzU7d+Pf`$W0_e$>grfXnZ`X@J54dUZJqVW^emJ_Q%VAe1{cge8l4N zH!#8eDwupq415$JNjt~S(3U+!KLE?8+OChlVr^bSc<l!*_XHMYi5HE#KLeKgUk8@Y zLF2ENvk33^V*jlAdae2TxHD|XKO<z9$JCRjAGxgIz3OYRDwitQ`S@Cn@ZGiH@?Ar; z37Nna{M;?|aiuKvAziy98@^l*fJcfnEtdGmv-P<m?8DxC{Df~f4^;ABsR)y<D3_iJ zEFu`mC?g}kcfcOqk1dTM@G3bXbGN%N#_?QJxqNkx*SmScvK2&g{qZ!ftj=T`JD&>E zuBbsjHy5cs<1a)n-`2_;b$q(hR1Y4k_Bt5zx{~WHG_{Fp9ZT*!;G5E|i4(DF9j^{q zVHL(Z_GT(GP$WgRyOWn=;k&x;Uli3L8lKrg8*<}Nw_{Oxy5nSrPHq`2z0LJ0R?_QS zuVc?%?O{j)Pw*fk8+T}bG1;r1*CR!Tb7<R{?NO$(_6cb~sjQSdh>j;@)GN)`f<uX* zopNFJw;6bw%%h0m9;gG6uL^GK*)_0%IkcXSC#$qv+=pry@=z-2@~%^;%V<wMf8Xy{ zd0^rukSIIx-)D)SG({rD=jk8~aN5FDhs67JH&U*-ACCBjG9-(v9x-)bXlT&sTh-T3 zd{Z}jO}y%4W&nq+BHo|;Lj3sbTkP+&@prY!Td&ct^~tZ)2{@a7Num4*K+go*I|ND8 zWh0pR&^hTQTG?cM2vj%m5l7obK^EGh$_ulbr+eB7_I<0`BM<ylLDAY1Flxdph9{0O zZV0=+;NDueo<hD51-D5I!au~RjbYtZ8<m6I;p58ax5HW+fOoMQ2FldbE^Q!tBlB0S zJeT4DZE@xR#jY|iT(~P$2iD`%IG~Menuj<nPTIEV<59l@Q$=Yu25_Hn2i<PNoiEZR z)p#9pO4<{GSA(ZUmIgCtxZDMaU`Lj~$XF2u*j+cAo4X{~{7tmB;&wce3FNCQZ}y{O zS#5f5#mR+mlK7b)HD=~WUJ-+WIF0KBXE$Uc^(C;8x8BRY6=il3h^mL(Z7RlFE2|AB ziUTjiDQw)}$2d~|c==E75cJB_^SxW6{>$8&|M~;^ypO7q4fdlFWl7j6#@d;_DeVXH zuuDwsj{F(&WM3oC6D!`QJOQi-KCb<pZSdWbq96SuOHm5EbfheWDC(#f&ymY-SKqGv z&Q1Z-?Kk)8XIu5`a{aT-`c*&2a>71$bnLWhNH<4;1(qyAPnUG;Rs^c=k)kxT+j*0= zg3P$2R_l0SF(g>Su4}J)e(B3e>2bQ0s!M*y%Ki52_=V$c{MAuv>x$n9zM0J=BcJfL z@XLlLT%TvxC6I288iuR7-SLGGj@g}9pW!6H6^2i)$O-D0cJFTxBfC0^rDdf|cLZ5) z*0O2zdkrPZ6F|RN<4lR(Fjr}H<V%#C%a!RK&J^4V=UL|n4-xWJSIpL4TjL{+RfgA> zVTTHYFz!Nri9*=d92krf$!2};y9Wc)S~O8(iqwH3xrYix<DDgB3XmCjMXYRk6^6Qi zbr7-JYAo0X#5O&X+6{^g^-_(KBv<WbhRtj3Xj9y=#K~y=*3Yp4PK3Z8KSqrfheRh& z>D9>^;u>smzdLmEt-m4)N8DFmq_n@EMD<AJlj|8^LG+sJ7B~(#Cv^0yC*Rnr=A6N$ z8EMG{eYS3XY1?Sfz`avMXrFQ8dwb?F>Fw2?Ie(PycP^|~Bv{VopEx7L31!V@BPvyD z#hwv#5OR=;p4+E1X6rkEJY>Ad^{li88@o~s)LX2DR6d19n9a`o=2m7sr5*RpUWhnN z%CmJYt(;S(DfXo5_WFfLc<a6%X-v-z^}MEc``MkZT8n7D6JIW>)ie83$xivns)#hY zq$jSO!u8>bNf#Vl6BUqZCLP9RR`0V;<gIvUZR9v^Nfu4;vSCc7LL1+5B~e2z4p+_m z-W`ucPw4&Lx*382tip{hDDgmM>uSWEG|%fEr@D&BnSvp2lxp|{p^5o=r&u(r%LAva zq7vaL<%}3bWvayseomw|Ytgk@*NkAp{us*O#c-bKug5ct?vTT6lqb{;?&9&7%JN|^ zcNyXQ@$#SFkwEReJh>)e@NxXaJ0SS~KgPLcB7V0XvX45Bg&*@PihRiieFg>oLGtGP z87P#$0tI;x-cwZd$MtK0z09<5>m{}G6)=F~^8^9t3t)hr!0_uG1Lo&n*t4&;?2QNJ z_crY-aCon4`k~!#OYXdNH;8r>W+fD^7;ANpY2)Ig<2ID8wJe=mnjCH~#7JJ&o8vg{ zHVw?!`_32;4T=RvGBXQUTbU4_c*Zu?LVWOgXX{JAJNNC|><+ApSupoPV0&^?U!9{` zHtBV|%g~8J4=3oht9NrNWz6l4BI8~kd+HXg;B@3X^6pdC>Qv;1>wqOM1ae$;6v=r9 zw~TL@#ul*{o@%%=?{itva3!rt$>#^-ct9{#KaX&`YpRwQ;be8)oG>5{_H|-1yx_0Q zXfmt*3h9uxBBR~0+vLdgYN$sfBV&nK(ocisz!+)xuHlH6Tc44b4=?pR0xi$(CD7eY zy)(K8DR&*^%vGuedrj%5id+{I-uCv{*?j{C6M%#EY`^=ERM_TDmn85k!UDCkiPiZu znu3Z=PCfq|9G+F0-v$*@WTw7@g9yNZi+=Uw9oxJRHJ1(U7q0K`f&-B1S9tgdIHWT5 z-VE)10*AA;OmHcd(i<!Lm9y{(AjqN#!e)~oR-9|cou?WAR^qlYDmlCvh<Z7VDRQHr z)^aemo9UZcRKY-5n&gFRS~_+6AzokTk>mQxQE?oZPs<wOdT71Vxn|eV*p4X!XE827 zV>O(2dqUIhW03b~(=uw&Z18-<w?VUuSFNI0VhM>$q%4SR<Fq~VMCjI*5w97JbEi<b z)@`+l3Prf}f}qp88`d0H=jK5*Uf{}htU4XGuNMk^Ax=ALB=Jja;B%;*nL#eGixidH zJA8=u?SVK&t}o>lF=%(#XY2uG(H&ljUVw|~>@TCu5|yN!8Q&fQl4%98SGBT2Lz+z3 zM&nD);3stmj%IjMHkddE>u9$F&d7R@C4Q}M%j)I7A8*VbFaOs&y{C~B;Xm}wE9#_- zKJa)x+w${&mV*3y0&?k9SoSiVz1=)niywKGpLx&+Md*@mjG=%h=MQt_7K+f<DVob1 zIeg-Mj)|7mYM`M<i6(AuT2p{O@QJcZ`<@w@s1{$&WPK_8&&}mP9v^jS^Wuxl@35nq z2sb&0{l#~7WiH8vyjFS2__`y1nPl)MWvuy+gJNW37>?2V@tT#7bDrLO02TbJ&N++Q zKV#Ye7Sl4_V-|ik6J+zNVKoa?D-i9UkhOj_ljN(3Buiem{Z=t&2*`6r%4@xx@woO{ zFK2G;lp<^6a~bjr@w+ooel`*1h4@6lmp`+_;7tmFw`Nx-<KV|^HL?h_{}AX0Egu5i z{gRA@h2da{E9%2=c=IwR<%9T>rf0(o0U_kr@Zj0oa$zBQ-^nHOzf_L=Rtpjo7lDfb zcgD&A^v#Vb3Yk_X_b9i!dq-b<<eKfHeygrB%I1fH<qtuDTT-0P)QEc*byn^wN^Tnx zHAw({>m#`-J<90S3>RQ_4crwoP};-2y^Auf5e2<h;mK4ZSI0?c<-@sJqSLOrW4)RR zFn9}nKPNjz=jYHRsl@7cCp9BlOQ(^2Tkz($2pNDRlzvaGFceL23uFP{iWov>%x9E~ zP**bjka_v7SxHkFo*fukrmEpUe5|=(Y#!)<g$nN1S^y}4K~~?U@q(w&L{U+CBvekc z%sB-z`E4HWr|<mu%A69*(>C>s;aLm&F>1#MkNSiKewPXbh9qfcP-tQzqo^UH1+4sm z@SK88w09nx3!=<E^_4_D{{~;`IiTPye+_*=4x*B*@?eF~zCik9et>cfc$WNao-}jf z`;!Up&{!h9QYekq74+?(Ft-GE%7{H%>~@M!f>z5z7@_)Fx@%(4urT8R9R+*AiM$=~ ztmd>dlvnK?cG+s<Q5%0);ltd-92t9S9Nm|l7p*jJs>QRJgb227#mRj6-?msV-*&@w zpV*gyymTE_UocYr<K=%bVsGfe?_lH!9@2M)IzV#9lF3(g3{sXY1Q-+x1|(pA&t(3= znC5{g%>-vLaqKN3dG^@426I~Svabq*v$!}8P%Zz?ku^PzMFY<Kku>8Kss78Wsbar9 z;0YAVO4!cl^4?wIFK<0p)dCLD?VC?Re@(@fd}m?V8M7o848nLSq{yJzAom}oE6acp zc5pvr3&~NIG~SwkK=nb9w-?SF>3L!CdoOv;waPgM5cpT0Jc{?1_z@%?%JZ8?!M;5~ zf2uD3BQ8R39bEqKAXECFyccqng<eS>KdSGq-uGv3yO2ix-uwRSZQqB?|LObw>}{96 z(9iGt))Ds0elW#pY(9=P$2FFMJZ*2fbJrf7o+I|F4ZEsD{0L1UiP`4Hrc1?iWOj|$ zSiduFtVo&|0{M)C9m7#v%E;^vm&5zXRYwS8_<Ab_y}G`W+IFUGfG~JDrPuJXGy5%q zutzzKgRoXZQm*-8IE*)7EMWI&pQKy1;W?d<bX9fJ{dtA%x@mV<2|xk7X3Q5Nh_8|l z%P?*1r!-S8eouzp<4-0<$Egorwe_)RW!X)rs=rlC;#{Z~0@nzEzO1DxvJVggA*Z`> z-XkS<?)4$(@IxP7LmSxdA%0KAm4ysz0v`Mx7Psvj29pYWl86d}b%V`%XsWU2L*rQO zCEQ0tyADU<AeWlS%$yXfM<%-j4q*`H@qks+eHu+Nb6@@g<xQd<$iR~QNHg`?`tTLO z)b7`mK+IaE->g^_`I2A?Rt$s1Y3c8BM=u31GW8^4mHsYw^hX}n|Mi!|lKG;^*1K<o z!D6eOexl9#^v{T<PBVeO6HR^1S!<ZkczvUdd?EgMKCUH|OGH1C8U7Am^hdVnCE@&+ z_@bAeJ3Z0@OQ#Y|dJkgB>DyKFarLiA*9-Ay^7SuZ`u!kaUo-uFLHGDO6I1!Y!jJxy zUxw*%6i4HtbH(L3pP+M9nnUNhnV8_!0O`$*KNke&L)LbikJI7euPz2~1&<1B-$c_R z7wUL)bvwDR1&4CRjfY|0NjfASCXKa=x+;abJX!8GMpP-eN-lOJuKotaPeqdf^+h3I zA8g;Cw(H88FTz<R5VUYiq}U=ngrTV*7|DA~Mj-1Yh3*lb`sD#Gedw5%1wMV6u^1Bt z?GObK_+>{?aEKy*1%!4OB0u{SHvwPs2M%>?IH_+0tL_g3s|&rV2`ln>p%<I}CIQ<b ziNmi6*uJ@hz>zh-yYIDU@`V5>`Dl`I&%&RPjg8+Lv1Ef}6y2a1u!h6agjPvP9fXQF zJ!J5^(-Wx+o!avIg|3W2730Yg@kQGF`@UqkYX|P0_VZp^QwM*2@1?As2CUd5Z)unp z;u&c(q>D^8n|-^Q)<aUB_WNXe@Y|Ev3n>7=S4ZW&`ip#*dLU7MjV9~8t2se7oHn-! z&<_D5-1ibxR<L|B$@|TyrQznS>Po5w$G_hv^>XL&Gqmq;TM91+FV5#%eq3dZq7seA zz7S$5rU-ATPHCFmUQxMpePC2VicPD&0Ca0|KGwU5Iw6y+NFwUt8HJbFaaWiO=ELU8 zzfDae&PR&Dv1rA(SHmGP=T4m07a^Om<6zOlAAh|3+s|?HGfnfqf|@a)7ZOwH?~D%k z2nBzGn(=G$(*N;G#Ecf|tJU-YG55&!Cy4nkp=RXO2jTLYX80S#iVvz}AdS>t!MSB5 zA%nb5MR=gxk|<xUs>pLL!oP-C@hJ_zD-6FPR{SOo|17cM?~&Oq#EQRzkdzTfI<l25 z3BIw_6_Kp{DbTFF0(MfbQy3CTv9V78=q=5w472g`DzsOd-N;|!-PRXb#=tB!j}KUV z*sjy)D7h1ka+<j?+b1F!67KcB%Z~+mok=unZ(?ZF%C)vg^^zy&=2nyJ`M4gjlW|ep z+3M;;mfIJ&z^_^PLTp8=gwvHCvHMBkp(g%-kmZtnbE}^+$^d+P^(LPPX%5e-+!a8_ z3H&O+=X1cHe@#FRcG4t<(>n!u@FhZOkV320*S65oob}Hc+i!CEI2RwRZTT5SdW87u z@2#dc3gS<=xP<wB4;g=ki*G0EdEP!D<GfsRpf_av87?lxj{q43T?$SpoBH%xM4(84 zENq>i$G%hW&uuL_lb{AZIw-N8f=-|VfHl|Crkk6_xEjAk#-HKhQnc_(WISGGn>ga! z0atgWAh(@soz?7al(A%TVkPS@L`?MUaW69a20f`t8_`YOv&U-K&|PxnSCWU0hZ;BG zleDYO$6D&Q$795K4Ly~(p(E=Tf~(s@Cfg0tadX_McaatSus}KNzR!)NBu1pCz6%bg zOj*Hlta8<LV{CXnh<fH&9)g$uVO|=scbnq8j3oMhiKFz4XW3p;0tfQh-1(DvXYc}% zUyY0T7N&`1Q#En<Eg@kc>~wj!ZLcxzcS5YzkQj*VmH?uIFp+_5+l9yC?M7uL;5Ye_ zGWKLpZ$aoF_JOnlujZ}2@}bTC_n-3W($8j5db^zY_Rk7{DKeVBYRUgr&t5I{>|d?t zneO<;js(u7LSAfZdY&SI!TZY+a(m-PGW<b#KZ{$^fQHBd+8q4-**S~-VxPXXWiGh3 zWEOuqK=zfZY(X}2IvND>VE`{GFI2)bvt<2g`C)}d&R4un@3F<hk_G*0JwUTR^I+c? zj7Hf|mk8Uk{W^KN6acrsF)X2AJ#Q9DzX-;o5c;!dyfvx*3GuJ|+M2o$3jRjvuvmsE zCg&BBK|A7%22!!MYv1klmH9$I^(mK}@(}aGnqlNiDs))PrW=tTg?Vc5fjqk$zhk)g z3Iub1o_fQDceP_W*8vkc;e{}Ii5l6wmbn8W9}Y4#a;{@o-L+q{t(I{Dc{r*)f2FsM zYTnXQ$*wF`N$y9~lw_T_0-K3iB~Hf8{TRgvR*xrPZo4K^$`!8dA$ZT+1&Ka#tt#N? zbx$8co{`U*JNgIdJft_E(Pnk$jYooL6*J#;L$ejt?kG;8Y6aPWGhJSCnNE&IZ?hpo z)OE!(X7Pe5TzMrIh}%+uL2W$wmUx21`4Yvt+x7jlFNuzl>-3aNmu$>$wR||Q_aSF( z680i)EH>#BNqP>Hds$lkQyHj%?@PR%IsDJ%#FgO;Y}<1gL9!8h98)ZHmJXX~bsmKu zcu&5%`|aM3^Z-k7;er?(_j7sx>L8FwesAP^ckmd3A%?^C2C=*%ho%i|1-zuA9ly=^ z=w@BHIv#rb$nW>-$<3}L!X7pjEYBX=L#!|%RyN(0dh2)Yo(dx>GR}){++iB;=i5pk zm37{nt?ts%)9qf5F)R=BZLk^EKyF@gJt)14_4_?}wSoS>-w@!qESxOvewN7NDs<&@ zJ<%9@$DBJk&a_f;+?2+J-lBHT+_}pk1XfRFNcJXmoG^BvLp!t!m2`=))jYv6DWYf5 z#13?ZsL2@HH&GERe7rXe61s4JCKff{WA38aAsxpB4{Z~iA}jj^=i!8+R-EmrmX!7r zZ5j*@2l{ml=ldJCV`p5EZe}r@_r&(VMMocp@}k@5e-THJUuf$m?-Tpb*4RI*t$(2@ zr(bGHI-8EPFuxGW-`14>w4PhuJo&kv6P|i5TJ#*)7fD6!D6$L9sH_Wf5xF}yZtg|A zTZkg|RqxIwPQ*7IG5pePg8?&Cgf&R3)v8Vp#q~y5!zHc*Rl=ewj%cxJ>1nbv1t`fE z!VH8wiPcGSM$y*KUdFbEyfp1L+341d^v1oozKLXW@2$Wu5;X>(e)P3f(XcQZ5Jzyn zM^=~FtR{nvX9eoW6Sf<z$@56B8kpr%p6-G^(#G%(@5^J-IQi%gS8$#v5t^HQ;sT$@ zp`@E=typEyF9)#xBTne5fUN9Y4abOWMf%iEkZo1o2?5_AlHro!kij_7p4MD$FNCOX zxn_e5u@OPZs1{Muw@sZ%X>6QLa4J0i$Pv-~7L?{l`J0*3IaXbHqU2(^f?Sst0i@9h z4mq#fb$RfcfX97q*Q8>vW^svu7i!P?P7^tT+3u-hN1gOexYTfonk}_ujD`MnEOHUs z)?u?Pq4lWN)YJ?}zXi%!o(C(|r@PPXivsGX8?JZr3frlQi^*m@t<Mw-v^8V3mcGj} z^EzeclYcHRw7!b2z}LEDTs2lnWEfYrNz2<Io=WxtK5(?13ihlj)gfC0`;+W^eC5lu zG~@Zo_CpP#ftFpTu(RLBUVk>E6OiBDrfMUM4bPwW6)@&S@eu7{mlgneM;*8GD&f*X z9mHIZa)5i+J3gTGA+ShlZOUP4XiY|6l}b1AJnLX)lr1Wn;-N$|0{H1o;NcACG{c@q z^jN&SWpSJtoZp}Eczzy7!r=8qRaT{o@f>r+x{*A>msLGdMD-)f-;ejc5bt*ro7c|C z$Ip9oPsZs_DB>fAIIfTV)JNbg;>~&QT4mX2j9FVDW{X(H<l%lbV-V8sx-hALmq&~q z=DUSJi&3{h19)pDoBgyag3}8zopmO&91#w!T~TpJ<R3bvH%5zO4Q$p}Lca)G?S?6g z8lyKTU-OO~Oq?2t@wqhjK&toD>UJWvy@jzWj?~J1VsQ6MWwnan+%JI@d808L)4<aN zLv4JjQ!R2TbM;aQNB?{PsHOEuKwAjjw3{0E>K5?S37NXCgQh`y#XW5B0$y2?YGhPB zuFjO)hPNPD(%_c5!<O=6DmU`p6fp7BqvUoNN;LPjJ+J`cGqgT5kaf?Qd~@w+paIX# zrdUBpCaDpRcClY66A5h6ZRUs?i`J_<ZWCEs*zF~`EBa}y6`iBU>g3Esew6mQDfR@^ z?cHGBE1}JAwh`S`D$r3A=7=Y+057=(Ul<<R;XWr?J*ica=0%Bb1R2JIMsY`);2I{k z!(GXorJmTJI6eY-Ak5W@Y8)EcAqVYx^+X7&TQj|GNj}Rn55Ew?3eI^U*L;%o%IS7% zru&5F9I{q>?FM!dcQ)`SQ2eP@?{06@w-Zj5JaIFoYRidcc)fQqVT>9@Z7S}d+Vsrh z$6`pci9|M6hzAlCu$L1q`2H*ftESwfGDd+9@8!MG8Jj{ep<_eGCxXyyC^XW`n%m$d zSV2!7ftp%xU;gc?WC?$DgHanuX`5DIqB?iMIIp^Kve#GgSVVuk{69Ys`TYAgN`9fJ z^RNiZ?t$2n%sD@0p83c-L;Z?(<~8K--(F)5UsCC)I{b<BUbo@l!dcxCD|s~&o@RlU zE)(Mp?_tW5Wa8SF6J*HZdtMA+VtvU*)c(Lm^nr}%yBT9Yp(E-(GvVi*&y7#U5$=_H z?bYabcH_tffy#3P%MZdxVfyac;HE!kCIV0R#!V#sjGJg-Ci)aMEj}}we0xXf^Kb<5 zawc0BICPg%do3mTv0w91c>R-(&2cr-g`XZzHZNS(Q*r1vJT_?O+mUOIn{i!g%6%z% z#-Q!(yiwH^;*rBv+seT~+nmEAM>RC7Y&G0mrHB3Cu>Ao>&t)jw6YARbB)^7ANm%b* zh#XkA={;RL-Vto~PERqaq5@20!U??^oX7PyT2LLV+}HkkYuvmhr9Ie$j2W7E9096_ zJ<`lcMgsX14LA2{?As}YNc_r^M-nEJCRf;W*lyQA6NYEVxb07Ba+a>u>5OD6r>Fs_ zUxXVHwsXAN<?Ha|%|covy4#OaPeJT1u!GjziyTe*N=UcP4I+%dH=xMKqjJrj_Af-p zM=mQ#SjHa2BPT}WUXS*Nu`!hk-s^F*hlTFgNNY@GiH7#SF#^S#0T_VZ&&jW&h=ZTa z{tF`z*DeL}w?}GF<>=|15omGZ2u3^zNA2NaI}bJ?;Y0NoMJzI4R37-kh*?-mn(md; zXA@n}#n^qVqbwG7AMY%GXXKiZ1J1?5!t>ztNyykiy8eO&K+?4G@)o{-vi;af>YhpH zjeW>{un(CJ?x5vg`uvh6RAQ$XpWHq+z>l*C&V|6gRY5*^fiB24^PbFzA1Wh<CkxRR zLGTyC<i*%QwFeX7Qc4@Vs-V{=KGw0ke`r4{r_@*0Bhm$L=ROK=E~VNEd{BWERsyFi z@0x(OC@!eAJYlKId_3io9{IpA^g_I`41JpxC+5$f@}iqw>-l)`?($X|@XFPHqo_Q~ zcMt5v!krZL9<Oy5nj}AcMa_YiTER!9;2ZPM`56HI#yqqHoiB(6gMFCK=o>SmHA@RS zFK2C}B8D#ZcVy{|i4VI{Luo9t>d1c~{4o){`-;|92=b#z_WABl_r4lr!1?)Ll9B1p zr}pUX0jpNK_I?HCgwt)~cu0eWTBiA&uNYFUARS-RF4NtNXxCvS>;{9~QB^*-ryDlm zlR^l|slHLu?l$V;eiiHdbY}P-Xqdew2SpV0)66DJ4-CUyjbFmkhC`h=B>Srl9c2hA zI6MY+b+V1v;8btW4nE-o72yPMexi(5T;qC6Q*m{-foEtC+N5-XMGpIo2Q&dv3)$Mo zQyyb?@_tUJIwhs_cFkjqICsDS+xQl~pZF+Mw}E*^Mt6;*cOyc^yReBX-^+_Q((Y}= zGJ&=6LW1V*$*#kdt4InJ?cu;g(?aozohE2I`5PQ%I2?sLl$*`L7Rip5^ok}Y_NaEo zFeYT6mRQLE5piyWn9>U=^p7UPKmPq6uPN2<Ool%}d7}G}=ldzjbLKJ^)+j!pymeCP z-$nVKV00vX7zq7a7`>5GbyDC@7`;)9$F)Df=#L2ds6KyB`uu%_Z6J*}-k9PBtE<t< z8fUl~)|poBGpz@`aSGI%*a4&VfUGaL2l+aT%r~E3g^<zMJDH`mHLFnVKv3lvGSpO` zupLft0*meon7SB!Hs9oXM+2R_4S4}OU)0?RKHL=T+%tQqu<C1WS_Rsa-Zfx0YzVVq zTsQ=#5bK^wZ>NemckR6mQgDjn&_FR<YKrX~g!FuCifjO=8He?FaAH!vm%}02w$xRG zJj89ySiW-7Y@Y5{=aNt%aFgzPOmK~!nPLfs$B^bO{qEA907*c$zXn4euS1MrGgSC; zA&DBCh{Sy3dFu$w->i@2K&G<pg|JeY?YDt@ZC%mWlxJzutFDTP&enAkcCzi^5XBcI z1b&GwW6(E*9ZZ0POBvyUoZT}#8Ij=k&GN;(YC+h7jIc$+GQ@>h0U)I)J)yIOgg@f6 z<=^S5BRon*zOn2@%${4~gs_+|>AXwv>_Gs(rq}BISJcbrALa&vJC(_`SqSKRfR&pC zFPlq&qu?NAE&@N&cg~Z~?i2ssqSb$&f$uB+y(O&n9RuI;B>juDJ4;UQ{Cn&-{=IJ~ z|5yhL-k;AOMXDzFxl8a%1um{@KUD%iLKN&l>H#c&ylJt#g<Ih8V^!zZr*NNacht3o z$VYN$LHY;U@$ycZp3|S5M^g524#94{*47VxfoB0&tq<e#QW&(X+MjT+E-eqKZ542E z`&K2tXq^}0!Q+>@Kl1o}fu>(!DM0z}(DdhtXij`jLp#iHok_gnd$13or=c=${?q>1 z(nR{%L;I_$dzt$DNqPT%QaB|b*;q=cj<f*DY4Da5{z1$BDk;3!62OM%;bH4-Hp$?u zZ}H%z>+&Sh)GZcN`@1m?xPP?!qtJ4vUFCDtdOJ_cppILmGi>^olETQxVkzJS{9>x$ zp^46OjH&st-xN)_J+EdD-wIiyO!^V?qx5=kUx=**Z7N@Y_P%M@*1&dpcXHfbTR}Xw zR!q!rV}!ih<Tbnp+3})Tx#4XlNG?Za$|(WpsPOK#60en{Jmu+T)3(qN&JU7$4fEtW zcq?(>=rV2aq~AFzR5$S6J|E}X+CUJ<Syw<N30mzX8?mSJ{ZQD)-TiJL@`30I@lsov z%HB*pbu_w(ZftqJjDd*Sw<L*>Lz?JFfB@7xM^rw`BX-c6@E}r6x8jgB3gr|;JGWF% zrC`JF;PsK}(8D#Yaj|6+k8TQSzRr>3#4p?NkY~3ZXO)ie+;ZORjH?Y7+^V|4!WLf_ z!D(+?&ZsU1Q|shmjW6Y0|Myq+iKY`jhBJh}AI^BCj{mRE)8hX8w6LO~e&tU`f4qh= z7V={Do#{|L!X+PK@WtBha0NcJK?7v(>(EBB1Oa}BX!+-%4WI_+Z}jZMC>Osln{*Rm zez4Xam9?*JaE}^CaB!Isc+~Tg`S5XVT8_Cg3$UG%$-g8Z^J&e$H1@t+^|I!VtcNee z=L)CqSr6%~Pt}2Pwr5DnI0?YCC=kgVhUxlVpBh0bUkILyhgj>=OJm<cCGSb7UGIW5 zyz67Lvbh7D;B}xSD19`|^vw2!6kBUzvLLbRH#!2$1MoD$+e%~Mx*6Kt*Wt<N^BCrn z^K9OmLI@^#M}-bk7Rc?~Ny6Q&k6f@4cX@lfwkL4(x)U9FMlaA1r>+!jR40Q>Cs#Vc zmwLS!TV<8AjbqCurbKZ~9`d%BEs5H*Yr@xp(tII+HJn*d<>J^JuQ%QuxFBJ}-&7vU zs-wA(!e983Tzhe##&Mnv*?w!t^A*`c{7J^b=Ft|J<5$JthEbO8EUd3qQM|sclK8A% zl(V$5W1~M47K2H!1Pn~&Y<BOghmqhs3@wVKzfi@C0j7R1z#d_Yg%X*|J{n+4C<7SF z-%PNR%zQ_`n_9nguV33Df8k(%bFW{?mA^fmTGk0bExu8N;eJn|eD|Mb(;F3Xz042! zXgY!WQopbs6;}@e<$f{ZJY;(hkTG|;;A1c%b`W4t@Y<XCSiBlzz-(L$EpXT1FXg2O zn_M_EdUOO8TI@fJuV*Xr(@X?LP@&Zi)l`0KPksa<9-)da@|CJzuqpWc@@B^3K0OuO ztKs=vhrZOVjoU}FC`-n`h}{YdTY~rdJt}5?nLY)c^GU%5h?NMu0#>`wKQGPG#3$qK zd(&=_$=@*Ue&o-*zOiJ2bMc@)*6Tf~tyV!!&#c_A2lRxmNjEWhh5L>)?G_H^^<KIv zHX4Uf2M)-*z7DpNle3onoC!eb&n<tx(ZF&l<k}{Es%mssXeJ04dpm*WyAG!ZeFy^c zZmJbphPN0(IhV4XaK`PtL%bqvQOK8l9=496D>7xiZ=h{m^vgI?#@H9Qe~Y$0B>4fZ zm_w&X(Tets7Wi5BvX*d@<ER@)@GN_hUF}4IonyQ}SXvqaAP<c!q1jEbXQZKh0XA=< ztzg5HObM&jlcw_ZaF`<m4%wND*ZHd4UQxyKk28HkF!J(3i12Lq!<8KFid-P8D~DC# z?G|n1A$Qu_9wS!~g0l8bf^cG8-f;5fk%KEISHaP!$VFfN&<`89o3N=U*f7~|>T=4m zgmt#kA=PhNhqgOSDEIkNXXkjQmQ-<IPe;X=kHa4?|MOxJJj%WwH3-lK`^O!XWV#x` zCp^E8X?)`LYva+Qw|Obo`b=p0!#$7U|IL3Vu+ID_HglFpYW5z%P^sk;t3WxG!=j=N z%YTS3!JXJG;GPZadx60d$X*+J9wh=xan*ONpr7tp+GZZ*pv&BQTb^GFpO>PCbg{AJ zt(+>4!e=G@eC=&FzBOb1v-|OgqHLB8`73kUyA5vn!GS=}UA_1?*MVb+>BNa89^EVw zM+<I!43R)2Ew1lMbXsvfy4P+U>5&^}m!Ab`%We`q-HTHW4$c~1ol^QP>TVC$qH!d` zYSr1w{yJT7E<g!j+B1+2pR`svqDq7wjkapAKH&PQX(kS+!9kakUWzVueB`aoNy8|* z+%Z_HHUv?h5pfshvA@+FU-vM{ZXz5)2khe9jx<NodI^*{A+s6{jrxkvyjuxR&_3Z0 zO_wr+d~0{?iMHxcYkZu(ThLxCt}6q|A}1+On>2T#iM$Xe-x0IzEoCZ#u-wem_NZE) zXS%<)SA1=rR)=ZZT~)LoWe2USOE*l^aTDBgBX?w_25<mW*jZ?2f*M|npb>d+j2}nk zJ;3&hq&Sd=4@&Ws4W1uhW~dKMt$7<*JT+^P8fHl1f|UNO5=l(afIJ-Z+s?Fm;1}-J zliJESHI~lsbCt+qtGIOgo(BF;>iEFWDhubnMlB|0w`R2OWv9T<Wf+6+SVEW_2uoFt z_s;ACH-#ncmS;$!W~gb7D~VCIYXB&hZNyB$m>jfuv(Zw>)2xj}8QZu$h5=dLnTv3r zu`|HsVywv;?L$bych)r^^wSIh1LF!oYnDr4#-gp^s;f0XCE2FejxeMaal5Z?Y(EC< z?oh;=dE8M{LP7iv&Et!B^LXp{LNvOG=7PPtd%U@8d|98TU`GlB#;#gA<XWFoW*o)0 z<t*fCsj?B3tnWUP<#}Ya+Coabb8-n8Lcv|DR&yp+5`?fv?0_Qco#RnQadTEr=-psr zPOdp@mPjlk>O*-R67l5X0s8VE_7uT7!RC9V2t&x`U#JvE;2AvVuBh{3Ecy1&^r7@? zefTW@Bqrr6ZDtUoLg(LUv*)#cnKpZ{C<@S1oBdY%poLzy1?6MeiQe^*xdq=nX|+0H zJNJmMwuU($Ivu6O3BsCzq%N1Cs_V<?iU&?3$cGBH;qd<>?oFB;)wXoONsZj9$*Rh< zdS&$nH|^OQ>7~(10<*dUdJv5O(FkTV61@P?^Xm)bO}Kk_oQM<W-pau-i-XX1wAp)o z-`Z=fZ;cm?b@avjphXdm3q9Ur0b12{4+>jq;-quEo2m_1;r-^mVKO8HCa3jh*YxD{ z6tjMp<w{KXGH`Uea&kL-p&}K&fylgOlg#PcVCRKHZqKr3YQJU|USys=#%4a*g@t*O zQ$}tLd^SN6Vhyj-#w${ztew6V>AzZrQ6_?lxR^iyDMYB7aBa$GeAkgy`9tN1#U4&j zjZQ9|FZMTw&-?JAPrT`#M~=OFE_N!W*|NKoD0`C5FY<MF_7&vVVcCw%sU}pO2m{*O z?z;oWN|}sd6Ydo|<QAg2){-W$-If8Y28Uyl^t*V%a+E+d=y=SOt}sy`IY;!<e-!e4 zwt^|VmwU3#Y-dwqUMawW05A(A!?~XBbQSIIIKuq>>Hpv^<+DoNOZC}WZN>;+WEMID z`{p4-yldm;TiD={UH+jh(c9<$vfS|2bNjVD5^b%UE`6)%qKQ<uzsiyQ>Pc1-@_Wzn z`Y3BH)9|LY@HitdKKIN0bS)*R_r(RP#@wU4z?MnvHPE8yu+>{{$B>UK^A8P>-hci? zy!H29+c5nJA_6yq<IME3WpFY`x!m2@IEMLjngxD{X5k4d!&u&kG2KRLWt<qE5>n5m z@ZxwXaPpi;HzO@cKdcwXxN+IG(WB~4_tEim5Q)v@5c+Kd9NVhm`9jVqjyBDk2RnKt zbOv@GV^Ag8$~sFH-7lu&1QVD1(qi5A>fr*B`kA>QHA@L^gkCHAph@%|%PkQ@w0zzL zhr*B%Y)5lvUNky?@$R8>S8)MkAx+wVIf|MaD=^TZ1#%))`mxwH-LaxGmyj+Nkrp~N z>^Dbhuk)Ji;n@m&{($kkhc$eON5_k_Q*zljhWh1@^uFpvW^YJ$j*v?;(igX{nbiw} zc0&NioETg#rM;{uRlg2YUj}xcZC(uyBoF3~49<t+TVvVymntq(=eDm0E2BvkU+7fc z$#>cK-Uxkp?<)QMxci64waP`Vyw2>;*6Ao(lzH1`9(z2g7ph8fQMfG>V!PMoaiMIR z8`;!`ikOVI5H<g_*@`}7oto(sE5rTn@=h%!^9&d*lu5uY2vc4)u8UvXYPRLvivlS# zT)8!92qx#d=tSwXD<*BGnkr`r`Fdn5CJ{O+-`ySSMC92n->RV+u!{j~E}|LDKD#)% zzT4;Hoi#o2mU=D)7qzK&QU(Y-+@0<=dOK=iTlYmSp2-nCwlH8Wzakz`##hb&;*sIr z2~X|REbIkcyu}LKg?DDV9~Fh%Rc8jA&s*Ujp>c6OU(VwQ!OZSvJ>lCcV^cZ<LEh{i zcy^Eu@v{80bF1*Fd1^1=CAC0hR2O2sz_v$}L0`!0aqYi;Zww!;g|*)>hi^X6VuJLy z&;Rw4IgI3&Ls!~eBX`+Py8tj>)HtmR_>axdTNTt|hRpY$zZ#*n2uLBn_1j#`-b{cp zZN5_v%RXL9KR(MZo5UYJ%SV&AKFg1qz<hBS+N%t-;;&9$kI=#g?CU1y2um$(0dq1X zlcQ4DX6%k?qFdn44&yt6FIg$gyUQZj9s~|h3<+BWGH!lzs_y&6d&i=*y2x!m0fTKW zhHO+ZJivFOT3@NF?UVR<dpeN!1hOwWC<q#a#L|M`Q%fEFWoMaG)R44sWMVE68M&Lw zSu8|nGf*J|<$yo8Q{C+f8uhoN&DSo1$;#GSM%Wgat&co)fHJz-xzUiYgw@y4h=4n0 zqc#?HnW1_YCJv1dm!QEb=f14Mt)$$Dt>EsTh!Z-<K`Dxx@v0Equw&+Fh=ovMZ)MeF zfX|1TguxEdhbe6B+slEkQB~-+n@aGw@^pD3VE&}maSNP#W22vh_&1WFc?MN_fs!qS z7vbG*VSg1vT8==Nhwn0PzPNW;QLlIZnPlkYV*<E9;;vm79ixu>=6<-`56$tKE2wR3 zDN@}+<qmElbAKkv_A*k4VBUOQA^|KiL<cl`F015OF_;D503`}_1&N~bCfqrF>>k*J znlm94NAPCd+)JLH@{!xJx3<^AqYWD@PNa@{quXwo^fg}sblKgG2Crbuj8RO?3hD=v zS>63wI?j3?>g_1#V8asikXJSeb@$1=HdU7ab>=E-bldkQV8eJIG3#M@mXgHAa|mtx z(RL5r1s1PZvX?VzgdAcv7sQ0ey*X$eTBnCP78bKCVA65BZx~|nwDp~oja1g#pmk0x zzEh9wiBd6M(RWwHuJ}{#IiOXuJUT<deRH2nb$<Ha(L|{3H<;GMWakr$vHZ6;V`<)! z{&H_#$)ouj))F1~w~iy$2Wx5hMZSu;&F=~1BSb|&KW)av{+6}8$mH;9p{)0xTlThE zrGB1>0Dd8E&zU*kTE3{PLd9<-JHqzUwLyQUQMvdUuPT)(VZNzUnqlrkZ+Sq^b*U*4 zyvb?3`+P0qu{!VU=D|&Q?#E(0m*@OaagOL%PlV-B>DUq1^Z>@DawYqu^;_?BDZ_lS zC6M6232=hU<V~BZF(-@^XR&@%aIEq6peBp83^eXS_7-*?_lrYw7Nc$D$|CAcGb!X3 zFi}thJXO`?rn@!|=!lKz#lObxvbI0xtg-fDPLGj7f*1YlhXz(U%4TBObLmp*Y>nG_ z!0N1H#NxC~AxHHx+oOj*JTcC7ijnHF7N4;DOR>8~mm+Luzt_YvVViwh*UMHd?tr`V zP|52OK~OY6)p`cn6!-3$H2F-I`j&%N)y(|?<~>AfZt;z)wJpywk_&k>V8kIE>_bdy z?s*JcF7x38=<vSPytql&OBH&S#{<I?QMmg+*P_i97Y?*xn}|am&F%HvYB?itn#~R} zWw5g1DSNBF9h3})sl`A9eXs#BXk09n#a9*O`ZhfBWO}yCR>roIjAA{dW7|R!Q6Ivq zJ#==pK3g2Uwk<LZp(0PegCrkP=Y_moIOm8Fh`YR{{D?~E)hzE(Yxf8z2~$p{rO%$A z!ye|jJCBxDm3Fk<hR*V*IfLQd#T~$7(nZ8XRPZR;6_<(cF%wZH`)V7PIE3kDk8G{k zr63b5>&kEK%G{2sXl<smf7KV%AQPNTpwtvb@3bY|PUI9<R&b4Q$28IFD?DDz=tdyJ zbh;GT)wY}JSaZc8h&Q^gv<sQP#a%m&_>7!Jm9x+Ib*m5zwLf7;5W6L4sTNrwW7hsh zc(hpTA0QH|)O)F}2U<#mL(YO{4x~Yhc2eYSe^Ju^M|b}Oj~3VLQ+bYjPK$AWzIF$l zn9e*?)8|Hy5h}jQ3q$b+3Yd;fTBtf}-(5iu7Mt(gv?7hLO=n(}i^ZTQxy`vG>#);x zabUpNAh`t6?zwmF@p<FMDhO>GY#iIM?swN^JqSY@7wn$4qZqC|uMejU;z$sq7xz8~ z@Hr2-<L;pD0fR&KY@joy?aM0TCG?Fn=4qkggmUHqv27#jFjU$coi(i@`d%szyna=D z`kK?$%}O?!k>0zriLqhfPi9Tq0qvOpI4@4k>^KE#s%(gKk~hNH{^k-6BrsyS$Bl%; z!bTkLFjGzniL@@_qh~YLM-3IXdUCH9^i*$t(go!W%Qx3Oyme{Kcf|3Mfw%GAL72&9 ziOOa3tpS4|%|=$7=<cKdH$*FroS1rxg%RM>A!ZHdtc1HgavK@2Qeggmr?b|bP#fw^ zB0}wW=J93oAz2Hy#ePs&hbv&36&Jk7O8rvRN2sFFI~>do2VQC{Gdu&x=$%TK(+tcJ zzSjoJNZU;$Dj+NgY(~pu5leEixay1!T<=XTj#v5S79Mx!5*@&Ag*gst0*#fcmBmD& z-~d7T1;11`!i$v_u@d)$!56K4B4`#p_(XEPiZXsZsDQd-%nI8;=S%Oy<IIHZM(BKq zC0*0M_ao<JQ-3<D$9n{vu3cX|{oOx|YGhYCSDf~0d%jJG(~aJ;<gH1gv0hMt`+~)s z!oiztTkf6BDHoDvI|Iq^_ox5sOSFG@N5P-R`;T2?-ah{?U*P?lhr~X@+R*7|n9u(f z=6~p|@yD3|hh9?potO0XJdfs=+%I}bUp~(&pZV>H-apTR=|A$6sAo?}SUsgjw;F1; zSc#mB{RD^8XfBISyj;csG|O#x)%U@fJ?*$U<lFNdEDJzMp>rK}T-bs%gDuZs&w@l; zRDXw5h;pfGdx4Ri$eweR(n>_Pb4DA8aT^#(#~yAc>Jp7`$>GYJF>Ohji*;n#%>uU2 zK84~~QHGjQvu+r;LddkhATHE4?~fo;VWc9v!E_z!Dh8w)Kkm?A@l@7>tF!NyHfZk< z`52QnZl@)t9ct?^O#8Fu1PRJ)xT&C^UCpKg!JQetzzKRd`KQKeym9fdnhWZ5=FUcx z5eDXNGea0XsKb6IZ8;7AJWW2FaA2Pv`>+n@vj&&Q{T#=&90Zpx9sDOkRNT8Id$o;U z5{YcwSrjO|YTe!l@A2Jgqx;?2HM!l38EmWIhP$dQO?g!|r>L>m%EdxQzLsy6n*-NE z1?Daa@@~`Xd%<FeJ{Qorfr^Wlf|2DXpdcS~i1khGuEJpCosB*6L`;C2<M2G@*N8gK z2RlmhoxKP9MDKvoH%b86D5-+cm`xt-g%0m_GQdzBT}12*yL!6{zYsUKo`--T4q7F) z##UAwE!Pi9a)PwZbbOI-Is_=vGZ0&bxHonBPBs`@w8AMWgVnx9*Q{CA1aStiCR&!@ zsjh>`&8wlek0g%zyFH1ISCT&jvSHAB7Hd%&$}&nSz{`X_5mF1e_qK?YJ#H%DILp~a zCduXJH~E4PaWJu#HrWYYOC?Nw5f6AG{`i1ShcW0JZ)y$R0Mzx&9mc&egZdn4;upQ7 z|M2ebdr3Ra4|_>3Hj4AfF;QpuLUeu(l=E&kG?b-?K(x0HEpebjZ`w8X>AIQMTBU3* zPk0-U0hnF4w`&PB#m2fsb#Cz!5I`)$GySe%Q?yY6HwBbI+YNA9w<I<zytd3F#B?L6 zdq@|#sIli}QLDDoj;SMOxbUtSOo?L~X+#iE0)huza5tT5)G~Cf5L%D!%ifH(SEb`I z@oo+W8CbqI?rrkEDg26J?wpSEV~I9td7tKbI`6t}Dsfsr2g<>QIsQ~G%q0T&R@IF0 zsQY{EeyR?kEOub;lj1?WGPgjiY624C8%UKdL{Au0UJ)Ei@peRw&U4lnh`BR2YLE4& z>xe8!AYzqJT~Jhlb?HvRD_fa2Sf#6K6`!lf+32B+W@p&oYPj|1H8sYXW;D1<u$;y$ z%%n7aE`<~;T&6q}g$oIrvo7t9keQ4X6;+sSgXqP+AlF2#3aoe{pdrJK@u1VS+bF=L z)rFuD<*O44;tX=QXzd$FJYAw>6U7x8W=f98FzGepUbtz8!?L1|RHB&nK4iJj@Od^k zl)4gHHGC~dyEfr=vu!RpAS-Yvo-O|Bsk(@u<i4i}uh}>@|3vIODYzr3!;~s+#UUxR zW)<CF7pHkR@NTL^Ft<vxxApxX7|FfAZIzShnwWVz9<6LxIG`pI2l054yeu~0&8%3~ zc^vz+Dw9#JTBbN7yWY-bkKg$dPmP;<B-mi?YZ4S1`X)I&{WsH4{=+;m`rp{(-n9Rr zT-u?$XMZEADu0pBXjbi|^b6E|^L8n6^>wv(9T@_H&KIwhUuELh`<ki$5J1(@LE>MD z!)TdnmX~EIteHP&T7BKbyD_*=8CPqbSa8!P{#imy>3xH_%hX#aCb2C2C|Q=FTL;ct z$VNy(Z~adN<}E?(Q8N$>FCD<$m4JY6Q7cWCP&t7J;vi`{I6K1$Bj#}aQHzT53q6o; z#3@>dlliXC2hR*!W34<Em@)PqWfB^^Y6X5fo>-`ZWj)Fj9zWzc+w|-H#nLiJxrVFX z<rgl5Yt8X{QG8p=_r}G*Rc~E9mgg}N$TC6eq!;<LP>%&J-a5`qB>7tK_>!UbqFtU1 z%1mr!BLa`KynQ`7ez~^L2W8eC+U37HAL~)|^NjrNd_0KQV@7^=KAwoz8Tq66_@m?N zwT$pX5A%7m<>D>vTCq293fo0Ro=h9bRLS#&zNg|CR)=y=DdKc?0CYRS%S<8}-Fa=5 zYC+z{`E4r^KF2HZsf#yrDEIF1SX_yD$D&k9o0o$Dv7@xvV=7RfY)LH!CUNClI8=00 zWLcQRuHrG_z;9@>&}@O4jgxm`TbYcn5F>23$jTgAs={-3obO;vtPa1!7Ppkvr~N%f z$B@}GS=C<)^VWH~HTrq4?F^PbP42*o*@YRb2BaK{Yw0oKhRdyA)`K?=iHh|-uuHXL z*21j2KY@YMp?G^<@GI#!pp0t=2*f@Q4&q%Ww}h!KE+H&sKDS%;#iZ<$h>>=?(*m%m zz@*5ewBwX2#$X}tp1xO?{4JZ;WroN>Ka_8*Z8CW7R}QsVwdSU<kGd{LakDF-_<Y3_ zHC}#2tYI?qJOV%Hg3LIBE|OHqP@G!4S*Nwp#hZ0{GfpRbJ&a?}_F?NXw+e2{C+4fI zYd^27&Or{%=auzDyi=9Ru<0y=;Yp7sNt<c$UToc?a$rq?GC{|}Y9c<z$?gtZZC#&- zJJ9pU0=$A<q0@xxJ`hv9am06fYoE(1U3)}};7rAj$R?dP`<gN9E6#S-X(9Q8w3m&| zW+%<fVxy=YB|_ak9t}|!M94W2u?wGJjOcHQn&d+@rDuk?#_rJ~+pP*wm#VGL7~4Se zG77q8)?QVJl|4)xyBmnH^uoC+RI5g|#$7g??nbXFV^S2d*OGT5phTI0PDPei<;ewW zd0CT;9b%`W*AP_TNw#AuYM&;%lk2mBns6J|AZpq-<q~dy`wc(i`o_L0c#k;3AaSPu z^zRyTS3D`7_B^|gjnk1wO_{LoyDCi@9y>8l|3MU&`+|ReJKpl-cKN9|A@`M5)0-Zj z_+F3ido@01P+kwmCy4xizqs*(;NVa71|KTi<@7U0`YT8eR!6#gwYGnxuLm#g@hjZ6 z@9$f+wx92OuOax$3W78NuPIL-Gz7<^vDS1v>tZ=y;L0~~$I@7}9oJIyAE-N~1o)=z zXnvsr`BKTTqVcb(0{%!7a4l<kS3E05{Z7<ZwBd(MRu?MfmN&{9b85QQ=c~tn{*~Ck zIUM7Xx2(-go~`}ZigdrDxE?0+8`GMJd=SRPkMBn#wXJi``8v250QP7Nc6AXg@<N>M zc_EdlQrhIS(p=I;-~(_*c<z3!>IE6lnVuw^pY}I$_IoG2u?wNv9ds>4qM1{T`4~lM z;ih}fG#Y!MxwXt*cXSZrb^!2VRq_zJm4eaIw|R%_aYM1uUMHqt@p2CO9?h82xiHu3 zH6;)=Cl*H<9%sYb)|@$97XN$TE^5LQS2Rw|iD;vV!V7n}=y$`|sVWxWT*EY~+QNc! zPvlz57G85i?tH#sF5BzrI>=Oa5NV+dGZnfu#e!{<>b=TSe60!i+V=Hhaws7pZV9+s zyx4+^iJDc1IYZ#A$~gL+Cg3u{AKd+374S!zfbZ=$i6t#_<>(8AO36@eAUhA@#92_Z zY!J#x+<4Xr8^R{gH4Ke$>b4fMRjIGTK@5hjH@E2Vvfu9@aU$I$@uxd$NnqYvn{E>P zT2E%O*(2o6%m<G2&I?JBh2#1zq_pCvai1+~)3O1bEwRNLA=qQRM+gDlrYcH`qjqhP z<DnlhHo-2+1Vx4ra)Godlf{87wN+k)Fz>kN&K74}JFq*-7ktiKO+8EL<z$Bi8r>O+ z4l6&JZYUCoAr^;%N5RvET6VYW^NQom8`r(+Z6oVfWHx1`9~(uun_hvQLY|7jW3l(C z5W!QdBXRD&$4~`0kGrsW`u9dHx-OmZaAD><Hc}#KHfn*`6&DDa8F&BZ>^J-uOzw+l z;KSsiziV>;q`{niVlY=J!Nm;9bo3_-=C6ta{wVtRV(wn!kMc<BI#Q`7sNv-_YQZf! zH;vNnaX!G+!7`C>jOJZqJFM40XsZm%!hpdYiQ$*qmFKX~K1Sz@%+pNT90H9NR0E*X z<^*gz1r2<Q+v}~+9Y+|!VprU7JsTuXgxI=#WQ9zWyGHLgCZYA%6**;gy&t=ng9<42 zB1uCoL^faP15Yf=rE(MjXG7aBnPnyqW328EO*ynD5JHAkpy!TYB?DpJahJ!(T0G?( zIX1eRq*}<dU=y~fejD%S0*Q<bzHIYSxJf$ckiLbIe%8aoc+U>xhGjFf+iDrJIqNsw zxPs_G(R+$GTEawcj!bi_Xe{3^r29N$A#yoR+>VOHM5g@&C0Qp7s+$Fk+c56i9fv5o z0Pn%N2P+HiVtk>@U_@TfT*4Mpqh9V_cbg-d3(lRDMv35ZE<A+{6FPEBKvzDiGx6Xt zB$Fy0*w05@w0BqG0Eje_@Ak_x+^)BsXmt)IC}*43QsvOt33Hs1B%PCcl?S0PZ{41E zg?y5IZU`8e^oC+o4iMNe^<u0r0=IXzx5Mx~S0^FMoO60dd2WdIqJ$j%tzJZ6RKdq8 zPsNP^+o!8#1w{`7k}`AdVrpP&&x`Oup$|#N$T4{n<kA{jy*cm102`3<76ewmyntYX zKs3}5L4N3Lti7TbaX%tY#75dKSP<y>0Zo1KB6dP~qgkED#A05n=1S^%=k;4kqFW@8 z%RA0PFv`cbS9f9|o4=S3!!i&%*b&+x$IA1VXuCX-P&3O2ta7=0uSE0p-H&@eMIRsX z-+w3$=)w)xSvY%~DoNg}1qZjTkw|jqU$R3`+@6S0CCxl)Gi=W~2DH@yNjzVsFdw?J zxE(<GaBdS6sIRSXL3GpWqn&*|2~*x*8C%@8%f68K5^}gfj}BM4b`Pg*(ih$Zt~RvO zO8f<I%v5)VZfiqQ%Sj}yC1TN>FC9|c*f|ImE4TI=;7NMVNvIbn2$^NwMzrcso$bx` z4Cx#8939SP8jsrmk?OD!r`ZdebA;UxB$}`%f>I{^R;L;?nn<uSp-vq)qp1^Qquw`^ zNSvALk?3Uo1{wh1meb5bnW^pX*5ty+PF{AFkj_+<`XP7O+yGLV4-M(cjwRoeR7G}% zJX3`MZ1r}T8G|}pr-cMbMu?P?9Ua|PCKk6ol~4A5vbKh()so8j!m$k(=V;k2W9{_@ zdgb6j;_GGG-oPzgi3icV!0|qxO3|I*1;mLo(zb*WLrR$|O(57599Iz6%S3g+J-3R7 zpw$@{PLqYorTv)`CG18=+AKSJ@NS+LI#dAoJ+X28d~jWf1;sSJa?E_45r(l--|VN2 zdpo9Xcjv5abdUs(^`U@O)7`f6Ep?#*gnNX}B<dix?oDx2s)fujS1?F5Lhb15)kW*j ztuxB2c!0sm{_GsFc&jYrMAsMUv?{sv^$Z8%PV{%)iB72EdIFAMIdazmNE2pe<w~_! zU_bYr8k_<A92XG{cKY&q%%1Rur~gHFup;xsBT#35fBHYIr1AcCp~_+b?%?v;JiYne zt<u0}=l&6U)a{d{kguijk7J+7wY~ES<$3ogH!YqqlwaeG=4(ami6jF==^Z$K?OOPz zE57(6hE?SA;>+Y3jJGQ{c*t_C;7q(^bCxR_mTwIUH3oGcLk9t}m&lE6vXDyz&etXM z3x)cE1t>42psU-(SXpjp#<zlVYLttYM>_boRjVr~w$Bf~W>d1S`X1&&PL$b)mFlfo z-y6;^ZWQEJ!Q$$;rm|{qJX|*A)#3S;Dxa=hWrgub%kW#Od@6vC%9fOw?3BE(&%f-( zzwisz=YMJM@SDHDe{MJU?ic)eHYwFx_}1o&-SP}@K`lPmRbugmS5b;Juq0?(8uURx zEy6U0v4e1?>*i>vJP+5>AqDTs)e;()Cy5MQtaDzm`-WDBM%Rywy@50l^L5V>_s9;u zt761M5x`__qzZ71@`!59=*;&mk9nn)523Z}rFyZNr@a%g#Z(P2qIW~=02oy7OlqF) zprLN}xT}XT1D9Aqsqld2>H%NO>c&LC`+~JFj!#Enp7T30viteer7deW-VJY@b24xt zKo+G~%63Or0+6AbQfxTxk{cLG7~}?A_+LL`qRN`l@ia69mxH-kV}c}Bmy5ahBV37) zqq5((8)axiAT9EQ6=i~(8;&2z{<>zhhUd%9Il#;oK;Me%wUqp!O5kUw>Cw%W`xx2N ztfL^S>NEfWEnjBS-;n4Kp~{tRzc`3ri}0T_M9%<AUe9AdN!cA$?2`^ux3g#(!yw1S z)XItJtG0<8fZ8J3LRFA6Q|5M{KM^x^YCuqL0uLnxuA$2kbJ{7}T`%UsRTWf~sh)LI zB!z_eAVFESy><z3^*J$a17BTR@sL+7q^t@Dws-Y#+8Aa_rp!JGa5~MX!>nP*yx&yw zoY`U{x(5Ip@!OdbM&1cCwh5nzeUHfaW{*3fJzipW1a)7#k^<&)#TnjhuSd5Uq`|~E z(_xC!LYq0Vb2M+yW}+OZ!Lm0u8`^hknIL?mkd#_K??^5Iq54LHIHFk`aPxqYD6uU; z7l-{7h$-aKCBiL_$#FMH?bE*_I5yv8AiaCLBP8q#V?ijklA^Qf@Q>D3|IMKO_Q)8a zF1Pk_{~v$jF89K_P5<L>RPHs+-{6<VR@f(N$LI#5y>Og0a>SoFwsmd`M$g}_ljDc$ zkH&-2i*Xq(Mt<pfuG6b7!K!-hu1&UII;}lCv&D-Om{s_MiQdle5joOfC^^wgvbGU; zwM_0;18VRX2g?`aWX-%}Dwzu2Tb2W%JD&66GyT_Rr^zB-^c>QcK{}T+pYh^Rh1k4R zAyUMq{Hi_Z5c$JL#LIMcEr=qXcZ;S8%n%BW4JG0EaC>*1|8V_VYfEuKVsr8CCh|E$ zXK`pB_BUCTA%zdM-JXp`I>&;GjeZNM1QW^o_CZ*l;Uo+K@fvK(%`ZlL?XNyPGG|ZO za2P)7%4H-QSLp}g36p%0PIw(<o4L>JvbKw7J1@TM0>|c7G4kiOkmGaXT;}cSpIKpx zrTxho``HqEZf^Oji|ng4_Gpp&TdVAKkv)C2%zm-aZi()W;PNX7Dj%bPQF?SB5#`R^ zjUua^vWJ{D@K{_M3kh=Xh?t$Erj8*Un&HLxinNg<(#fY63uCb7+?x<SUgn-Z9F9?I zh(w1V{(c{zN7?KM%fPBTXqByZD{TYxPn1<}m2MYH*61y49_VA|)<+8+C-?I?Js6AO zaff-lsXb<G+w+;hlvf<_X0rN>Bdh-G*GTd>exqfMvMKIFaNLFw$ORTY^bvn)oAwf> zS?2h`7NtVZB{+mUThAW{L1+2L3TX@1SXj{el1AcDjY|@Oe%egw6&P`d@CUMwbUTMD zP)oEi0&=d=JTVD2wnQI+CnA-s>*XfKvF0D+K4Q}@-5!Fw7oh`pKQ^%M*!uE1VVseM z4WD&X76&yp>IM?SrBXCc1m}Y_XlIOg;6)`SZV?;$U_L`b-rZe+O+%Ik^F5$2Ns1V@ z3se9R5GNOH3YQO9Hv6e9M;^BPS1soB^R@f~quBfzqxkCvY5v+E{qDp612&@MfrGIU z7Ab<2y+{&XJRA=@SIdf4<A&=+>Oy!v(jFk3%5yoUUeYSCiBe(AlN2B-m!Krto?Cl$ zwAXz_2Kar~R+nnoQ9Eod-Lkg5JJTA0fW=}i`!0<eq8*%~vPQu&Za4dVC(BoZw%5k> z;ys1H+l}jqaucu~6drVG*{Ox0g5`E+@*(oa8}Z9cKsxx?AY_MHV{Cd?HKA1Vl(>5e zVg*}H?cuaH4iuUkRv|mXhj7A^0lb@!Sh(L@FXvdj_goF35@NviD0da4X2mvgkGE*b zFZZe&kcr|#o^O19A&>9?9}AGgl%XHbmlWr>lu&8gqXKJj&Fqx+0E|eMw2<XEW>QG{ ztevRvw9nFWPvZ^v^uMm>!(H*J<R-~9`pNqw<#(mWA~PP=WY*n+mHmPWYwNt-0!E!b zq3^`rWP>u5{C^H^I^>_ewY;b4b@5+M-%6ylWs1Klk^bT-J}9yOspoiojD-=rHl<7n zcnz`1v<A!bi<-gw@!HqVp#3BO_96Tw@5c3g8_2920@P76V0q~&w;{ZnD%cQ@7Pu62 z0~s_PjfI-pMORALC(3DeaV!~y8%evdVV~WP4t3ZbLMapN@`<>p@vI?T=J>Q@%X92= z?&hY-hjVVFv0|5d;5bStSvHu4!Q$AZZti6a3L>=Ur6{-X8dD0}JQEHWezBqfB2Rm= z_J9<lk!^B=ugw1BUKz?!DFzrSHYn$EgYL~U?00B>_Ev?HqrcCx32^ltIqiVDuy=rH z&Ok!1?LlL<!#8<RpM2-KwT!)1vx!uxqGwSlyMqB?P5dFms-$*|7i(NFGHx1`2`t?C z4yY^6KJQiKG+S+3g*X_^EPH}?c;3$qEI@VR!<DmtB6ur%34qClO#ZMb)(<P{o`_CB zj{8kub`8)q%<1Gy+E%Sa#;;*iIA2c?f1%=y6KM|Lp$nk3u&T53Wy77Fj3=~(of*dl zuZyskLPG%xa|~qDuOdP03UcpTDOF^*jWY|9w~=>)INgtXx&((@Pf3a%Q)8Kj@zh7$ z-Hli(Y~<TT?xn+2$bd4_o9#sD6sx#r;OG?EE(^B~rOlCIiu4{)aTPT#(BcPEX>Bmu zX3tWx;cN#0bb{OEV#(%3Chv|Fa%ne0XIZNhb?UiohFg`Li7CI(Opu<o<MK9vtDpmV z<&Woyv34|HjhI>QaiT3xHJjf!rNaUR_8a@wM)K~UF>y_&XHm2rOK`R3>HpqwL|vek zsHw_5NsNY!oo3B3d-^X_S(IV<x5Z2S+v#%eli<?)?fy9E;y)d9Wg+!$iwiV);8%UN zm7)j?v?{Na?ke|s7Dk4CPFf|2Kqet2mX<xx2Zc6rDdD`tcx2{rVuEM>3U8-(Z8^WC zkkK?5F9wJCAiMT2tAxJ!O^5C|QSK278tb1$yY`=WIj^ORza|*GWDOF-Nq<%6Pe^4I ztn;{sjN0_xv!RJ_O$=N7l|v@$<pO!!i*G}dEbYTk?{e6ql;B-U+W6(fogs$7SvB(B zhw`3?hn_L}O&9lDogcI6yA1E@{7~5t=Jh|t$e^QD4wspcR_^&NqNanwI}yo9X0q@_ zDOre&N5_IVUuFJ+^qcpwYWWVl3A4@WS1eEL$>fXw5e@p|;1OQz`I`U#t|9Taq)2(O zz}%XMeELqx&v=uYoBvdbg0v-WSG_|o6&#PFqMN5<pZP*Gc;RrmZ#Z&u+!E3b+qI!7 zl(wvO-rCrSW&=u6PAAB8unG2yn4=)Ip1>g+q;WF3`^b8G&^nYSs!?`Lszhj8AFQjK z*s6MYA`ltqBh*XMgRb7-bSe%c7gJY0yaVY>n@s|tyvy0tz$8O`;J1#8!vaf{Sr8WO zrA3#w87Vd<lpeDw$8y|7#T7*H-nq$n+>?NH@6^T=cN?T`X4`@AIaNxwSon1<H|GoO z>QBV|SX^*E+NA-e!lslI#B`Uo*pUeL{cW2cuoJQdiWF{}Kst8L#gN}-WXe0$w282w zSdeJmiHG1yATClUcBBahSY4>yX+Tc_ZS6}5d$rI<a-b=)|0Le`M3f{W3iHm!v}=Rb zShYvR{E8DpC23!HI1Kk=f9UdT{jVgi4M9qvCGACu;7o5l7+_`cA1RRQ-oaDn^xWOy z>7ge1F%oQjTTRn;O>Y~NngB~$<Yew++S{r-{pjG(zckujtO@&ZZCdZTIuy$@(0_D> zN3lLe^pw9-H`%6j_hWV<=7l$mSBJ3Wsc7$md9ouW?9yVNBMlMZ5mZ9jx-YQ8i1>7S zr}F)MgN3Y4_5gx0_mnrFJDAu6dxa^4H19Ls^Hh(WwxQs`(iytXiZnkFdJFbrN7e%- zgTyrAc?)?%w7XH&jU=7!PE){qrthjxhZf~rFD6Jf_7T9^y3PH_fR|^$2MlFPYpnJz zX61rRu{i<H&F;j``$I#Q7XrC^gvANKu8X8&HBA*m$_;chg+tvWYnK@s&DbpniK1uj zk*T&9Vo)sf8J@;+*j)NeTW%qLy7y8l&e4u?ILJfK=RQGNb*z!6e{Tvk!EK?5TD>M1 zR>O>fzHQHyQhCm*2K@B@ydcs`OZv~Cg#KrNk~Rp@D=4)ua#-K=K>zi%x660Hq^<^U zy8H~7HeUnNFThGBQ^G5(_%DZN`4>|8zX>f*#5Mhxtnp`|<+%&{??B6eCX(k;nH5|( z==qq-{SA0oGo8M20mxBCMG@rbdw^ajO)<l*+#l#n)HWdsb{@B5u38>nRe25UAk$?V z-kae%hSNzuPqvERG<+-2)P^1uY++=({wkO)JNX+xs6h_(6Z9-rRz=ImX`#fGzL#7r ztZ(OYvrVPDG-f79Vd-val%@^tejlO-?Yvjm0F;2fT7Fh0+rwRLd=1BU3z=kLq*D(B zH#N6gP9M*OO(nv30YOy0bfu8_4qPWKwb|UMa=zv8<e+z_5%+PPj^mIm&^9P2-1+rj z1|BuE92J5TpzNg>0?sn0l%=;1<eJ;{{g$cW1g`YJ0(``(b{B8UX!9z#mmGm3r0=%M zXw)FN1y9F%LZn(n$&;Bb))?VEB{>1<Tl)xdoGEgA5kg$%;RRq+B{33NBBJOf6K=+3 z6J6bI#K8fvKZKW6_~lc3Cx>~>gWkz}G#q!twxlndVt)?^>q=WiSD5Fv1h1>g{E%0b zzuU;qxzBVFDLVp+7o=Vf>=s&{VEy{ZBcJ+VGoE|czwW`Gf!23DO}~jf3IXY#<nWp| z=WD>ma<fslz){TEyg(0VS+jei1vK!9*w*E?sRDa+4!v9(ReHSizy+XoQZCiC3U1q4 zaZmidE9v_oNm#B&x2dxAN#X=rYLTuAPFr#l<)@jjgj&u3LDMdC7T*EkTn@LczwWQ{ zhQ)S(apvgD=8Q1Yow>00Y36lbUAsR=VjtY-3o7WkM4uUaFUYgsvYj0JO(6$%0u_ZK zHmo(l2EHqe2^S3X9&|?xx|?Khcp^M{2OaWj*LD<)_*oV4IiBv+p`wzE4NfdhT}^Kf z<maQF6F1jvk22yWhQZmqV8(R*1SRvu>%7~R_ZM&1J;ZsBk+a8MX@4AhV%Jb3#Opdj z?)kX^PTAj|{_6|OJdPMI;deNL{b%8fIS6mg&uG4CIKSHSfBnjwKX68S;7s>3oB@6t zXL6a6UvVa|-c-Ur_hbKPjh_#66#5Y$dBz!<d>rT;S3UfB{qHrD@>eJQRoL=*+J9%( zA5}VE&ij`r`h|ktqgBzD8hQxA_DUWbb}zsddm%<%6NDq^T&FVJrnbi?c5^%0_NtI~ zIF+PD!O$&q`O*WWc#jyF)w7l`9>E=PEJwz_Pr+I9+<mIb*i_2iCPncO=x2GYnBq!o zFZ6WQ9*l6(!5a~FfjZ$7)WTg3OaLArC$Nex>UG#O)zBU*cd-`76Sb*^y|B2F*EpQm zXet)e_Kz}faP9$69*%ho@>7eDM2IIFmY>sFCU<F<r(VmbMdP1vq1my7zWfU42%|H% zhc+9bu_XvF;O36QL}pXQ@z7ylX51|l<+Y|K7@`gRcHW)tTyskLTSofC{)x!UJZQSs z*!9U(NByqfo{53*u3+9?7`+F_L4(BbV4aUfyL*-LMfrm)@Q;xFi#+GEULJda5_}() zxfe=nhEdtf5^<SAAE&=4u+^N$an-UOzB{m@$mq?L8HP(rU?p8gN5O%=a%C3#`p%ho z+cnJ3ux1%iB;YvJ-&N3kg*Cr2;(~Qw)b4!C{HBErW%h}G>N`O@83J;`ly>Oy3&$Fo z)}3%*LF|Q2BsLW%%>kiVQY;2af`uN`E|M0ZmB^DC*N93<o|m02hDI^$t5Z!no*f+T z6|y)V)oCRAdEmT_?#>*jVXoWrIItKdF}s20W>V6flDUjYZ>W6BTt%gE7-y)roK0J! zu&}3;41k>WH|NDiBl*DSRLz1644n_!&I+l;v#@sr3q)~TkQWLo2YzqNj3DCPRx6dY zAHrC(<DT9`f-n}xvH;W8a2gZL^$pr5^5WEM7CT6&8HC#%;B5C-6BP2=JOu#JPHAv^ z+J>ZXGvp4=H#hel#jTy@1fp4dXjh)#A5%`okNC7bx7&C)Hz{+G$mHo?1@-hdD)y4w z0yGryiZP3E;OS4)+LGU$k4FY&9^y5h$mj6RF~0R!TWIS+E7)l<LyZq_V@W=tgY+6+ zpR9w)%4=23+PZ38CsDv#iO=P;$32=gzT+a~;`R=md};>q<?vo&sY4a;3gO#qwJhZ2 z-|`xDjI?QckTRzeD3X#4XE++dR-w)5UC$(mnX1PhU%^9z<5*fNSls6(tUv4a{;^4G zmTTs-ou)l*d62@&q(|G6=_lFfFXfq^LS?FVCTER<V$UAB_22)KR6Ii`k1x<k!ta#5 zzU;2Iz^nc_==UrQ`_{-sV1}G>&WF^s$KEfPckeRz{4P708eUG_s94TV7HNFT541pm zCn2X8s{oFldxCx2(9uF58!7!(UA&H<C7*xa`tt-v?oOvICnZFrB_RBqhp$0$`mL8Q z_2}jM{P$P)ODUbTqo@7c;^$}O2Cwh_89G0|{Xg^a7bEk~9s4%#l*ZUJoDTs=w#zdu z#&7xew0)x$va!&IRsDoE7k4#&&c>LO$JOBN>Yv$XYZtFS-f2JEXp5WpC$`#GJ8dl# z`@OyPI@?dgU)^kL{l|m+{Pu2J2;mp&@fY>N1FJeLZb4INQGbY4^VpWvg7nlLILVik zH!zW-fT{#c#Y=i6%2L}sO0BXgJlFt%6vjE$PHO>t>@aNLaAA8e-P*!oWr1a7w$ABY zyJ{)6PzQE5SeU`l!Z>;Gro=9^8}GI^LNg}#wjLX_=s@0^x!jvqOu+lO-`G3$v~f$S z^YE68X)jS<^+C{Q8cx#R^6-}t)=YenYh|C`svUE#n&hsi<0&4pi<2K7Ld-_=Qrflr zTVG?CCP%z}=ZX00TcsnA2k}G^r$-iP@QK#5=KO69FZO}_w2HskEdI02l2W31t;0E( zk9Bcad-*;xWb=?BhtNJW-dQQ1+u3|0#KN+AeF}Y7!d{v}@w|Um0ql1>cv)3amUVi9 zMgk+WN=j^(oeVw%u;12Y^I=^sE9wOXesNuX=vKbkw-?3PKWpKhl_DRG`m&-Pu6jnY z5&i{6uC$*RXyQ52`7X*SzfjCI#-c4Q5U?R+?zz#<LDMst6W+B>lHYeKrisWA-a2Lu z@|xfC?M~+XofP4Sk2?vDQr7lP+Nb}Ip8tP6PFt^(%c^~UM@f@3p_5S_#aTqgeM~*l zT~<O&1kZ)TI;fMRJUkJM!>C{G@aL5EaIr{+OmgJc_M4<uXYKvp0U1Si<`0<h{V`t^ zAPTg;H|LfumiANs=2xE5uekU=&uYQGfQ_q9t*-L5A<|<lu5VIa`y@=#OdubhOJl^? zDSz~ro~_)Yi(~R_lx0=XFR=vd<7hG?jZXW1w1wWk8}Hp{lY4gi*OmP|;-fnzuweLy zU$;i>w1!Q-|9#*6I^>Wq`v-CCk=9K4rEwRw%!4v~$R}I8*3uR~37ovXVQGD%v#{W+ z&<7m8iUce+>fI9#zV_lEan9h<L|0N%B$hv(#dYL`NBwvfr;?^3sdSnTBfy8)ft$DI zdh`)}d$PBg%oI|4IdGGPcROiW*}_abVzTdgna%_m$I9DG`mizw)Vr1Sr4z-G1fx&# z0b1N%TRf25lpu@uRB)Abt&o@yH-MjKcg*0~6h0rVUXzkC^7+0B>I@~oI8t|r65)Jw zRo1>P3Vq;ch`0FwIe1a8HX*tRP6V~^o`xnwNXB8Vp(9WAq3UP}N5IH$Xpsp;aHx9a z>A(B1ci(GrqSL8t`|uS89KMWL;LzwPezK@@A>vQ}+ruf5h!X>S^GBHUALNhxX&2%f zt&(18)q~c6BPp4nIsVj2e;a`QJiz<!)4yWo|JcYs2X^>fx*$Dt`~|N;_`+-W4aWZF zF)WNd|G?N~%~DwT^ixE98-e)U5k7Fe^#J>8-|86&9zmv2HiYFD5QAc5^YJ;z1g+PU znOH5)^VOBZoc@Tz{D{Fk{ElDZFh622&-v58!eM^IU{+r7k;8oZ`_1d$f8d$Hu!e3h z-*NMWUwvZd-$sysbA(^_rYQ0@6UvIn-?aiE(>2-E$*8s049YUMYUsl|&TTcQqZH7x zgKp!sjd0pe!|92bd4B*;K(N0{=0$%#@g|tpXsH;p*CV3mK*dQ7!EJ}a{Ux!pvt0zI zRNPL&NiK>(jkkB4UE3r&g@ikEi(k9BJ07b9=cY1nka*|x2Nm<6+0`i@hbi5dOJul} z`=PRG%jF$&Jh;59JrN=ei2ZN_#squoUQROW3e!JT@)-AK%(ocE<-9ltU5ppyUGx`B z4@s1IG_=%SDr6U)Z8Yl|MWP>k>+zk%Eg|$edm@%Q?Z0AAmv`|e{m#!`=R<DfPx+mn z&GUk%zi6O;WS>_*^v_%9KN#VcP4qV&{(OY{6%+qe9=`62ubKF-^6)3(M@;-zdH5r& z@Ff%fRUW?B>aUqN`5g}@o$1{>A-_l^Sk_s9SSw3UE+_H_yc1hj+j~~Ia(;5Kx#H)% zQl`rf9CtBCEt|BT<>bENxNoBzUTx*ekvjGmaY4OTuVBz7HYI<FHHQWMvL?67ih3RQ zl|QS*hu_JQ6lP2IkALObZu*5k7uK>`!`OdA;O8c+>P+lDzL$uRbm#ucf0w=a1&4lN z(1Q<;m4oE;$LA#H=e9x^v;4bW^*Ojm+7WHM%<J#W>PuLGwj+5*jB$d07{w{r)cgAR z-bP)$hUazm!t?ugyJ&gkS}IeIT+p{&Cok`gf!}6!4dWxq@P5txs85=R({~`e3saug zaI4C4vOiel+6>mYp_X*HxpF9XI2EJ-)ZIt3hJ%diOvC_t!Uo6oCQRq+z6CPyibTXl z@v<!|01{KBi-kd1$S(I~sy5VcFgVe#XM~)~rUp(@aqzjiIMU6`TA-z~$Nb0(NuFI# zDUNRSNh~%`{}!k6W}D!GU*l%R?AqpJ{{0<8N1hq_GE?(k<iT&>|DOC!f0;f`|3vz@ z#*abeBYk`{B79u^3-s~NQ8)Q7Q@5{1_!;r4$Lof^)0{`P2F8k;8*ukFWn++1L#DIj zE>0tgD|6~OaHlQ)#!$kf!icqgH{u}Es@X`y1y5Qh-Jty;?(A*t2rQ!V7k<IN!m^zw z0!!1zKY$T|(S2R3oJ;7sbdHn~7t3SoWX7?uFLBZ^lbmqKT*^8(pNFEBd2-YfYk|qr znIpulNjCA%h}Q|t0IZwvp-F4$cIi>rJgvee1>4BV4~f^;-+FxKjd=YD$}<0D%JPE| zegkOdk3jq9&9VFm<X*4-6Ci5($3v9-Pl2d4l=3>i|9pU9VJj$HYk87Of9TG4P?FCA z5H1#`Zmz@iGN@x+boa_p!z2sP$Z4;M&#G!24-S7c_ap_n;hBrWO~tbD*zK!xhp}C^ zHY%lemy?etDOp5Sdb>fu9@thpAZvm>1v(^k7)(Y{Q~El(s4xutK|OH#4%}M`)k6k1 zJ^kAqL4;j*Gp#l|GJA>DE#mJ_|MCVf#Je*fmi|vW1NtDD^7#zNB7fn0;!WLS0gU1^ zKxr?2Gyg|nv0rwZUnH9UMD$qu$D9LwP%!x(hP5wF2fl--nP^`j>WMgHpFqlF#(!i~ zM1E0nVMaM!dsH!lV#w38l8d=sLmoi>d^P=grhXP>e@p)QVVwPEQT8?F`NwhgpS`Qa z<o)Dr{p?-6=+FGY+xpqNdejm9mBY=&^#5A9a5F&otXUEW85O`9f>eVRf$7^6TYwxc z!VrTxlm@!pe7K&szzO7Ud45(H!H^=V?Lr9KDK#WYB9cZ0)Z(}bwbaG#P<vSWB2UMN z#rf^ki#fewl3F@cV~rsA)i%rJ9rqe7>paknhy7u=cq*0pByiGj!lR+f=CC}&g>&fy z#9gfF-Az`^Z*t7h0u6$|7@wp!-O01JI}U&)lfGTi{S$$x`zh%h3o+IhE6cq$RbA)^ zzFNwoLwxtGZ{MNf$2m|?Eq8MC@2={<_BM}?lY>8wNdD{`WQ{NVaYXWG=OC}~#vex{ ze|8SCSeTzgB!6}e@<jYBBKc)J@<AV-k!K+jKZ!{GEFSsp?BNFy$uHuO`_Jb<8Y9J@ zB4I!Av%md{ZMv2*41@NO4leU7+p_%&@xH=>*LtcKRLS0~*f*l7eIlAPVN0J*e4eAG zUyS{MXucoy_$fZQtSAb#r9Tm&d^ZOV`nskYo3AAUoy-k`ToXe{?YLhV^<BjB-5fkq za4%9sVv0YVEPZtz@>R_9+nf3)M=bf>E~zJCSy6&!QLXKm+L(>*K-q#)Of`5RF=*qp zH9_JStuSSfVY*~f1f<;fwv0-MsqMn`F3U4`T6siazT4f=GMiOve@r%`(q6B-bnDE# z;BEax3=V#IyquK^Le43B2ioHep6&}#^rWZ%&cpttjZ{M1D=3tk{MNy?rh@XqvMc%% zVV?FU?mb<7I}ll>YWl0B@Yy3Le)Pd#;!*QInMcj$@CA=rul_cV`tuw{{_7lO{>Wi| ziGdt<1fe>fOYK^(N$AGuZu=5&+qPGp2yWkTWO9JzVL16_Y&)S8M2VG13ls4SGABSG zwWX0qxZVye@T?wr0L$as#iPl{=glX=s--E_ITyIPI2K%)b0jPd+(B&hqe!T*9HM>! zZ4F4^&2E`0Ae1#AGIn$;%BYguZcoIHx<jcDlXXcIC_MK+V<3EmpH1chJFdJ1r+MKp za1P?+*+vRY_#p%N{H<@_5#zszdee$}e+hA>6>+}D3;Cad7xF&?FQzxV_+ahjf26hl z{~<r6#04hsmK9arz)RAMm&>rwwSmOLg*VG7jX?xux}+mkz)jI|UnXjjpe+85YbsrB zqeGoo6)G<?iA->_)Aq<sPOj5d`TvM}lP1TtCQEFiMJrZiQk6_)lG<L&y3AU}1p$yC ziN&|TJP`8?>@bj+2ZA7`U#|;BL}p}U<eRF>dJ!4V@E9O)hjZNhx*umwVSAgZSU0Lt z4Ynvo&X@tvUh52<J{rnmNIk7dG*xQj`GR}K%fI1QN0$s($iw?qxAynFlFNNmSd6yh z#9j%7+8tj0_Z97*RatS<eXGm*x$D!<Vcvo<G{}}r2qiw}Icl~*>DHv01wDzZiM!tf z$LS{|nZ6UlfHey5nH)$@YkRi$XfOqRfO1D(4}-uKqFsrwSniEplqWheYx^i)&}qzA zqbGkgq(Js-EmwxW9%6o_J{3s61ome_1poPEKP93QRv|McjVZuv@g*X9caXKWG)YD- z{Z&xT(bH!to=JNW;k{FU`VsCuWED&WhW7WqNz_`Oi9X{uYv&@nC}|UZPSl@D%zsVh zdX4`V`_~#}9zYBrHh7KyuU(ziGl6f{z8sIA6Z8M-csvvHwYLfVTjTLO_<fBY|IP9E z4t;+vb(*|`J45wS-|~JeCvo;(aMw=@0WVH`s7h?;C?$X3plh;IZyYb$S&>k7*tqtc zvn+{20C1J)P>roLM%msoBpA@~b-U@fbQGODRg#NWYJ#<kpuFD`CVWbEx(hg&I3H+) zPO}Py&kDs*i;2m7%3L?>{sosGn`Iw)N&d(nMO;#K@bq0X$M{?7^iRO`uGmE`%eX7S zTL|)zRWL5ms%n3%j74>L;t?wJZkDl~pR4q_-r#bQm5F5%u<lO5^)h4CKBGmXitm8X z<7tCzGYi`X{02W%1eC#ded{=SGiJ9_E>YUSb(@~(sevXFALnA2#lXLhhJUCOynp$( zNI*}lOm-VF-4T#KouWqc;OVw;&lapPY9sgL%fGC^WoFgo-Un5i)yao)pZ{s;b*45- z$$MzE{}x)c*|Hbc8cNTB$7eeCAC`N3yAk#IM(cgVKYgP=5ySW|AM@>ag(6Af!2XFi z>;69`&I$p0;C+jCAL6VGcpGOe4`ur3HQVNo9FXUoUS$vfM|6MpPA~YMt!?`90M%R% zOh0AdJ+Hpy-(CL90qR;f|MzML-t+IkM*N#K1n*q=p11w$8Uh(uSKn#~pl{xW6|4&5 zI~~}?p-VjLs{Y8s`+g_shdZqW&#(9Tvv>Mz1*~f2zn_u!N5{f1_#oyF`{o3>;vsV+ zWDOe-x!&2AURdh!K>Om&?gg^GoVVv~si)5AF!M}AbcJDP6AplxzQ)a#i1F4*YAGM+ zcH@&#<oL&3xILp*fmxNoviTVoox;hm$BcR5s(S1r{ox!i0GMwFA;4und`vmH+j<uo z_L1sfH~ThMrbIc*axWziTipWb`>`;;MpX6F=Q3SGgn-RTdfZ%VwM|vT9-q&dddkoK z>d)Hv_hVrzT0J5v0e|y?|MIVHEQuRI@zi1e9RPkmJ^1qw{5c=*1^@SyT&{S&R*wDq z5Pd!F{0_x`2hzcM_yW}b3Z}o{e>Fqzb6om$F8)e}-siaVv#r%%%Fz29mtK1k|FsOg z&vEHz-r--$(EE%8Zz#7A+-DzMNxl6Sb3=R<D*%T4-^|o|&(m8|5&j@kkH$Y|>Mcyp zeETMFhd(~Bw?1L%t8YzPKWFUincV-Y8GAot^|v^ImVAyrSdN7J93^<ce;IxFiNoL1 zBMAI+<UXOL$(#N}9OXSd;)ij0o=?6fN^IW~B`y>JY?ezR2l$eH827Kn=Gl|$kH?*Q zBuaqq(%k+jrU6Fs4<bLG*X8HLi*;}QxzvkaC0@KoNx!FF_+dvZfLBVNJ0G0!Ff7f5 z!PC=buRa1q(4j@qdxt=;iRrm+v3tP}k{sS8y%wD`xR)n|96DKDTBf-)$TEAEcA}ff zH`J7#Oj2^p`*z8$TSq<}**3K|{L4RfE$bZ9IP7ljEn@AxNHPTuU~MVJjinlh6ZLr> z1{f<FoKbG0<EC^@cq1J+>mM)wq&*rth_m~5Vc(y$PXt-gi;;P6D>D&#>|l=P=q(_S zuk>O>dYYac;>5q0Blz*gKed?q=dW(?>@Nqc_PCh6+vBv-`C7L8DbX3Q8_Z9E2g|hw zyJCs}aNbv<Gr$IqKK7#fE;#c?iHaZZ{Acg*USEzcl3A>MXBKIZpRzTt*P`b&u%E!y zf6vqbg#I*ne|7EvzW&baeR+pZvNbRG2mA6T*_r`i;nG$P_I5d(sz_AQ?n-+^QHG{I zNbq6VdH|WqY+u+kUMOl5r19jL(xVWdvkM6uD!Z!%7@H73+d5a{bgYO3Ox-&803C@b zVe$c?R<+BvLi8jI_zt$GoA(&pKF;A25+AG}??vXs+4&JJ73yX~6l}}I@p`eN>6{)t z$64(!ha|o0t-oH|Z3`E>d$Kdy30b^jR-RL%VHvfw&|VFE?IL<hWWX48AHl~o%gB$> z16WVYhMD|mANx7Cixb+PIf!z^z&3QGqO-7>k_p)>ay4(-xXr=`8co;1xzxB3Db2kF zJTryNq154tE1EV-z+e;rSc&oyE}=Xg&SSqtBSjI60U(RE3AD#I*&3d-fQ1WKqS&u7 z@gL&AV9h;GV7^&1YZN$p#rkMj1!u54+To7~taPv*o@-qm{K{X}Irm@1XMUl-y!y+p zb_kCHt6HbWXV!xJTmviOzS=w+2geDC=Z@gV`d~?scuo@2bR#B4Si2LfqD)V_1MDbH zZ+wH$|6qNVJS(6)%RGR6G6EkG0>9H$2w#R*UIqUr2R8Qz`1P^&VhRy2iPwhf>)oAw z;<>~KzBl)PKMB5V<>80Vu6`=8c~)Dz2j2^x3NSr_f&WuJcRRQ?_!$95h;`@LpZ80| z3F(^pr@!iglCO7oelNqoA?&B%PRq~YP{go4%LVLF9@wHU__tY1XovZ9s<F=2>s(0> zuY!K;+{gD~c-fiY4q?Hvml0KhdMx+)ZQg9J63;%qodgBmm;?&`{uiG=|AMc7(ReJ~ z@+|RpuxdTe`<iI}ELOd1gV_3C#i}IH4t+^JwT*x5K)A9Fk^fpfJnJ>9zaF8gJ=bh( z6sxQj@8ww8Tp7ODIq{&l9epeJ*uKn0(<Qx><r;P)w=8~CHc+fJ^l^J4wd)Xy3T9kS zFZhwf%v&uV@dbv$hv(FfM=9Qot)0c%895(@;vT7t9cemL3=N;?@D4y8dSI&jdbMDE z_3%=cGo{N`JNq@h$Yf%x)A6CSoO>`TWJ>tGV0Tr-R@BCgIQP<z`bDN3H)3~iuy#qQ zH0hAtCrbdll(=`3FWG}$&FKR@9td_4gxXM{TJ08!%+N>c^UZZ%ZY^bd>7?SOIRK*= z-aBNRa=&s_%5&x8R<UPQpqvsi>R`MeL0*#8t-c;e?i_9B%-UH;$<5HZ)8tG!zx=O8 ztsxujAOCpy-#*(hzab(p+y5RNeph;<=t+2f=AS>U72;cS+vf=hy7|_pgV2hvGIK}W z{L$gp3TS%4e>nO1VMBk{9{sLxDw%Zo;WLb|=KSxT=R*lFe_s{u^7T+&17-ogdsDbu zp6v==uRYC&k0Z0DME(25W);-<X>i6Q;eXX|j(lx6$FB|Nz!o6`Fb+~HmN<lq!HGJ1 z-AL^@zAHvQ)git<+*_S@7je(_QhLzzZj+~(7jOf*0UqhW6{eLk=jIUez&zsM)FwFb z7z_#8pR?;=cj1)n$mHH{FU96KcI<tk+MOh@%r=d;ff;O3bfxWfsicipltZ+Al=qI~ z8hGsedVA|Di|_k0i+4vXPR7&49C!#RuJU#x-*x(AMDZ>Ju6ox+WY92&o8?gZ#H}87 zNR-q?H8>{cAEOHh-_DBRnxFiDd^qDC#xB9cc9dE%2gn2fx54%0^r$zkkV%-U48c*~ zEU_K|6p@q*JD0xIYt7Y#Lq*e$@txjsm)$j_H+->$To-=9i@T!3-i?O@>b_%lmn;&> z$PYwHQ?XjaLgqFb$fny|+_^s-wmarNgo|NysJ=Z`?KXQ^<5>r5oOm=jU~{M=L@8FZ zt}syr){}zMa&}KxO>E+GbnWk@;|~NZ#8W+LXzKA9Fh<p$ZMJ7M;6h5j8?v>7bR@jM zz&~VXzVl5^KEYf(r}5lOD7k~tiyO`gwM9M3B@qS8=5|q1>e$>F-My7Z)o40rb82RX zOK&RY%!OxO(6p8o@TX3l$yjpJrQ~u>p|Gu8)Z$+7W{&h@>S>qb#2Cdq6Y?3%o{Lco zr&w&mOV>(Al3Dc`V2>;}k*Ban?nIc<hnuVY0F*h~$K%3sQMSlC2-0R*D>Fji9WVeF z0lFTQnaelIJ$AmCPV5K>vH!I;Ect5qsNYm%e{AO$r0n@*(BmR#rgiKo1Q=l_&DVR@ z3KrrU(fSKfCSQE|+h-4q(9+QpaekEIUoq#eA`W<qPnh!!ahhME9rbK;P3_N2fm{K? zl#iyXnOGdH!QXhM?y0m4mfc8t5*6B<&+PU+Ckpz_qF6GvGdIQJ$IX#>+~wt^28Q4^ zlehR~kCd1!R+l+dcfPJpJB#P4t7r<8*3Z^9MTjvbbkvhJ8<G@S+0NB{#(@<KNSE2x zLZ|Memh>!H+eNmE6dAL}P#fJ0NiQ^UW#`1M&EvfU^O|$sZ$0lg2OT%C9C`-$;Hc+3 z5F9v&hJo>}%Tg2j;eNgm)*X=Q!8`f)64FjkwWW+SI&TEZun)X%1y)}|+}aKR+O(E@ z({f*D_vUddRS1k#4lA1!--r6cPMc(5F)zPB58c~32VdHvNBX{7I)`tT=mk&3dlq8U z9+f=wpc%X>DTf1WA3~4x1=YUe*I{2CNz2&`7<i5;MiN<ew7e5jyU|oY{HDeGJ2izJ zJIFUdDqODFKy`)QaPuyBKoBH)m6s<$IxhQz3Oyi{*b*px<6?i~@(wVesu16k)V6hp z0wy-5;8m!5J;Yjhy&Ps?sPY?6OtD`LyD~j3W3&$|(q)ij+|-^SLTl^ll(tysFsEtX zs7114n{4O${-u_#gMje-zNLLv7AHPN4h(cA%N<Kt3)|LPAM?7k(cC^zOfH(wLfHt4 zOeye);7q6QTl2=5JaTShn=QBeoffN;O^P^f6Rn0Gq5A{tH_8&c{M+fGOh;^^=dgAo zXO|GtnzmA$@neUim5!7m=<$VFvHQXjcs&+mYm+1i{(HwRBJ%!%CGc%mEuVP&$=&jg zJZ|EMOzEF^{EG+s3oJ}Yz&M6@;J_|bC99@+Ic|>8<*F^^*$>r*X%Yw-VZU%qwdfLB zJ42+gu-(8TdLSnWl{SC@_i1}H(>UHYx1EX>!E))-+es+fbhGP^$S&V5Sj3;ihP`a? zK=79xS5b3`Y>$j{v8?U|=TP)uD_MRv?WV;Ul{#n-d2<SfjiS=$biTo<i^2vwOZDPl z%^fKbCCuks?Ym?0Z~^L`N`UsJJY;#OK!mJpkJy=_bg8{d>0slueR)g|ba&Ksv|wV? z#=Vh@J6CK>j10mN*i?g)QcM{L=R8h}D^hjE5uYc$EU8#5l5ikoim&-RBzF&@JJQ(s zu9r%kA;*5k@39Clv+~EovFd~1sK`Aa@>)8Bt$!bwj=coboed8tR&VpmQ5xG8&2qt# zhU1pG3J;byujvJ>hyLm_?B17<s#f-fP@Htr)|J$9Q+y-lLIdpVM=6jAy0?y+Z8&A= zwCMFzo2T<R&H+}KD6P=P<x&RbHAAaSC)}4z*}zF~0tS&_&T7aV+sL|0_UXci{7FI| zF+$SqqqxNx)dR%CX;%`|mTiV4LR3F!&z4~wpw7B6(^<+Sbrg<Hl0}Drrx+`BBsW#s z(`^@ZX`xVHr$>kWo``uvB8J%rv&3FE3zRJP!U(vmQ?k8Fsv_Q%ivu^-WeC*K-ewE1 zH{hw;mKY9JR3-w4QZGy_I9#n{b7Kn$K0eqeDwhX!I@X3QA_Inh*WKlbauu%g>EBV# z-M_lH3Bhx|#B&b#F@XcO1`}vJL_Hh_dna1^)%_@Q#*={IS+-#*%|TI~vlrm%uMRwL z6u)r8S-^6&yaxDO9R~LEuY(B8FF1DoLe*M(4aygZ_|-8zL(HBdL+b$_&&bgF9HC=B z*2C`oTGhsX3<dDcuR;NG9kN*FIj>6fA|gL4F>&$Rk=UC$@ejcQFqbr7UTrxZj~P}= ztp~gEV?TrMmDsob3o$?HwZuC3?|r(lszP4~=5L2<!}mr?_#_!0KLhOBx6rF*;0wS% zX8M)$p3t~?KoXH&4x=gz7~<QvSpsu$d_*mBm&3jzDI}&eU}GFl2w5vIoLKWEtF|7` zXI(uUh#B?ABFNdP;F=oQYkFo}DlivtvxkqHdpazSwS7Es5A_(itD&I{#;(1$<HeaD z3SA!%KAVt^Y2eUL0U`{Bn8+ePxs50w<96KBhep~JLVHGzi>f?u0UO}9(Bv9!ZATky z$`<$uY@JiK!KX_zTa;y#^Kr%M>vrPzsjdjy*)M$r9WlK;*ak(zg5_I?8g4RU95v1h zeDmBbQOcUA(jgUgYuq4~U+^B?lU*(0v)ttPCPxox33&-=%`=}2tx-FnLk8W!zqq&d zR5XP(#LzZu74(+Gcy$1*M|lK221k=$;_G8?pUx=%IB<tD1B1N}PjxuM@vVMv+oX$) z?W|x%ScEk}hPir|e00_3K5@nD6oDEMotX*!@z#506lz*TcZcF+r0}uR<MfXC2?-r2 z&-NO&(yOfDuDT=~?yFRg`$)FV=;gH<GS|$M&pJ7x@R@i7!xjp(Eq9K$_hXK*`@l#X zEzvI#*+hJtsrwMh0Zz^^qzR($O@$m`a67O9Hz)*jK8oEnL<#oj;`-f5`Fnpg?{ri2 zd+*U(x3hTHv+1((g-|9XGv*82)Fr$Zfq8Gr)BYHkVR!S<lv}$;HNY^fwasNL3M656 z`bj%3rXf{V_x$p&d&5qpqu{d};Q=qP+b2j%_su_E{_C@UP1eu9kxf-S>MRQStf{{r z_%QgVFiERPdNMM}TPR?}Ox*$&f$`Qtlza+)Pr`zi@0l%J`P#bTzdsxQ($5C;aQGpV z_S|!8pV9XYkI$-r;`|3WRD|{k0L~UgduvLj_YMH61=*AdL2n&bwZ~dbDH+iyych6J z=v$MVa*gP&O$GY(tOET2nJeJxYcl0qU4t|#tGX?v&EN-pt!w!48kqF_>!<s(&-RQ( ze))92dbVfo$OmKYDO>Sw&JnNX9KD)z;2OEyx9Ao^km)%2C;MPYp&Oh<=&|+HUOG12 zc$&-83yxj4kMo0S4`Cbn<T<FS(_P8@*3ukb=ogz~+i7ZBjB9uCm0j4JjcX%6?xPU* zg+n2LXE+D;06ip1ZS9I0yaRbojUBze1hZRI)snf#8?cJmiZGlM6H>XGdI1hD_U4F> zOo)Nmyl)D`JCZzhk#DN%3E(eL4&P&<cjyJ^Sk6Ax9N3d#m$OMqV>&V(Rvb1Qn>zk& z3xG(ocl;fKZng#7HLSVp3mijEF!u1zcuienO7M$19s#Nt;tt_1jISF^Kw#Fr6mKL4 z-eT&wem*I-C)4Wb+>aZ1E1K@{4iC2u3E{1+p38#^;5haXflR;EaH<QA{ggYDL{Q}i z^2|7hn^$u(02dRJjw5GKKNINtX~=pm|L6Kq1~l2;#Gx=@7`=}U%pB!}-VERwigK4g zv3%POk4=Rj+iA3W2LL*!k~^f)J-kv61`!VC!4fg(=AH7*g+0JrfT#*dUxE2;<=DPH zV{$j-wt3nF71?N-SsIhP^Q-YV6eIh%XUB6u^MzK;<9%s1%cTwsNweoojjlDC8^fZj z^Uh>l_f)s?p*cE^<Cc^t+|koUsRVDbs@_Y`sWs0x@e~MgA>;HU*J>bGFSwvn;>ql0 z<l2RX9UR-?P-~`3?fEpFy`0-Ep2oMSf9Qzw2KJl?Io!=_lr`3-=iCPDZr!++lEhq2 zePhnEO<KMU*3)$7qY2U;u(@3nF&{C2%HFUX!pH3586)nnp&CVe?MC^37)^cYg0~LO z@Q1dy#V4@z1xb6R1iXQzZ$7i}EBZsE1^8`@yyjxhp7GXqd7+d41sHg*V92k@qRF=d zY(n@F3~}@m+^~kZwkcd8;SF$DYdu<Wh4e!#m!c=){h8i7IX~sJ-qSMIWDITfH%EDG zDF4iC=ciBC0Qh}MSSyN&dM?t}7;<A0<=Z=cu+V_@rM*8B_|CiM%F9~9`nf&sw^WL+ z8{~?Pz|Fntk1e`AS(goWIvrzXgLIS}8Yl3}1dAWR%7Piw9Kd&$70V;<?kZ&o_UuEC z_Ojiz`-Qq4D|g->2X(UR8!yZrEUB6|VYFZsFL-0$N@BPfHo7~s8aKz6;yN&1Wfkb2 zb!^YpWN##lN6t;QyCcFFI5cv5T?Eky^wxd$w1Py!a!csS$4NJ^^oH8p>JAG`(mG)R zlU|ouD(~~|k=|W>E4HKn-FZ@ReRD@7g!qErV*MiKwnknRyxeLMbJ(K@vz4V29nDQm z@<Ytf5BNg2O6(mTq}bJ+RxtDoH381^01WG)672<*rs>)P-)WX$Y`)~F17AYFORRu% zZRQ>U-%%e8dkHZ<kKWtku0qvoH>j^0<m&GwVq~K6`i7UXt#4)w|5IG^bxnTV<AW60 zP83{+h4x|5J#1CKH%=Vy_s%h;0cZ<3Vn5s$+`rxpA4_ho7<8z8r25GlW+m%5ufuLr zdxC8&U$A-Hxfk@Nga)5y95I>x_J}(D72?2K<dZ$8q0}DyoxB}Fm%tQ93%CZXX-p5g zes5srV{?_v>!=S5-uJZBI_BPk_bCF~3h*@+2B`@m%{$*zZjwISu>MLMurAwW6kBtt zEjLZY9wzeuU2J_{Cmn}(Xa?!v<q>NDfNXc<jlWEGm06c-QDP^~?$X=-u^{TrYxc}F zULdATAdehR@?u*OtQ=c`4=%{ePy6u&UZl*L#Sm8Z{#HGBk9zBEMA0$I{CE`w<-Faq z5;bBzub1v%JHaRDLjDt=i%-5pEzbN;My>fP)WUabs`XpX!0FSq|47uz{xWI_xNGE2 zJP){B-eY=`db=&1VS~HDMbkR&olOJQ`Jvk4(<JBYY1T2D2+o>GNHk0dH(J|H5qI4P z_f6um5~91?hp^w=wq^;R*f}&~w+3W~HfC;boc<h{kdozf>po~17+zZ&MTS7?t60&@ zOlq!rnxAa#D6}x*pfPzLHjfj11Rl`NtZcJcgEO(9NjZaug5^|RtX>!NV2<ayVfT@W z6WK<~ZEoSaA=9{obfR9j7K8>K(4ch3T07fN7@(2awj^~@dxasVeGaQ=*0jea+H=fw zgi|-MiG$1bke(dC>d2}5mOftaQ|--RcA>^vU3Ns?^n!YeExjXHJc;ZJS6`J)BjCB} zHN`$|QsgB44b(Dj<jvtt)6K`fgjz(fG*^_VdE18WG2#z(lX>G&kn0`h^-yjM*pijN zm%QDbM{pmmlq+^JuJ-iUZFy=4_OtFTK7wJ7g`MgfCi42$VJ2NXHNb?5hoej#gv}Pr z1x52wNXB{WLHcsmCcr+}fjD1reaV+{VD{78hp;$v-TtyysWW|u$V*$RM*%?90Ne^j z?)E5D6h(~3!O*741?$k6fr;em(Vdp^96?4N7pOvm3^zZdoc3O&8~w7a(Ia#|0Ng7M zmj!tBrM&pMKV6PAa99pU$c>DN?w7N2NFKGW2WxTfQI7L{*T6j(-Qto5wZ9-Qu-{o; zhu<oj^dJf2!!g#kY<^S7Lo<i{a587-ikKo-mhITdnDp}h7<~}*zr~mCH~9koErU9~ zd~{MjpXH;{Tj9>zd9`iEER)70Q9QeURyO!8+&u>^d}luiIbOU2+W+9}H7k^Xc=p*_ zTk)N>9%M?xN-x9gdjS23a*x$fy?t|3X<W#hEdSD3wLc-<${^Hhnamr~+15@^Jy%Qo zXXiMyTz`kpL?Qbj8Qv(oC)7>w^L?HrDqp?Pw**4%Jw-65XUUA#wERV;%JsZpS{u-S zRsBrXr(tPHgTFJ5PXXUANAk}O<O}{)hq_-S`c?87wshu@1fErKOh4S$6F_<z9AQKc zcck$+(NCS;PRVe4DF~JGH^=ydbqqzEFS2cIMDoIG?6BL(Q084-avF~?aCLhdu^>dv z#nFrN3sYb6vk_el6HQDq;5U4eMOQ4GY1RQg=gEWhYrc_OY~Lc;5thJgrtT%9HuR`( z_Bgu8n9fE433E+$ATO4XHZsFs#R?)$6~UZxuDh8`a}N81<{w$s-2m67XJ{1PZRXuA znyaJE<<$l<ZpX4aa?zdW(?KYKZD<JnuBqI(Sq!^3YWxUiw;lDMc7QL4sv}{e5ku}l z54Ok5d%r9m6Ma6C$|Ige5<qYlPI*QFXBFu-{PuQ2=^XOs{zmIKSW(8eCNZoo@vI`@ z<6Eoj?<dvf_!b|hlTptJnn-k;GjPDUYqwA(`7w(8xlBG{`!?M9U@fF&;H+Vf@x7eA zeQj0|Jy$RMmR??b-DCAfrN-uZd7Qk14$(Uef5DGJm_Un3h$431P``GT3D4kcS3|sr zJwma`GC^YY!UnABZ8toaP#cJ3Y)`k$Xjxj`p8JSodYztodCb~$RujQd#~$x^^y-&Y z5@HWmuo<3R*0$!sRY!=i-Aa5Sz#M;ar&wm=Hr45IN+<)~s^(P;N6C&Fx)GLxDGhDu zKqSgqI=TR)ee7CNdSs7lw&REMXbo7QBKB|^ujQe<Fxa&XPf<vbrj%cG(-f9uH`lDj zL)1W>BM0p6qmR>LNVi2~jp2!Hqg<7?CFC&ks0WvLv%NbMDQ`|Yl0S$U(eg{_c<e>X zW)xAbcl`y=ge!6tVevuYjz2o`J|BoE-&m!2<=n8kAcB%I`|XxuJ$p3tlT?osXFgJg zw&r4WmjBtL$lv(7zjXDb{|?#o=Tc~0lyqyP-&2gn$<pZ`DwQT+LWb`#MTXI7?X6nx zpWfv1e}HECyry1F%J;nQ`&^n|#}TPd&`g7Eu+X+dNbk@r*|bi5RAiHMXk@?T8S0;( z_2WUp_bhElqAXy`0h7dx-?Ow|uPylV+Q(6OPrds4<Fam~9|z{I0PQ5EYvrE6+1|8u ztAG?K8o$J1%-2>R;hmTW%ywjTrIz<{=SCN0<7wlPHpk5{C>a9f(_LP>gtZ%?jkk$b z67?p<?%ZH%L>z4A-d9jIO+q@jaSWXAwF^sC7T(T>DvE;B!($tzhJqIy8fv|x2XU#L zVaE-J#8s`Wc*8U=w_;I6-8?y{w9-JWt;7gqQUao0aCYZp_!VCwcs(XuLSw$)FJdVm zCsUiyIYRj+Ar0EGt8hzPrf|Qd)Z45T?p@wSTw_dtXy`ME@E2BIRoY5hXKQ$Fy-4OQ zS8%TxmYwQ~%drbEf12J8v=g^I;@a_;ssjY4!4?&}<M|kz@(37SI>aL7sp!dq&K}31 zYCjxF3QyO89_b(Jn}}Di!#Usty~Pl@QNKvcI661NYnY(0DdMxBF#W5w`^=F2Q>*v; z+NEU)zrVXZU|(pkAc0lez1H>M^xFD7!B1;V&8vH5(g;}JlMLSoYOUUR28nQZH9n+; z-mQ`GFOCVvx4vQkZJ7qQLRKTDEy9Gs!XquJk-WwC=H%R%wHL<^hl=b};DDG@2f5Gk z?PB2>LyFePzNOk%)CK-lc)?$jE_{rD00j{ENTqk)&aH<;HISGCw=^-d-!b<A*=wgo z-|3LXj{dek01qE4<;|>Ic~}q8<~|T)CiX_$X90Jw*g8V@P?d}}8Y?sO2o(1PA59D2 z>TaNT^r19aZP#Ag^p^SY2r&3Y(%Xr5I`**7Bl%d|&v&sWu(L8FCj{RI&S~?4Be-s> z?NPGh^I_Zd93e+%MEA}zS;G#Yt`S<*E*=Z_IGPn#2PCnV!ehQ?vg<HbCkz-pH(6AZ zV-Y$_s6ez)9U)nt<*f=lf$l^O*y9xTx=SDSDoxrLlwpOwISJ0)T=tRFKreW1$vC}V zuu7AyOXNKcO^{-FXOZCo4Qwc>cCOh_aXw~lrd{^t#=q}nnZ_~qSeL}^^ztu(CT9<! zU<ecLF2otTvsn4^Pb+CCByGGC2DQpb{X`fV@YB8>M9J$io)`)@-YrlPeuAN@LALqM z3k<s^;^5!v^J%rnu<7fN>hvWqND|(>?=5QGTavDg^t;%=*gtoZ3CYZ}2KycTfyX47 zt0SsV5-KgPskidmRb>?m0oVS_O9CYMgV+2clz;gF=Q~zJJcFE<6UC<SHNRX!>3HSQ z;%2yFW#lRI;A(ooT2-apnZ@js>cHBY(G)ni?f#%jgI`m`bz~n>gK+9W%Zm~`U=WCK za9VbE>EvKso4}NL5nIkIpv8mk;12rP+g#LiwAxde=kzVo(@YjF-PBZO>=q3vaH(7t z{Zu(yW%N<;><R^XHl>Fu@I4p@mY8_Y9qf$F12=SUi{6Z*M^bS~-b{?Mc4j^vrx4m} zlSXvg+h*4ajgfSm8lD*QoUeFzQ*V)+o!#}M?NN9{e2JAXputDE=@det&_pLslR2BE z2`Vpe0@0+0*h7-QUpHVJ%vNO`r7DxY-%Zrbzq@DK(9E%)hr<P~l->89i=@0-B)RlM zh0DtF-+~ceHh4}h<9po;fl^=(n<TMYP3(rz5~Fx+<vk_<<$lm*TGKZ^Rfu}uD*t=n z!l6Yluie{-)!;@-DBKAsF!<xoRWk*JpPA&jxyI+7(QiazeM@EOUMqNkgIBG=xYbn+ zE9;q_5<B{{Ir&;29<L7K>M}wl%~SM4%JzUWk_T`<Q88b;G)TsN0w(B>%9Y?R{aP48 z<68<1!L!W#QPJ}K&R{Nnaqs7y^Iz9NCm#Klk_*`NAaTiPoC&s}jY7-8XMSeaUn0O; z1<6ks@FR=9ap=7vZ^@bWs*er1U+|Dbhi5fU^>K`~htJ+u9j!8YQ{g(lt|E;fwUKXd z^;zM%;7^wxAj%ucB-+Z&(oUlIRYL=nsA5?|pPw1m6CLKr_{BBl{TEz7qSzvAF*@vz z8=e0#jfW-S^fEa<w>MZLt~ndGjh*sPwuP}eWh-X_oa95~s>Q@A%O>IbkYqdr%)vn> z_v<R6i{Ej|$gZhYmBP@B4vBC=?xG~rSoUDtOA1^I!E{NTcr-_Pk7F!yDrDm9oa?C1 zk5}BSBp0u3A_W|s)suq8+&SkmewhZd;D?IeCL)!~)YV@yGOg5Eqc<@FL$h~}@Qvlo zI3ua2hfCOq2Uc|uc;V4M4Q9L@9;_)i@NmL!Ju~GgejK@Tl=Z?R-+NaI<F#^AHHS9f zVBV|G7_O`-k5!nkNy!rCz`?!1SPw%!;KJ?ddb>nr4PXGDjr09N>Ku>m6#g{zsg3Ej zGUJwloWfOE_ZHrn(13B^)pz`yC08=6Oz<Tn*<>tzCeV$wl_{4!jSprk(ZbHJ53oUP z14Fwe9`^Dt#95}q81OkF-b5J7tko_1KP^-KjlxZwY`XS0gC)PaW;|<tTJ*NHe2<iq zghbl5HZcosziU%brJRPGmH-dB^VjCQZ!!x|yo&9t&EU-+ldf!AS9lUibegd=OCF*c zPb?^=p01m_M%AF;ouV~e+)EjZ<9w*;(A+k}t?Gj>&lA>Wy2bKiRNuBgx|)fJm$vmm z1&pId)byMQI7m|(AXf6JuR(1uD<r)ox2z?KpK5Mw%gpeer**=n84AGY_*5q2wAWl+ zbj`<ot*^0mQqDYMZ{M!1zgOb_H0En{z~3Kr?!B08jo4WMYbCb5O1<e|*kx;tEXG_s zMAd!bP5k=qI<o9%IYTgcD+JAdbrRnVkS{xi{N!lvHmFCR`7SJ7I`2*oJ*n$Go~UBh zAHX=ES~y$-B;nDL@Fkq<=3$>~6jp-_eKX&?Ks|^xwcl08qh0WJouScjRxf;5FmV<E zFr%QYGomvzw3_d2&lRn8odsplFz~H1g$;Wnr1oAzEK>qpq-JZRaJMAM*7gT^D9^xs zqzym^umTtH!>~Hh$1c@`J7}IBp{=@=(Irf3+`iv)DPv+1+DoH5iXx7w`&pff+Y4U0 zgLk^c3I*$@MnmxXSWx2?i+1)QP`mjt(3f*?L#H|2Oy@4W0vAj?(%uNUyf{Bf2hhUR z9d^5H<At}m4feDt&P~S_c6r<lhod14i@S#<RX^isq_o(`Qh;<%M%KN#nJ;*=XFIUY znl~znskidPDaX}UcTRdWJWOEv0@LKj2%Ekt)%EPM?6$xgU=!D~m7+gibRBry3y!@D zJ;?bQ(l{s58GoeN^i7&!m27yyzt~N~x{5^W`|ep+(3>2??X{}KdP+=wYI62T=mh^k zN>d|#<-R?I#%tM+@T|EvL*%UjRP9y~cd(yZn%F0Ex9OO96yzGyHU$@Tt#=?ZX_MKt z(2Um}7@o_e-*WTDvdUnWH)k7996dVwcke3aY@P&~+D`PryXOyy=_IAWXU`HI=FP>0 znVf;)nPVpsRftI$Scu=9lUu@FcWAO<7&4KJ062POAdx=2ar=bhcgtb}CegII>bEv! z*&J|To%<cFL;q%!nAGnCIiImEcPHB4#}5?W(nYM~eY~L%Vc~4PQi#zruFx0@9gP}1 zF!~UQl#f1h02H7`H5Ma16lRd!p%~njhn%sCQKi)2dTHq$)?UcH3{SxoLsW#2RcX6> z`Pb@Nv-Z{$Y_r@yvI(7ofIn|F{^kE#$;C2goyod;{~KKeS^1G)9{*ANB7T4-M545J zXzHJf%C=427TCZQz+!``kS)uw*4`?2?MKXhPeEJ{F#Zcq+fP}WCkZtx3GHt`k&qw) z5?Z`caIQl3OA_kx|7;RkH`+T1fj0#t1ly}@qh6JBw9~*c)sU(69Ta-gi~#Shf6p9T zkCjM*d8!>z--v3r<^jkHzn4uSc){PO>g}q8$YvV+mFfH5?c(>S>i39h9fv=rsxOG@ z+iCzC{%@{__vP@{4yf*V#k8t`-5~nt72dA9@4$NSIOn7|E^4GL*L1aHNYky7ZCTbU zw57#zDe5fCPM539PsAx!dR{JSW8cDoP)xjoz2K=$!<Rr;0)aSk+^r~XiF;}j`_!t^ z9B6YH-X(J(vFePA*~Wgb2Z7zx*{P{Eons%sNQ$TJ8L5z)xOv>0P`=%|=;1Gpc)8G* zz{@kV2<7JN-iBmQ;=Daj?Qq-5P3<i3!p-8^!-eHoQ<d9$kUZF9Xh{ZsANcb*ziCPv zUj%JCI?nABM{%S(@$Ha~$gmlJk3W#b<Xof|9PWKf>G$VWY^eAsm$M38pq-xG()=6< z(xvh24Q9pVyiH|$P9Z2aw~`n>u!<iUegt#zy_ADp{Z-_z(it!aUXP4~q>8agfT{>5 z#GWDetGvg$M1NdO!h5GP%Oq5ZMiS#d2x$>N>Ffo6C$wME*_SI#f9mY5J8~`9cvIi2 zz&?CiXK$nzzmp!5<JPbGS%6hNYQHG1TvOth-m{U4t+CS$>IzAn%y71f>E8AGoQclk z5tw(!im}5{+bm0cne<*L6|>{%8A4lB6Km1Wug<MG%4ekXB2T#BJN}$?)(y+DVM~q` zc#Elz5`uP%#lwk5*L30iaD!2CgPyk?MtU)mwcC&)ZFxQ|^ljSiqN`4OlU|9U)}S2l zZn|r5tsBMUQkZ<u!4M$fSnamA{V0*BCg@|(*|HDM8m<6Gi;gFqXsq^lZ5o$x@oYV2 zQ+(4ra!rp(R^2Mqh?sfDq0<u1*Mla7i#oI&#Sz_Zv}642k4J%P@BHk=S1yE-;sv#g zOA33jeX1X7ML|yeK;eGKwR{kl>9%j2!*x6@p1YMK20AsAQELBq`L91x92aykc-G5D zmUI83Snl)z*RF5tcx5@~i?zmku294XGy8AJ^mM#t9l`Uji0rnltn>DlL{|Q%dsEz- zy!=XJO!*a&8EeRgi_@sGo+<c>S&Z_s>MJMfp~Gv7rVrN?lV2M&5%16bDrx$6o?b&< z`$4Wn1>f^D>}O5ZbJ&3iXqz@kD88%jfc4r*2K^dbc^<8#p9SXr#{0j&{d$~d{gmea zJ4yZ%c9Qn$(P*xx6PlDv{Q~|Dxk3Zxoi>;2c{9qHZTJr}^O@nA@NUVEU7yCp+2PFT z7WLMNW}}00fjasGR|nJ*z&ITAdkMDN^jH9#RgqpE<QqoZwqbFz^+>Zt$v+7latfKa zzs3D#Lre_v$TIbg@3wnjR7h?lRS9#rn__+*X-;8}RBP|^fp)?ROcAq5s>sIbF1;LX z78&PmlwA>*Q?|cSSu6tpZ}`Iu*bB+4iYM;wAv3sz6yR1&ilv$hSx+|7LKo1TE%o7A zu#1!Relefa+msI4jRzEP&#&qQJrVRAlO@^}arl@vP?HC}obJm7Vh;sG?44{f)40&6 zE27*{2^MO}F|hM}2G3xn;kQN!VKFA5_bF$LW8Z82KUey5+FRw}Ye4PH%<k-kXCb9Z z8{%ZlVagU3p}WjTwD#8gMZ+ZOKCXgy4dt+__}@3t!!;cEvk7EiwoUK*r$JTtZB&En zRtolzz5@_Zu4DteN(!Ie>E@i{%@1_*u^Mo+K8jSbKgi@!#P5_N=u@HYYLl(}%dkB| zjaxg)VTS8tfgaCT1WbbMHoVNRlaUWbvRTLU*vFM_=iY6k`vJZ^Mn&r}oXEK_$3s<A zjj)#s<ixf~liiCld%;QixSgfMx;KgAULpb|&NgaU(0$*xL$rZX4}&cq#gsfbFmz%} z!=qNFn@o8EPHd7@L0urzEV}!ayghR`d*U?Bzf1&DF2kb`sPlyHuS|u)$3@*k2o`c} znD04#7jutuQ|$pp?cC5&Op-jEACWcix9NZhZhhne#UYa@y$M@j;gOx~Fr>|G@gv7v zj#phnz3dUpzG1vz=bMyK>P8T1_{z^c2TxXfb8Z()gKn08AU3gfbZgwWPWW6Chhu$B zs~i50f4uzPKT?qr=9j4dNJRF36cOz|5K$}<-$L5f7eu6bRa<&H?UjU{y13BcS0vQ$ zMfu7uza$~^Kb?fW<xl_+X1*dJ6A^B{BSUY3|ENT*>2&lSAkF-mU`&6zCR6w#m(7^? zKS}8Ty7({~{5l&p_1c`_O}|&M5au3(Y4M!C<Z1bvD)YNH)tHR^)GT{Gq0NB#gtavp z4~h|N4i@^^jpI#?(EF(43C3YY;b=VvdaF5G%XdFy15os&e*qg<7F2e=8WMm?KcqPS zZi2IPxFzN$ZxCjG%W2&9Expbn^#$jq<*Kv~GN35jsOb1)oRm$6qS(R8kFc$mjVU7= z1qEAdLhvT>Pge*DH`W1)Xf+X)7yNt^;&jvmEGJ`@zq$8Y6K!eEXv|RDEQtPiT>Xf0 z52$j{M)Z+am~_JQvmYPX9eE*|7aUFJ{R!3cjBRh*q(|oP2=NcSegyZxt*=C?U1Q3b zx5p&pVx%SNTcpC<;ZYKo-IjMYFL-+%XpZ7y_!#P_N)5XyQ19j0gb$pR1vDpi+Z5LY zl1sZ35iAHnbo8ul4r0oY=p_wb@S@0%I&h7EE7nVmOq>pGsks#9`$WGPT76P0tjz8{ zK`v$(T%!|*4GH3A!R^7NwYAR0t4=l1EhN!>t{#iLUufxl@8+}W2WuAcfQy(gB8Hl~ zonN(seQ!+fewyQCRi0h%a3xmqF`wj<Nc->Vu)wQCfY(C)ysAX_=Uw!^hu%D$_H|!8 z1AIB2{6Ib*&oak^C9{=$G$P5P^JmZJc>?;~_ra5^)zLeh%foGSK#|sQs&i}mJPA7m z74w11j92+8(hIM{ZF_j+z@w8*7lKH4na@FyLN|;(G7Bm0tnqY1<goYCI+~W?f-~p6 z3CLzL)Up!{tG7b$9$M~t(j#SOyM#8!LRQr3Ugtf2F!q+{Vrn1UVtB@wAe~QWtjj^v z;bh`(HYa>T1LH9Z+1h%+_tuW3{1Ol^WR%UcuR~00I)=VgoyzPwH5|9rhSY6$i$hFN zR%%JGvm;Lm#bgimjK1I}Sm{I2>YDU?W37{bl*hU_1qy@bn-fLi?3rX!YsX*Xi->R0 z2YkK6tK^e-#&gu4^_PFM(v7v7E>?6>mN8-`!ocy12$@!jH0m9~xl{6wm;X)%)up>; zT^0OBra={6y8Ca<^;trE>7rLB=nJh_0NdY7Z_o6E6532q-YV`}0jR0qlxLpu(1M;3 z#HvvyQ_`$Z(ImbGfZw7&)#r2VGz}dAm_KssBl`z1!)pnk6#p$<x}*XYc*K=GuZoKQ z>}Ri~wEo%(z<5oMd$QE9=3yP2KdL+atP=}d0U8+B5^24=2p7#-yNxmHS5$xyd(R-u zwU3x#d5LBwv%F}FlqWmMkVgKk7I{r${@iO(Zou$l;4Vwx#1FB;W`6+=0Exd-udjW3 z`&GVRm5k89?h74zUL_=+nOxMj926A%Oma>yG9|xfpnSNdujcu`bK2Gnm+uqzcjoN{ z|JBTWHF3|_+^2bZMTA!q6nc%$;jhuTe(`NIuw#~@5~L}u;=1q?Ebc{^@{dPmMKSKG z_W+$AwaaYJEH9|daEuz>$V+Qtm_r}8$stcpNhpe^aPSL5+8Q`B#zDcHZ_qWms)9{H zYj4=9#nn!d(hC}~yPEXw#$Imru&dD$KkA$u+(M5&B!0dVh2$1#XAW}xY#RlS5@(a! z3x4$E19jl7KGw4Po*Y$t3M{AS9U@bYeswto$E>`Bh<?Z&3g3(llZPYX?dtZS+mr<O z9%{8HC~{%Go=fCrN&Gg-sdk)g8=Y!iV=TYvw-;YI=X$Wax44W%l1(ypj5TL4h3*U9 zHB?km{u!f=iVRk^wK48y-Y`e`@rc@SpJ6zv?2EHnUx9@R_RIl{x}~jM4}iP#)=%W! z1>z;#TU{V97vtLO{f%3!b!|=!xk*Itdu#SLW?9v|-`4<A)?tPYQ56jzR?qyDll~Yz zd+SuUM@{%{?b;U{Inb>GUqsXO*!aN#fNFk`R!;w*6Tfzq189Ihdz#g+Q9+sp>+Jwz zK%Bpl=u`gfxpvPweEiE5O-A24y8?&tKyd8$UHwdTr-}TFHyj=OEzO>xM{^;b$Eo*M z5gL!>#H3k|?>7=PesIlrg89(n&D*U?_}{;6jXS=f$m%w7(ifwcLZLJMGO`F}6L^f} zi4W2{o?V6Qu~4M|(v;~qsVHxs=vwZC)`RL$2)H6di2`TlViR4i_7=$^AB;l_;er}W zf^NBy*g#_-^1@>nDDgBONbIzYcRhPG8$;j^PQHu`S#6DyOyW>maIr~%!w1K?%IPY1 zqH`A>oq9RNT%F%fvXtQ_rdf~eC|m0FaNkU%d#K|u%Aq*Ij-$(0cZ~*?v`a47r8q!% zWHI)!uGMqHAYR@QkWVZRj7S31+wB$LP<3uRZEKvG<8Xg0q@;*VH(7h!P}->!?$y!4 zrYS8CgMnuT(`}3$MD?548}8n0*4ibIktSdsi~2ybYh2+6yoCXIK3@K{XPtoespQs^ z-aut=w9Mj09{=(3|9;?Cwrs+n{6@1`Qopk+^gokb@sWGac${MHO$;bmx#*uieE5}q z@V7wZ^ki4$kJuIanqB`SH-a6uf9FP5N2bB6m^*msW|ctu{^>Uvr5_bNOmC7YdjAY3 zF42$Ia$?n@_~Oa_3hM(q^9$bpD{uC#JJS#E_SKt#-I{zAD13Ph6>85=A^#RC%%ZcX z)zJCjOji!!b}GZGC-YRL{iF<8f4QiMb}pJev4<O2`%<>y&45Wr)!32NAu$Y0fP3{? z`*BdCV1p^+)`_d#!Ukf+@C~Vn*U8v`-FkE5<M#f7YXVe8w+E~)XK!-^J{5PSQPbfz zwtHx%lM>GRX6IB1V)5EEWUwxYnzq}9gJNVV+XAoQg15U{oZndUyc4%*q)x-*?&tUU z1|I^;&C3L{u}9d)m$p1mf>S_&a3h_wuZ54`h^<-_7&Y~WZ7<S@@i>u<dx-0+X3mV3 zj-4Pn;J*R4_fntS#m&?S6Aw{07mZFO+0i#AeZdzf)!NI}n2E;Rbj0@V@lgy6WfvR8 z(%TN&?pWzsQ37V6);xb}^^dLbHCy<Dsz)*cR0sSB=7qw)^~_Kx9FZs^Svx+b8Tvyi z#rqn0mftNOQT+!dsh?}%wO_`M=^Ru|1=qW<cET~EP;n2CcH3A~d%d*`^>rrduc{MP z%lEBE%(GtxOK`}KYx4J4?)=_`#vn+rCp`N4BjTV>UeegF_5=~W1}|~o$v!I~7@Q^N zH&4-v9wc~5iKa*OQ)cXk-YF{!J~0m1&Yw_jl{JfgF`}>iO@6jIzXv{5svnDM0sOm* zzQ64HZIy04dsk|Xtx5y!5x#!o<oz|0@0-iQld@<@;2F;B`FeJte7m)fGvvYsL_;R# zCCZs_ylgWnGF!~%rVyd3$?^UmM3ucbLM(V5mbzd)bhGB$2~Xa;MrD+a&8m<{0XbyH zgE6#oK}fNn3di-JSAe&NVT<H&t6#Js00Vk<0K(W~S?m4aFn57HU5wUJ9ToC<8-h=p zDIo-<LcM#>>B0z=GbMs`&B<4jUkt$Lro02}dWp2L-aM2C<G}DfjV7ll)KD}DP1dL6 zIxK`f#}xdC?tAadPv?famZZ@$I(@nKNW6XdH{5F&r&Tsh#BDc9r1%G&Gh9E8eLTR7 zKs~L2&;KT0da;R$-@ts7*)_Ywzx|I_VFL~S|K4bl=Y&tF%>&fd-$L_iyp7jtyYO71 zjCLP&s~VFl&@!e0$|Co~V41rZ?%FhFh4xbGt8!oJBSm5$J|yOJAQ8!X$W6CdW{t<U zG3TdYU3P)P6d`h-)^y@;DI1FQ{qCkyf)yvPBHB<)66+V;d?!XP71r`(`l_rj6aPPR zZ`R|uvaN}|+UeU5?e2yRbRYY`Z-#7(5=jlfJwS63NpYTsH_oKQQJh5ndIzP<tjw&e zy?32^ANrvHB}=3tf)Q(dYx<U|XxHSshyOqRuX=P^XyHS6@7ikXZ^avccbf+tDYR5U z_{F;}j(*{LoxXTpwUcoNx>$e4*dJ|4sq@CAPvg=}=L-N?f?~q*vn&NpG=1DTwwif% z4J~#mIy+Wavs71v1ahY@nDd4}C{qP9GiMLzILAU!Z=0tseY^<<J1W?F2e3K+@cd2f z7sus}JY*E`QjR;XFw4lU2T#MQSBi+lFsPb6J)i!dUHtI;!>{_^_HJ>+mZ~W*gQZ(? z6~5MVEPYk58T<n%L4gJR^sMQ7lv*3_*Zyb+z6K_LV-J4af<XTN8#dvx2Vby%>n;Ru z^X>V+u?;`w)VyGST8+42^R`Ql8&)+5FYpIzLQk^BVO_Uf(-n9^#0`14#(RVvFYd9= z-E)D$3bpcAhpxDc+2I7A%^hwk_Igq_sFUQv(nTdYWZn)tDG`vId*Z-Is&?V9>9!Bx z))Y%1=SxF&I=;}OTjrwhapk5>e~S^jt>hk?Wep+movvCEKol^a`o(JFOQ9M&H=?|h z_d+73rd86*X&WAAemfrmVLfj!bRSY~a<~2T&Vtp8y<KmGOQ*zJBN;1x4{IKJuob9p zXhWT^>q22UOhxf%;O;gh5v$d0*N+<I0ZpQxYe*Se`T~KsS?H@xEYw}g?c5@+SVs!n zao~<dC=1ZYwc}EnAnb1NSqr_LfEx47N|!1X!8|TiDlgc$pp@GWl=2m$;J?5JNpjvS zH5SX;{LuvtTrEx&OE_2etZwoXINl<w;isrDm)<L%PC83dk2nUvB#*O(^KPm1Ur#qM z=m(z;Qy<(Nf<5U0lpPf(V1eAhUOhpMwRA=CGrV=Jd;&W;_vtzfiqj1Ce*(Uh2H<b- z|2H7BcA<5Lcwi8*^ygmzC(F9$9;^cVfz}@Upl59z@I=QJ60bOx02e^Z=cQw!3q}u~ zw()6m2A^|p8k0wl8*m4BZmA^TX@$QHfuuPB$%m$3b_KWwxeEm=Z6v@}5WzVamRcNe z4Fbm6ZjqDsa*1RC$(aV=q=UJq3+e}wZIJ_D7GvVr6~J`hJJv$3aGaMr!I*ab_EDJw z?otc0T~765M%(j3zp!+Utb^ycz&e57E$e*wF8I4~>~jiEdIwHFv>?pK2*G<;>U)m2 z5SWyHscwAhcmu1JyGv&_ur|wjJXZL%gWsd)&s)^#)7j^@DnPNqz|!K<l@9C!H+A7_ z;%&{z1LJ%=1<dMAZal{DdiSHEIp?iMm%_x^=3tZvuzEt=xT0tM8UQo*5gmI}2!WMe zJcY}Cc<YSwm6n(OO3VAGFPcN^8LI$$bXdChfH7$J4~=s=foI>ByOyqn<kE}Mv0X5x zJ9t0$<tY&R$Wp}TH@j}>&Id+MKcp={kj4O3^Ab#l#E03$DZ$!nn@1+t^X{KC&tn~w z&(%0s-Z-hJwWnrpDtkWE*`3f^6B}Hs?^2*&-iWqTFDHU6oZ-d1;+0<5uQ{LGU*%+8 z%UMF2XVrEHq!$J}u<u+SV~Q@*T|Fp^)+@zP?9-by!r1lhDEctvC{JtLMMRwn%`65r z>bM?aAI%xP3Y6U;M0^r&?_NyRD_{kmu#q3SwNN*r91DrHPMfLnXTca-%fW~^MvzIz zHw+(|$bO3ZChbxpIfcH&lL4Uuj7l(No%B!<o2nAdVg&`+HUR27&;u1Z6uWfYZ4Z!G z$l9gk)mgYURuS(G@gd1AIdxvJ1MC)=sa@;Ko-$6;E>kliG?Em$9`qfJuW;D+Dc)4k zra?XKCLZgp5~_S7;Wti|GWg|hg%>p5*(`Fe@AgQ^JKU9~TF+2d*XreOZ`f3<I`1~U z0n;D2lpel81<Gsxpd~8LXpGsr+tU>N30~#@Dp>d{P=Wu56+Yz{f55B1!dG9B!N0se zY<&8F=y<`M)(;wdOcB2N8mWZ_Kk>dsAyQ!T3o7>X+CC!ZAsbp~31k&2s82fYXY4;E zLJLSsAc;frvwsHY6>yqfbqfp5@9@wUMZOE^V;^i!CD0`gog=8#NMmpQnUewJ$Yznu z51HOaR#!WgI(YNvtn&{~{}p-yRwAe2@6Y@Av>%xT>?AJDcyt94H*c8*?2^{@xCYM8 zruue;{_6TH47cB}&|h7j<z0NgLVtCAmZk>Zuh1{o=l41SGMYf{JK;zMQKMMIR`LO2 zAe)Fcdq__y(#wDqG3PLlxfXs0M;qPrMnL$!gl-z0=HVa~ubYC}%TYI1bL`HmMz~%E z@i@kTTWa=^GJw4e$7+D^$L_F4b77^J(rxV*XWWzoy{mKcLhNw_m-UO^QtQibS0j9- zm6c4ayJBpD;_!l53At{(%n9XT%ae#HDcdc6vMRDp66Ge{&)#NL6vxomFU^4F8zPIl zc9+d-peLOj4`i8Cr~9s%PU}6E>#%x3?*daRjf}@J26W7-o+2#o_6A=8$hmEAb}8;p zt4Ni%im;_xFbk3BePKAW47y5*O5GtX*X8q8+5`-0Hlnf%JZM#C*}k?5E|CQILr1`J ze&mJJ?Kcq>j9y|u-$#IyPadTg>=WY!3lJmr$=@X6;F_U&1a{rfni0CHaX;xAm8f>T zAR1`#@P61eK<htB-QH!E`@ne%YFtRQ{KX#pf+T@WsNwVvRA$!Bf>LRKn(Q;s$Y$#; zy|ry|@Rn|U!0H8mga7Y<EUq^`vz&_sB>`OVz*<i%2M}U$Y$NN`EhrA4m+!{)=mF?j zUGc=7K;zHB)+~AJDZZB{f!|F5Qp#QW`ijN!5}?)8f1sEj*!1y)KgOB>TY<law^{j1 z)i!(vs8Ot!zri{@<|!lqot1CMaap}6`+=rk*9G8G>h*ie815CA<*@Wtd}8^}Nd5<d z4nRkPU%X@S^pDZfa>pxbe8YSRc*`Yt%K*inD=;&FCQrMMvsuuR_z6EP+$KLT))RYz zS)FwgmyKM-^<4Rc9v?gEfgk@mdIZ-PfIFYbZ;K5;AD(x9c<0In*THDF%Q#*;LN5E_ z6Ka0L%!>{1hI@}dD~sSakJGaorO`dRl|SyjJi3>Eo^8Kiz!&Tj3dG)dOxQE!!@yD= zR&7!NO)sH9(|D76GvKukAJ=oBL&b<I(hGJk!aci7gy0C^_`&D=OMsf8&0J1SLS_j^ zc5GAg_}OS31Yhq~L{bQldy%KEG6e4=zhKa%Jdk&)RSt64pfY3XC==s}hl%qX9WPV7 zIk&8=@b)&>j?x}!vpm_Y#IvAL;^t-m+^IxooUrDMu4_w!1LD{aevw7Y?qt+Kz$qnU zB6LrgE(t4SmnC7n>hB@e?>a{|`}@*cantyxQ-YsiL^p)+#`SI`^T(jVDj;Ie-7ey= zaaGpGVbfCXUO}~_<TlErq#6-MF8A{P9*C|8ElH?`mXa$$DrSGAC8b~mNjMM*bJIuL zt+LJ9_t=;J^9=*yeVdgLfPb$j5dVP!-Tz^{lM8{s8`wK=AJCp@*DctIA9XbGD;@oy zSkdOC<+AqfT>QCKTX{Gai*KEa|CDP`XGh_OYjFDD8a$ahAP}HWFg5$$jq1NvYy*X= zW0P<(meAiQT+VMb+u$1Ze(jH5-tW8RKCV9Z$u0Lg%MZpBznOgx5{3`;0kpWRd8Tl9 zq7e68qPHxEUB>EwIKrAyZ?Z|R_j_P-Mz}ZzF>>c#u*ALewWxDh8lh~tB8k!ziUVG6 zX$7rTTdi7ee4A0ck%~E~$hXDqmZ^Ar&^g&$-!l6J%eWPbA=kK;SxRmbhFJM%I~{jU zs+`qS=Umz16W+`kF}-a{TIYRfO_-{)SNvPU3;{Uydph-$&v0V_8-LY3wItpyL?6En zVEq}`04w<o-1r@^v4CCSJIB>u1{>fB-@uLE0UJP>eFHas2W&iC=0CxW-vJv7Ech92 z{0`Vy99loajo$$q57*#NaN~Et#={-;6WsV6u<?R@2RD8PY=F1<4cz!0u<=Za{|YyL z2W-523paiS8#In#x__%i`?gM`D|wP;9XXFQ1Rz=~C-YKFfWQ_@oq>)d6Z0B4b>5CC z_{O2Ob$QEjxfF;w0-eWx;uHjiebM&QinqNNtc3CYdap)Fg~si)zr;MJwjBj?af5Es zFi@fOh0!m!@@VdJFR9AZ<iM*}xRJ0ciPK*Gi<otn8-NAgEYt?;kq70(zWg6=%A2~^ zw-36T_@TS~|6z2uNMh5Q_V`y0`tfwtPRAue9Kcn=y^@u)l<*z}`w|0tb}iN)`uhiW zyGQ;)f6xDZ`db-=<cI$5K7{s6Bw)^93B}z94M_fx6XgYaccbVOB#_BnFyGxMlR&;- z8xyzv#)<OI7yc*iq(5?&EOP44-AP}ZC170ao3rHh$ys%Z7<>W}W{$ECIA;14jv?@J z{ssREep%dW&Xq_U3frxw(WhySovgtj$jjDUAFkuz+s<~Z{N;p86)&xAJBdh^O!J1$ zU8XbEO{mCou@hm}f-7<`;aM7I(y$BvW?IQE#7K%eaDA#0FWA;(3rgb|C?kcW(Yz+! zA&KpU-YwR|(pa^Qb3PVm7hiX9)K7ij=v$l=*t5>xLT<EPFt8J@%2^2G9rViTE&&8^ z7?Xta|H!@Y=8kwvvw3q!e4pZpFSw$>KK&o5Xats1U<^re7S(IcSO(Z21_ZRKxqIk^ zPwRhOf*&9D?&AbJf-{RL`6E2@HLUX~Kw~`uG(QEemXq~I;j45B+_sBOefAP`2cOAP zAhV%`Q+yfMLvMW=*+V~C=FJ7azvyUi%5rzwJc9JkP%zNx8oqTcwdNnm-7F3C&w>xE z`_fNzK{xeNKZ7R}M=+~pMyKAf+Qj`Zt2b@A%;Ifc0C^P0DFWgNoRbfl{t;5jAG6WG z>OJwpTTrT9+#3t>0Qlr<@M-!Mg+0h{U#V~_Af4JpcjAo$-rm<x)vvYS=Rg%eLT{le z@S9JZ@?$m%{TWMq(47}=37%z0vS7-MqHoTIVxR^Kq}f7$;eit#TF>r}4+;wAfewJI zc<%<etor4ru8+&rpF&oj0#=l$7+|bG;?!WhvIUtf>!2i$bZbU<Px$<z(_e#B_z+}x z1`yDX(A$INcu6t-I$!?sjlNhwxq>M-du@{>hY?*yAMC<;!8#8%W?$Ie&n`QfvsiT3 z6JM`ZhBQFu6bQ^vEl%q!8nyU%h~R4k^@-10w;~V6y5E8^)DqMmWB4${s3?w!Fi4V> zO#DVuyR|m;kSWkue9k2%B!qoOw5ZGPLepQl9Hw9P8S?VCQ@u%XE5as`bZR9a#B)Mj zbb;C|X-5CqR=uKr&&0*v^*`}VUF8o#f1qHV)rRThSep?5>`Q2$mukLe)t#n{>?^M) z&<UMm6sImA@Q)4O^d+>s^y&;2O6Nkj^RLvqi=IE9Qf+xKONC&+o>Q>99^{Gt?Nh_g zC-=+nc;Fi&fUpGk@9+9uG0tkc$1lysy^9++9zti8+<wUjkbFaJ^Guz2rJ%MS=LiXw z`7qDce@g-BYu)H|Nf%__bSO9$VB|KI7(bS0`8<UH<1W^W1~9RpK~K8FCEa*ZorPTc zu~r`vkKd>*UuiXftXjx604`si_?qUcEEI^~UjR@FKupjx(RWGJS;&ixS)3_!;SKmJ zGkgTKe>um$GQ%a5^y@kPl^HG!Z9nGt8_WRC8Q&dGz6S6&e29cvi?$Nh)rY++HN@Dn z>LyUdLu%W|C6v)9zNuWd^Ezg?Pk_YFip}5xxqGu60_axG-O}{22uL^W!W644NzGg4 z$Q4WxBZ6>`E9}-eGtj=Rnsy{#RnFGobQ5U!ZforofVJ*mPoiP0Z%^Jq($T9ol#JdV zcETF+CJ2h9GwZ06bl9G#nO-GYQy47;d+6>8MJ0>8U=76x@>x;iXkhDVg41^3gn4jf zJ>pkzwMw(O42}V3?(Wy(Ufzz_fg;$Z#qNRY%1hbPS>r^Jn5JWqgj50RS&A6jR+H<V zy`-VJBmGT-NF_KY(OyZrvdQdDb%b;+Fy~k_vlmPrZqr?Z^>wJ`@5}%$Kq>CH(6^u8 z*~;z;0kCF-V>pRX)H`dy;-^>;3jK$?0q=o-!Ii5EL_3IbE(J70F%9Uoi#cs}zT*p| z^Tr!62n5(!JFKpVh(22%5?_n4kC<g|;`5u#dBGy>Nn>MCApk#5s!<C2IC=wrCj3y} z7KtoKY3Xi6nUW&l^fumW>5LKu?pI+g=35>;pM-LQoUeL~^&@okIrJ8+xfF3{`_9Ce z`hpF9$Rc-gONNIv)E3(&x2_sr5!)k>+^OkHC}hP(Y>TTp)@?@<Z*9EU(Kt>yNj2R& zupe9mk1vOMP6!=d!{=)$oij^|&}q0bXR3v6r4$F~Ah@ND-u4<(lOv(3s7**WJ><cD zVAZ2o*hPp0hqAmAKEozr%hp##rxCK4lryLKHbcw(ty?kp;CmG&v;JIfcOI<B8aUnA zuDGMp%A-5%=(%Oz5ZMLMr}@CMYAGBKisXkVBp$0S*>zqa9xfV?zlwq={*iDL<>hbE zYES91-k#w*F_!8fsjBnjE9BW9|MBvl=<4sJ7lzY&FR!K8pZ?NTVJSEN!AQbU_w<1? z)a@Zr>C@E(vPgx`Lug~|;xBO~WRa1>a{s?BqK>1&fu&Snq&-@<KJ7=6{WF34)H9EA z@IL#qf{)8LYBXIKR}OD7)Oj$jd>TqP`LH?i#6vIk3*{~U#=0`43_xYnqv!oKdTUHJ zVbhlm+z+C!C;8X$A-!L_hXE_hnIwH7t6x4ni~EK5tFPBa=No36A0%a!PM=<rh90AN z9Sai_$4x+t519|F`@2BAm+Ds^-^!bfw-j4^)5rUxT7NVTz<yPfzm!Km%JP$ESw1is zHmlmEsUo#jgKa|@y}Rwh^-3Sy`wXypHk&Y=*k!ceyWM$b6Tk|TO~-M@bl48O0FlQv zmMfWwlTKSP#>f~)4PG<hL0Cmx>tTC<W2a-o?U_@??5X~~1NQV7rl<3E)rgx5oKC*I zCE#tNMlb`jTOD9~d22b>u5J8$o2{!$LLAXG>OdwE4ZPGXiS1Ow>U@|<x6MJ5J&eVb zY-@97E@A4aP<I!H#-Y1G1EkOTPHV*e=HMwegfJ|tx@7`D4c>4^uw5?32uZG*&RcUQ ztQIQwH0-8HI!3_=_u+o)9m~QQc#3g%tVxF3#1dR88mkr9QCS-ISBfz<7n!mVBou69 zy*KMCyP`s=vFY7nc}kZg0Q%O4K1*zo2Ya#=IdwygYT%3MG;fzQrONB{3xJd2vzS4k z62}1Wn!}bmzJC7&d%L5uqN}Tae|rAt>?|SbKR-Qxbao!T$Dd9Q*j?CnEf@YkOOR*& zu;)3sW)4*Ar8qK4;SQVeR1K~;;vC1Ukd?|^IT1!#XpNp7TaXXX)mpe~Bxx@&K*ut! zQq?50jat>%to>Xt6S8V&$?1ASwWZAf+2=EFn@PfTES%RDKilfT9_U+LsAhJR1R2+a z8FcDT?KMH2BAWHV3*>NBXijoEDS5L+=QQ$s)`d8qWu->G5TslI<iZq_wMPQ+jvdo% z2lvU;n=5vwr3{PiCbCIZTXJ^8p5W)3o_X5<=pSh93hT_hBL$@{QJ&k{yf+L9n=IcR zRZoo)EIp?=kfsXHD!R0%Za7ZXjTI4{Wjb{w&PYbwbyh%$K7RR|8aZM)WkKs-8!L}G zaa;|Iz4iz90Bjen^KNk#<>6;Fk$j^jjFEj;6ZfUj*R*6e`m~amw0ZGpbO2zq^el7! zv2oau6yE$=Q~v3uHwpHq^6SkkAr}YC5dE3DsJ_|o`C?SOcRUG}MrupZqifx->`Bd? zZQA?W``~VKU`o9U-=%BGoA9lHQStGvzpm@vjEjXu;=-c9yqJNS*glPmrQzg<YnSgw z|2Fm)?4KR|+dYrmkKY>o*E+#3eZxQ33Bp2qS?z4Pz*(y!@zWJPpyK|JkTe^tD{t7# zoEN`f2SG}=o$mNqr(fmvT9Z5KaNbw;YR=D)zTr4$9Ub=xH<0XYBMfC*oiJC3E?Xy% zyb!3Y6*zRksN`<n!SO0|@7KmXtE#b^eC7Zj+>MTpm)&Yqv)lR>we>!X;wI6U>Zqfc zX8<5;A3O5uuv?&44y~V|$*>Ki3!+TqFyMt*M0M-ZB)kqx>L3tkJX|(#us?@*F_Y}Z z0}zlyjN+P3C3cgq^0*xcrXYIKIqvFN7k%mA1-H$7hO4!ui6;b`{T$_Nw>&dd1eNLy zsLhRq=%OY@Fxtg(w6>10PwXzceS|@QQiL~Lydayd9<ueBsxbQYtP^BT0?QCT^$lO! z`&gYjC{72}H@!L7+)09L3KpFS{F=P-`Li?!R0)&3e*Xpg_Kw}Dt?6p?$e$t1S;sE# zaf*|;I4+lJR?BQ%0ylu6Q2E8N^|#^DtnCMAd=Fm(%(yiE`*9EX2wnaq0QM^tvh2xU zFkNC+C?i=21y|4X=+=wn*$NvUa{^D_M0I6f&s;Yw*+hFbnuRd(x{OSBw7rL}8@UW< z#Z0pSL7g6}OL;ZTuy!h#LvE}}UXYDDwq@xdM0a2o(XG$!YXdqVdzUguXibcbuA1w5 zdk*=`)-kM2=yX%9)Cf89TOL?GE^I(nc8|!3KLZ(c2i95bH3tK6JJ^h_(~u;J(p3jp z--kSE*>;T{<7=2kLYMpdP2es~l%TjsFb-@)aJo(H>|li4AonxLnH@ujTS2kv9B(c; z>(zL7HqzYBkC3k2sH4{n;+)BPDGoZi2{_7m*;tBvW^bBYie|)hQQmb5=f@eW;3v9B zk0@DiYZ8rHggC?}RQbote^^vVM^we%`7Zqb7Qff{MTVVql9JzB_TmDq+2Z7Yw=XEj zNMdh+6!WxX$A=_k3HUQ7ln3HYe2A`6C4tqM=!|;(8uMCeNIq^7fMfE2K<=FWBxG28 zVJziNylWYWu@Qwoh51i|=)-=x;wPYT-K7HLiqDG@`<IYjIP@MRs?VU<=|T`Vv-$(= z^bCr%U(OUzt9P(}U)u^VY-RX0pM&C*`y}<&UU7*Qp425kYb*CB@9XmflFzdQEo{!m zj-~F9`!%FyEJghv)TxG3u8sAAb{)^}4kesk=p)UXJ&C$b{Fy=V1dv~<xU`2s)p-_* z1<zNuI0~M808hhdBap&foPmin2axnMn;zE$@_7w_Hefg<KK3Q(mJ1(xO|#VKo&y-X z4{N-W7a#&&D-ChOtRJ!j5>#hAy)j3Rk7lS-NG&f67ee}{a2u&;KXl#__69~T4dCyE zs$bbTmVjHLmggLpYq|mbu|x6kgjZAVbG0B<oUXo=_X_iTYY!f*N2T&JAop$N@~42@ z-cb{bkfMpIRZfc|siDx$DsF_&4x+qCVG`C`VFh;7=D<_qjkyA_MGZ}5kbNvjImt=| z7mhorwd`Kn=Z42n(W*iNwB6eSJB{p4%!XiE0W)B7<Z7>A^2As<KdS-_ly*jl=dA0u zd+Xwjdo$yVDcAOq(_vSG;z(p&oVRRh@U`f$K&H;dkAU1|1W18Je&xLSLXKH{)o)6W z1(G%M@Ji?m0Gg<KS7+fxORtxu<B{^F-xw2UM=g2%KG1L9-Qm%A*JyY<e}5i7p2ipK zpE-{oPvZ;r&z#4Pr}0t6|J&#B<7ot^`@?yB=+W1De@an+yaj{!<=%X9<^W?xWgnc= ziTYU<eh`JfPN5=}q_@wZQzbiPpXd3<vs`)?eeTf&CaOA@4}3sCt}Fdl=>x8(y!FNv z^yrQBIhl*V@z$b0xIUM0@a30vm=2@#=nnYdP8KIdi!1)@&SSkaHI4{$^cZaY2rrJZ zPA#sqUj)MNHqh*}yl5&Lt$m*d^5f*mnV8~IE^ojrx<hF~-8PlOs+RXRdCXTFpW3^0 zjW<2N&g<j3h`cGcC4l!Yj9o!Yko!o2tm1HY05S@K45^lOqgX24LD)Jh+``)Nwldrc z#axs_CU0G7LzJV_&iBJvJEfdyY>jNcZ}_?3{g?k35Yjyn4<1Wi$~gJQKVJU+js%G4 z)@5-P05DaRf5-YRt!`O2eZxuvMEjo!Ypor*H!76r{2Ko<5?+1FZ~wA$78$*8K!Ckp zemPYiAO7i{|NJYflbIfs@bnM8uKy)gng#P%i<44$vpT;6GB<&^z5$u+H2{<Y0P`9` zUur}@YZ`!H7M6)3@7?*ccKZ1AkM}GY=D)H@AFaJ%LBB%cMR>JHzg+u{*XA*|-<cV( zoxhpe@67BWzkZtA_x38A{gT&)cm}%ICC~-r2Z?85dmiXJ9Wz;++x<pB$tfs!Yt$X^ z95tHz96nqHu1ehN3swu0Bn}CJX7cD4+q*1orV25FDinY<DcqoGHLUw)RLs2WT7{04 zCJI^Gii`;p;Ty1gCB@XNIEHA*C2Ns8r6ErSvAA(A+Ky-IHB+toy{;=%5LvUy9{Y=I z!3G==bH{1uns~v^*?QNhP7qv7c)Hv}w7sS0s`bRu4Y06-PK3SM`&N=+>o5|kju|6c zz^E=L%bnUe`ofNyp>@-fnhNKKejTD1EzwvbI~&1DEv_ch>u%=FqHfQ(5R0$e>a>+_ zYPXeL3b94-Hko+<m;%?$%xn#%gqvpI2G~FP?l6~Ozm_fUK%TtjhNf(;*F3xn9Yee# zp_lu+s~~`PF|6F(Ds6Qe_c_Z7V6P5m?tlx?DC~}$xRaV7TDkpk3JVgi+07Ouka-=k zI=V-c4Y2rDTd8U2(KC59INLX{1O<O;ygkn#ww;)wMI#!cFg=xG;{9@7)AIUoly9P6 zGnIHPRzNFX(>gnFmabM>TU_l?X_U=SqwF<X_hnbF7?N3)6}%M_5h`8TT(6XFC8<yc z4`V?9o6((b;ks#pgQ)Q_%w{vNFY~>R+>j=rxQn~GU+C^;2F7JNcj>hO`jI0?X{HKt zin0~H@Xm?-dUD2mZSk;^+g$@4+uh-I_ronV5cwwSy);#tVDK*jat{fk)!}_Fgt(|_ zBTGKo6+P0PjfFw^`f5+9kp6+s^)v6QhkSB7zhEZ{;rI9Es+{`#v@IAxs34D$6~)_6 z0{uoXu7<*?9eECGDk7uLA9UUmoqVJ9X15eCj<$J(5+58xM4xaTR;HQxQ{$(<a5u~1 zw(`n(2R@ri*uq@S^GW!`eYX+8ys;(hp|^k3_41RpUecf6fBUVqd>_ziA&|yr2E8o* zG5SRs5$FR4Et2*f2@T$}UvjskMlGqhe2HJ(3xPw}$MLXZ5(wk@?z~Gm<pV!w4s;t< zeUPs8E2ez!w3$Qq8lX04>2#^nIe3Co<n1HXfWRl$I=!U`Eg{?|rXp&=wZcVf3ZAuD zQn()1gQ<F?5G}J<+82Dpv43nYvnPD;>HM-Jxi9HM4;w6drgp!k5B(83gWv8(N0)vk z;eWzdKQJVjd`Z4jZs1*f)ambN@&)?~c=CHvdVq~j#pD7TjCKT3ww2UGk3>Iv6onwn zX&={_anev~8lz-?(BV+Uavz5Zjtja1yG8e62=W5E9$v5ufT^(VF&xzIJyAg2%QiW; z@g%uaXm~vl!FzMgZ&xG{?SL+9Le3+Lb2u0o>Edbt{BXJ?oSny8rww;lViyCx+Z^gW z)My2{;#mXPB2^0M<Q2a~5_dh^C{c7NTY|N$w&jJ<F&cH`w90O6Ec7<5_Q6c;s9Sbu z@60uJR=D9-`-@XTx+@mZJd*oR@)-9vyC%7*L>yqv5@@x9)DQ}KK|FUlM}bOSuI_|v zl`Yk*;j@Y$nTx}5E8e_YNluW;-<V9vRx!il=)zlT=&X2R%UP+z60z|N%s!GilR60# z>=2Y&+HPkh)oM%iL#0)cK)6)p<xCM?Z;I0c`LN5!6zD_}p3ZJ;Ny<i%n&UQ4$ii@d zx}RJZ$R<HMsz)~3LqS~haOsi|d+Ga?JT}0Z9U8)D$l(Tc=5;mTU5sVfZiMvA$#9Nl zc*f+qm3gF&5kk^nb9DTfM;z@!2<K7lf##K9@my_dJ4N+;Q(jI5Fe(uUPQnRJT{oXR zATiakEXeU>o#i$jMigz#r0)aejM+P2LG=ZM;g{XCyH<)6UKDO^t@n(z&PV=4s4WYh z3>c}Hy@;r!laU;{ad|hot$cyHUMDSm`M*V}J+09yyVc!MUY~M9X$paE<|CT~2?ig6 z>Q*Hi<R35pi|%EkSN@$=mv|2Or4~F_ZD4zh{A+$0Nra3KyGs&e_vi@r<Ouv0YyHdD zF#ai6G|%QoCo~2O+v<X0OkApVFxHF2=y>_IgZBC-Y-yM?H44Nl%Rw@h>h`!@`pz%u zl_Bxu^!@W-<EzJMS|U)QPR+tan1x5JyzuoJz!LJ;Q2i;m3nbRRTJ^#i3Wu;*N<}u0 zj$jYk$`A5o(H*}Cyl1~+!)Kpt_{qYC|7bgSuJT@*g{riwXdO$|rr^Nh(^Qfg`|~`Z zkiX4j@v!skSf_$*jkh?%93m5G#PMLOFBlui-kChI+`5nXhBnY?46a9dO^-BVprur= z4UV;4c)cs&dnp+@<iiwog;!1LYUT!<kwgitLv=&(j)Vi^430pE_d18PF>xQ!aUIAT z9d_tuON+>{OLf1YTdz{CL9WsFjsqHSN=GSj7^?$^oWQ7FzaFHGN)L^<&WqbRnAxy9 z_xjn~@%wxz1w=pU7y2&5b*B?p2av#N-IbHTb#1W4x^o<aKxdz#yxHvZG@@cD?d-h} z%R9kGpeDQQ18NMlAR74drgIx_3#6{4t~w2GO1;Os>WE=<$Y+j+%(T5anR1N_jAh?9 z)Ml)PGg{NJM~R`gG1AlyG<~?gV0nwk#g1$%xDJQTXhMU}qvI~`7NE;b?Ufsm?r2t` z=I&rQ*dNxLQ*osC3Y_--`UGT!NjJ^`+jjXX*X+JDy^@c-A@Chshw4b%23FKCt6QcA zCqj|Y66i#Phk|wBPCcqI9N1CXro(p`6%%l8X!hMfiD(_2?2I0UEEBsn@{aNjR%O@W zQwJ8kDcX0V<?s1kZIp^x_=GuI&j{rpA4H}<<I+*Yq&^buur5%H8}SY2*w{`I(3z-f zdvB_Fud};e8&}6sPsJC^u2vLd`QqIotnPI7aHo)RyKzRBI^>!(k%NkMLVg&HDt$|7 z^OhLqmtJbRFiUHJvxuv-r?u`f?}UPrZ-ik=^V5IJsKSp<Y~+;&n!Lfsg|~4@HxdX4 zSZmlKCr)7LNi3}v>A~QMS#pVhEyRi#I*e>8uX8QgLdPi@Nq#Tr4OmITGJe5V4CEC> zVA@a>Q-DT-xRFyJZ!FpBTSS;6PTht0(jr#xbFK5sgecXlDK1>Z;X+X%Ej#T^FJ-U8 zQLP~4rfkns+~EfSzN9O2z8o&mS+<i2*nQ1BXlvNAr=5Or$IBWGFsm~XJdR3~7!E#a z)%5@;X(MnYyDR7hcC8(T2**?oxxxdpa4B$>(~P-dTCC(8tRIGz3Z1rMv|eM3vsHD@ zzhAwR9kJII-(R$|=a^7rn06&A{t2w)S(fp2THglbavVM1yKeC)F&dXW-&r>;HW-r| z<UVP6s6RC3#&?*NknU_C?Sy9NlKl&o8ko6GcUxCqpR{AW>g;O6c|sr6X~H%#6l<Zx zM-3E`b4~7)nY?4_fD8DhHza7k+AK*re5z_PHe+e;_|dsR<jWb~7q$|Y%!IAxZOo#( zarCv2_h^*o@{u4&-Cr^O)djl-pb>JF3GJD?JCq}+xYbnpz$>?Pj~`*^w#qFI&rmVs zueK#-4&lTF%o`9I-PYceBbE)ca9Er8?RwpJF)iUJ5I^ibk50}_=HpZ}<3c-#ra2Un z*!D8a*BWc{{dCnYcLXv05@4#fRmMUSB|AQ|mC6b*&MAb88&^d*s4Lr?EIa9-?zpF7 zV1cx&o4Z#+04{g?jd}~n=w(sKq&Msb80*W`)sG#q59&NuLQSj#RGL=tNI+0r1OcL8 zJyKp+eSM=0k=jh8&|~?_|6Dsx8_SCJ^1q)JBIx>7#ZjA8rT3|mYWS6s*!a*M0AJbz zOov3bm*$qI-nH38xF|rqp3|TZm%7j1(NBaxVKo2hjX@VJyQqJc2DSyQ=AWzsOMGbJ zrJsjyoyMRz7E8g2zeOKjx+l-EGyQk*^2nD%1kD-G*w;{7cySE;Nsw&glfQMB+SR2M zV*ALSe#-|jmwb?U^(US=0x!Rmk*lTvWc;(E$TM<88^XX+n~e|Tun+V+OR|ti;DodE z5wPyyr>QTzh^NhBCjqQssi{i<%kHy3@$$q1Pg?DOIS=kq!~(88{9aH_vlIn1m2h!p zk;JwVNfiR2eD&bl;7OSK?OHyPZ~=08`tc}$R#-^5up>Y%<FnnJ@cG*1SL^WC)56IT z;HJT!fC?xtQm)NijxU^JM2+D0RW(A|6vBF36=;f@oqPz6YbEPY^Ri)3tG`8^O;USL z@76m(^poS&An5_qLgxj$4QN|K)o_|%JvbUV!yesQS6aqYC^gtrJx5~L&X!Z-%;Z|? z&#M*YON5dm6)xj2IFacc8_(DDxLMD2>7MRui1r$T*of!|Z0193^imS5VrTLJ2lsiS zCsBLD^B88(ER?+^vY-&V)FY3Kp7ztG*{8S3Z~;C{rZHv@FfyG__xT+0`yC>~#FeBE z7`lT=pmGsuO9#Np$-LqqM*7fQG^k$4!8~gP36gQ9E<TL<I>ws?U3CSntV4NT^YX5a zit7#OsN0b1fe$d?m$Y#1nYE!lnU9_LW3Q%gLVr3UOTq2yA<;4IW0zB6$pr$s6<ZjO ziDySt3Y<29;4pGt_Q-P&Eqn5H$5fQO&>08|9=|9HwE?(}d#?;+A^D8oJ$kC*m#;m8 zgt1TDj%l{-K5}Y-;!fP=#2gTh4}RU1lnUo{1(g6j$>e6kM8O%(ZF3)(W<N9AQwC!S z4+*QSZm?MtQ{H{h4~D;{XBEo$%Q>Jot9n&!7HhAEpg~>nh@|-Ea*2p10I~8Ta?dzk zn3)lbk&5Gdi&BF<N*=jCm4bZSuk~#YyYLR^nY5xNc9g<N4&%|NliKC#*3%M45Rr5* zWQXZ?vvy^Ndo4`vnG8bBt+R2V%PF1cInIH`=IE8KOnQM$_OS}Pc3zhgLKJ)F!q^); z!}`n(abH1i3VP6DexgVVK^&#g1nt`6z^YIzsRY}Q9|cRB?mMU2G`qVl$kjQ(VSAlK zM~60rRf=38WCwLh7qrDuxOo9pH&t{-U;bm$)1|QXk6OCkZY}H|FaODk^1S`~$-5=h zM@8{nt&;ItJh}Mzd2?&FJ|h6xewy6v;m*IdUteOH>?T|a+-4y0Qa&h_T+%;Fu{eyp z@)nkH?S*7Am~a{h+|r+Z7<vEg6L-!x;TaNLjI8#(X#gcf8jw!kD}t|eJ7A*$5qKh> zM`a1$5+5eA%tO##58)&BBz_&|p9f|a==v7OD84M=!;A!K<md3!H=&xP0%-{tIuI~l zpDhS3;9%<1a%A7G{-QSN&t~|7{m~r%$_(Gz4tzVuKbzsAg1{{Pni4V_QrMrDG}*(} zk9&hs4+w^o8~6o7XNKUjDA%nF75qk2HidnMr6!&?fpSJpsK;bkm<v6*@>j|PO*HHB zl+|BVbPfy#h%W6e9|M8SZv?*yl^!-$TFxC|X;8;q)XFf0918P1THdxFR>#b0EyB|z zEN3$n6$L~BAUnZ$y%~<#VMlDH0~^p6yuog0NFA7Fr=v(EuFkLs$+rtCQT(CyQePva zdAAbswjzK!%{zrFg)>sLZbfql>|Dagu&v6XWz2|BEMj)m?cOAak0Yf?2+=B<*DP{r z#V*GfVrgiuB-n{YjkIodvwVdq*-zj~=}J#KiARY+?zS?jkrJoZ)Jr*F<3bwN=Q3*| z_M(E8CNA{F(ee0K$r}1w>lw-zl^MtJq<am^cl&e<z!QiWdnBNKcATr98(Eakz34vo z%Cjxp!%e(2(SCKo|5{y$Yzey~7f$`29ay3>YVI1BD(~;F{k^VUu>H8ZVp7?ot95@I z$~@$BVtYC}cShgOMZb}z9HmdUp;$q&H~YOt82O2Jttus}eG{%<Fo;}TAVCbzJ;Ka_ zjGqabVY)*@<>H{Ie!2H{#;Uqct5jqT<b8E3AyrD{b|2T^e5k-=D(%hsJZyHW$?A3& zq>iEaf}Mh?<>h*B3ij6NrxS*ZJIOYwgL}Ol6l@cw&RL!}m(>ga+8y5J+wo9Bfj=qi zrC`=>+Q=#~@mCny6)XPaB4f$<mI-YX_EzbI;C1~q@p2}c8%=pR!eE&S^FAuj7VetB zb*Y`!9V}I5j_j#0_oo5o*Ye<QLRV%jW3QD*rdqfEc==zoF>BkPD&923-#5HKE1!Ld zg;;&ykL0K^)x&K*2ir%f<9qHhaTkKG5&M7r7_xt_Wt}Nsz^`voQ#0UjVLw>_@4GZn zH0=KHJ#H8Ae|V41D>l}jY-}hkgNe<<xQ@|nC4Q?8@as>$$BRe);O783kNxn|AM##X zoq3I7FBxy&#j)SZQ}6VQxo=hj*fj7`z`bnqHxKK}g92N4WsDbfVDc|bt1sAAxZn@I z_}6RG+qJLj4-V_U;SK!oUj8$$U?E0Y7&pHqk|40;!MI607&j9NgZN@>u-WbBL%PBT zQ$Qu`R2WI<v&R>#*y5eqW`bZH>~Y<A{>krT!BWkGEH<cnz7mzI-FFw?jKhQ0WgKIk z?B-@!>xw^zSqgCHa1HLnzR0z@oJFkmFVjgR(^T^*tUNlqz&wh6fX2O7UNigH7{iS# z)_l;!MOYXCIPQ9u#~tU1whchEN|D%lUv7{6Wqk+B>0%M49yTp=z_utp+)H#1Md&VS zqJ}($Lrue{rRzXH)?wKR=>*wD#GB%MPFJ>mg71aFoWl)XJ7ZYNTlPx$yYSqRZU}LD zgiq5p^aCKYuAyfS*=1AU`^K7luuTX%I5z!N^KYKOppt4^F4S_qvF7JJc~weUFyOM> z_l(bY3l}!yj9xgK!6`*<#{B?g$5XqO&m|7uHn#}2il$FnV@PK*jMw&B)V&?Tpm%bm z)sAV1U>3vK7Xxg6m?E+0uRBpf82)6b``*<nTNky|rg|OOWq5j0r%2{23%L`tB8(k- zIO;x_#pZ}f5g{bk;Ckf>qqZg<W`iARH~I`;2i;?jbZAZ_VIj45P{)gGg_{W*){w7p z^94XN9yS8m-g|X)h#c0e6|fstw7g=j0-Ac>)`2Lcg@tGk<=s?5mb&7~O-Nj_q+;{p zYykyw(fzH$p#o!HaFH)B2X3ScjJt{E9V>tx;P1nGCOU#~CV5*&@Aa<8l6Aeltw{7L z+CZ7}w?VyV&F0SHlJrh4f~O16n!jPWcbvD_dWz!?P-0K$6(*^z;KqpIA3y(^nk0A@ zWUA2RWw~WMRhWTI#sC*90@Le&7sp#=6&v@c3DEjW85t(z{1F|1irm@68~|{)JJp>P z8y8VNe#iN}<i%h>>rleb)`=pL%6j4NzBMw(=HncLJvkl4n8Gc=zTIl<n9TNV9$KNi zB*Ll|A#D#MQHVJQd1fYt=~Y3ap@WFG`BAU)JqskQnWEM>TgMDDWb2N3uEa3Og+zsB z8yUo0MwqP#{>dxYdZX8P;H64~oX$*AkOk8<08q4X3+(yZy*zSef7vVa(J>iMTBV)s zlL!3l@@;S6IS*wz0;>pPb{JBf&5>n1VRM9{7YweC_d6jE$z2SpNilUT$7K3OHDLzV zS1l)FJt^%VQW&gQi#J#7RTSlo^>FU;;nu24Twv|rvjmlri==Kk9pFEAoo{P6Itm-X z%Wn!_T(Cf6iUSE*tE5^B^`P>M!Es}ZWI)mucI34IkxLpH#B`bQJlTlb=8BlN^k|ZK zvRdW+)UYTr`)g|x<@chB3wPteOf&2m2JGq`30)e!6;CJsLO5i=o#2f^Y37J+wv!x@ zRaF)7_D<19emy6hLm_P`b!F#m&iiW%-sX*4?@1T;+yWnM?<Pi~a^1HnUY&u=ukTGc zOz2s#=96<?4b9HvR{ebxZx_tMc)KWCU>l#@bSNo%y@a0Edpd>edVS#%I)U{1teSym zSlRx@Wj-<8*OR<GX`Abo3+MLeu4c%2!RGA7i>gJ~@zG%O?rsb{JjG~Ij5zMfM8oyd zR=LVjW_kF;_OWCt<T(^9(b-YZX}x>-F9Lqs6vYgYxr|ErpfU>;$A2ZWBwLaX|Bkh7 ze~ZGuv*Zsemo|Q`YEzA+`2JOI-YL%t>xVvnR<PY^9kbsvcbSLR<+^<5zrQAaJfQ)J zWp7HJh91!1p<IEbGzYLbEy)?0@gr=gycw_EgR1b|gQ<{GoS<s#sKZW1E%HFGv8Ai> zcjzVm3B4>9yD8xBRc^vV*Bg(lizkJIdL2)1Wt?|>p{Rg^{=pJ#J##a@@Qwh3v-`Lr z_(DP#6y}wsa%w>^3vHp200i@vPk3KSMHlbUyB~6<K3}`w`NwesjrmWE{NuPEIWxaK z^0#sSj_FBsWNJ$&C1EyCGlzYctZYiFy*-ez=UQW%L6?pihH|GgA>|AM$J)&Gb1L)J zontjF>!z4UCh`O~@|Bx76wY;<q$zH2WFQ$xb{n>H1nW~(^~Yv4F_Td+aXFDTv*PaC z7;#*Y)vV1LweVy!P_bC)KI*}1qa~Bo3pT0)c_C$YOB|eamdNccA~!9NVgjCuu4Aq9 zBFJF3+pOtK8=>2J?51svCJ?(*#K1*>ly-uo3f8%=+*r*X?*%EgY+o=CdG;z`StKdb zNoo`pdOcN<zpKU4Jk6)gtul{ng8&4D86JJiQgq@O4?P9;ok0TfMnKobwc7o5%2(T_ zUK8-$OV+vSE9)I??Zz~wnNa><dd_(Iou4tiJo`$$c{Sf7h97#@EgDlj?RMO7Jo9{V zp*q9q%X*^2!xm=A`B{L!lu^?pW+mTz_XYd#?eBAhz;LQ$cYs%XcaX#>0Qw{6*Kkny zj?k+Q{(P=aSHrXk%gDS*@hl()$6v*KiwD&8?x3P@OrZQ^or8_1HkG=nt$SJAs4guN zM+Z_m9<PU7@{n^jCWF46IbiMD>qCar5yh<Xe2>Hld^nYwFNV84d*=GK*xT7E)(^MC zJ}6!=(b~kBLT@#5Y0qK#%?#yZFm2-lfA<leUKMJ!L#Qs7I-*-Q0@H1>gO4iiK5U0r z2Bs&oZ3Qt2tauI&joEe4Kxt}0keex>hU*~Zd!R_Bx}w4rhK}Rx-VQpcs{H{z#LgXy zUjDJFApdM_+RM&@9fiI_jZjo7elI;Z8`mXSm^93d0Xt{=!r-}bmDDGG$B&04pZtG& zm*%hBN<Sny_3tLh4<9A@(zy3clAk_F@^=oUH{T@wkl?q61b?qdRtR!P%o-=u8=Z#M z?QFRWqkV6GroWQEpL(Z$oX0!a&zm9v*5jMyczIM4yvo^!<v0ua=WE}s+R~@tpIf)L zWm_CIzqfF|*QWuw3@K&vQN!tawTT*lrDOt*l*!}reacID-Y)JYjTA6fD)kEi8|#p~ z(^t$y;q0REVLG}P#!~0GMf_yHj%U5Qt37YIZE$K~r#w~1#Zi;)%sAS?8Yi&Z&Ksg+ zMDfC0YdI7Tc#Jgreb0#@6vH)An8nGXQLt^#`~dB-1z^8m2u!TQJF$f0w`sU+bKDV{ zd_#-2THR0v$-?G%uI3eWWgT`f?7gx}`BmCnYa!Mjgaeyn8|J%2n<KUC#tbcB>4Z0u z*AA=M7YrAV8EYyc;kKUk!%Zh3-e~qAE_9W!k=8(hC!w{!n8~_Rhf343ED)<ZF2Zn| z`jd{;627~1+GP`U_UdvKq~ZM5r(r9MU`I$V*mpJ!Iew&4e$OlZ)Jq#XqKatx043{u zR~%e@V~{36v+mfoZQIzfZQI=Oj&1DNw%@UB+qP}r{q8yUoQNA;*%eX!tFxcXr?M(L zi;KJP1{8Ys2bm?t-RpQO<ImT#`_5O8wSGS0;8p_Igj1!xNb;hRjTwJ8`8!2@E#0rD z=?rTJpnMq3l`xJ50+*~c-PfFL2&M8=aA4Ca2Lq5;NedHWSPhgKj&$||Vl3-&eFrnt zq5ebgAh-E;wv7kkMlw@UkyPRBv5wUnOF=i7oj^LYo<TaKNcO5F+FSZ(b8xQVn!H$( zncm2hiOp8?98mv%;pm+slHOw9*C_@0yzP}Xx`C6gG$L~}x_gIx%z%*vkm%40=6LXZ zNT_0AR!o5_GIz6b$j-T%V3umvc0Kv_g;msIDF<%s$~J<NGX=BfJcA_H3KJRuk3n0v zZHimHm$4Vw^T+~#c$Ol?0bJawg~;T_6=V%+a4gWkL3iR&<UgzYD;<+&^<+N%mbGvN zJu1hLcf8*n5eCPFuSe!pzKm;KzDXq1uUTZ{!hV*ZR?6YrGU>~ZWG`TgaTxPcUTl(m z8D5SIH2T~xzbum$uFenlTB_+Vw?}V=?!2CmrQ}ZH9*p}=&fyOjHzyG}aud^ikX&cj z2CqcWdW3g1y7N9#Z3-2XKS9KL2JKyB{TLaF1Q?{Yd0CHOJZ_ISRkqiS`MRHE?fvej zx-bt6-HAWIYEoxzZePb}-vY}^9tPC^03g3;BH}$Mm}!<0c_nYb7gK;AHSwGHD}=IV zUzyJpFR1;Urrd9IX*0RYQ_%g>ug)}AH1>T@3eTL#vN9%q%k-SxYC#goWAIAutJ;_3 z@nH+saDdAtJhS%pyi~^Mx0f(p2D!)`sk%9NHlbX)@mk=pymS993C6sXFKWd`>{Qd6 zYBi;q6O0sc;zvk@VV1|IE9+V%2&_EkIEo1cER+|t`xM`BplJZ5KLplku(@YJX~-6Y zPoBY*a$69@5KQIwrd?MG`}6TV)ew-<8Y;PD@^0nJ`7jMII2DHw(q#XJO@(VB?ajxP zFFQ2F2IL@~(Dw>@hvThB+KgU`qwz4?ZZWyLbk0XpZT-p#Xxi0Nyu!a}fpR#-F2G;c zz)bQ09{<dA;}LWopyxfub>qs>j@h7X+mD08u%70w)~piL*EJ?GXbTW_Q^f;*0bpJt z_I!1TK*K@8o(?t`F@qc{aVu11a#e9N3)l=oX^2iaDwNztQXdB#Biv3E)5)M5P-v?x zms~mtF?k;16qC(dr?9yh-H_rU!wU;q=z;GXW2l&(KWQ!v(OrYw0jfp6RWTDMQ_K>6 zGs%<SCL+^~Z#gg+;wxWKLD#23RXC>?aXG75<UB*hZ5xdwD@v@%J|LuI|1m4NLWSH3 zh*u9!vMPkMqZ*+{8gX`0<#vrhnU1k!-%#0oC%MCNjr<$&yRp(r0Y{GqR0(<6qsafU z%VA}mfoStq8g&kqL$O-UlGd`lu!vGO{UKn^mfK!au(r&TFX@v;yTL=z*6g=}*pwFR zL}au@G(VvnbA`e8fOPxwqI|IXYrNN6ZB$;(L$?p*O}v9vU1OpbnU7y}lS>n$QD7Ju z^y$ZO<-iXpXB-OJpBF)Tz5Z>X8&|1Uh8@&ywdkhUb#1riE=@w4Er=5q4d}feiz|Mh zN}A^8J$|#9OZ<dHVc0RTKnPecXd)2LE`4n(5TQszU=>M8Fc1ph!Hv_d7x~jJ_rC~% z{(pthMbW^}kigJ{0)Did<%NHB-F*1$5c=et*_ba^X|XyhzZTwX&LQduzL`j7<M=+F z62e{2*T=qCKzfp~d0ZS&OtE?{&b)LJva|Tv!65p+s}M6cc@4MJWF@36*X@FaucqL8 zv3<3wFHz8=p6O6`69%*VQOof5$5ozCU()tdym;xE)$Cb<jPpAf@py%3VqF(}qtA-n z7R&<OQEV{^Q-{ZKm9xZONc=`v{)x}EeV@yG^x4*?r&+XHD3A|IN@e9`KUXSuEw(4r zxLNJAI2u<okbZBqaSFqbqqXo}<Y#WT%52#=z5AUVOV4VpRujLPFCTThw6AhT$SwP) zP<{%gY2Jq8YWeC4gORgh1>oc<e1IreldS(#U4revZoA%~WgU~opr+$9TO`+IACuXm zS1TPdJol8*qY6FoX0DLMfsblK?qa`imEFRldZy3+5Q8&COX>G_u|nmk1fI8q-FlUU z8uY+Yuhugp<)7Q{aIeZu*S`o#dvB_6kGo#1(h*+ZxjGArzG(W)^O@0ha98lW7*MCj zX_kzN@Zl|SRZh;4+m`aYxPoxKSpLkDKg!|Pm#$SOrf9RMq)1k=9^tUmx+!j<Wvf|d zB`CEII<LkZQFkzCalNqT$LYG0Hy;Vzx*&JESnrl<TCCu=QJ0afP|R10ImSl!G<T6b zi(h@e(kApx({4~PUz$$d?q^N=PErVZzv_6l#85dYJ}rutcI6@KUMSz4L0J6u|CUQk z0Vl*o`xYwW%vc;B{b4>5d=;W-U+h+Gk}_Yy!LD;yeCH~+7PcM9vC1}hC+hM_rGV%C zJ`<Ezuk}rAk9hObOY}|Ni}SUb_6~k}CZ)YN{&;FQEuq(5E`JMn3*sW=eEeSD$o15G zUs0!Fv(@LV{=JkjbvBS%!mh(velLEMk$kaqjkVEb-;nWy%Mo|AS|_Jm+?X~zhtE>! zTCP;7)il|dv9(>b&c^+IHTgOm!K}7W(Y4mO7gnue{kn9Xj;B&8o*YNxvshvi1L3gb zuu9)R%y4qGp&?hj*=c5;m~ykIaIxB|(~z=MVa9fjmphxkz`JE~VbNT>_!SgqqEpQI zFUlrcw!u&#ZMSc+=I0qA^G;jumeGz`-@53+-zu{@Xt(0OFzw!?Xg4pFMz3h8Sm3_0 z!;^rvihyr#wALeUG7*-xM$O{jM{Q@9zW#KoUR-B%jkEeR-x7ClIZdA><>>x1L%`p@ zU;rFvuSr?w_JX)$oAK^+#hLkB_T?0{oJ^x;zgi^WDZRw0eXx1GO4VSs#F3`)G%UBl zCiMnTak4JW(5qOsStt=1xVFN6qIH+GQSUk8b>O&sQy#F^Gl;ZWqq(~ppF(XnZ;Y!| zuC=;2_q8uQM^_e<bl4uRHO_u*WZU#S*l3B9q<bxXE{YDHWI#@*mIB|woEg1L$ayp_ zZkLKU<L@vRCo3*>mx?RHnnS-*Uf>ZWbC;1}>w(G5Nm`@lr!V&r)ozi`9*=yC_Sk-8 z*S}%ce_`LgWdA<4aoeJpqCxF}wtqE8HSJFA<8_C!O<rcMmWgW&A6%@VmO4W&aQ?7n zAtmpw2a0$cOm+DvijItcj*wR~5FMm6Lz{3&o~>3H`7B{!rfy1{zD!c8v+;ELbr4Fg z!%_3%+g0L<yKH@Kz}xL3n)o^x-hP*U*Dj)LKCw70<vYfutE@jM`%a}Y>>@RxO7CfI zO5w_outv_>t&&(YtvB!WZb4S;t|09YpB}r$)9rSaQ`Be=mB(nEvPTw_IqWX)L_JT^ zU{OkzxKwYkLKY(-{=tw<E6yQ%L9j#J<TEI-RA7Vg&I%%>XvULaPEp*0qQpnq1nQz$ z_V^XN$i_)MMI~6?g}YM@`qdSXG%W^W48H;SQ=?WFZ4XUIMsD7p>MAYgO<Ha-(0Iro zq+mYI_BfGI?<wX@zL;)6Lm)17hDNVl7FWcUkezg$CRJqq>|(^%l$c<IhhW^4!0da4 zs#54?9TsYpE`<*ui_n{AVOi+-V^;)eDpoYoqVQFAX_IUaD>)TT1Uq@U++>sLGI=eg z9xrymA&0{$KQ(H~uf=n-p6)(%n9h_47oQT}?`3|QGuLTB@dN0x+-#IKJYa~p%RYCJ zl96G-4?kqkvxNibB=~(49lqLUv+NWEFBIF9yuJO736YOem`q~hEmez(Y|>t|T2*w7 zoRO8Qq)S+*l*+4+l$KS*F3;|8lU4>aj;mRx<(Z?yRH%BTQ<CwvE<H-hPe~#(NlCd; z2UOe_P8NB@r)3cZ;1tL>1xilJVw%=xZ)hk1PH5EhS%jKW$*)j5Y(@s7t=*(gUI!x+ z%G9GUVlszX{Em|CtyYa{MHjEr(^YBt`MXb|vJ>WcetDH+{|gQm#9f9Dm6`j)8OF>z zjDJPo>lFEBsaBdDVEj0s(7chof*o$yeaCXX`!JK771k*7Jb!ff+7*%{bhyZfG<th$ ziw?99@sP5T5L1(oMu61ELsh9zT5oM%NaXklb*J9VXn2H8vw#@bM-?G<^o=KOAKC4o z+wDBj3J*Nnx4VM^eojw*a9z$81*o$*Os&03OSX6@$%;En-|igwq{eNUeKq%r*ebI3 z#$-pS8p%z~y~8ZyTGT5WRb5s9_IWdg4-3TL`_@M~-K9hmX$jYx#Xg+6GW>c|6csN@ zD-2evID9<qhJFjOV-Y3fPvPP0`-E3H7ACmMF_Py=i`dx6(B=4+VWuU&toRd<+|HJX z$sdNoHF8$A3@^*a(ovRnT(z63MC4EO)<~!y^hR3hwv!K`g_xQn^}-FUnlieSN{3jN zz73YQDfj$3p4U4RXx7>O;w)V(mE!}$lOOS#8nnDU-+GLcEWo+o5Yrk@RPT~&LaX)H ziq4^sO9!Vw(cY;>IFaWtDs_tvp(M))80;URuxDxKGdH8ZyG69l2G5cWiZ`H`Xr6Lm z@Z=<?4G5nwZ#b#f&Ei9eJcLgNNKfwNKX{ya$37|%c!q}gO3gj+J%WUEiCQ`!uN!cI zik>PW^9U0;R6*fO?5P%*5m{72=_y3mk+_sn)yV{3Bj<ZjA3)716*!2UitXu^oSI|5 zN5E}Sh`5iU@S2_4Lqq3|&wfFPAS#mR{QGg~wiS+FD!sJmp$V=_a*mee5eHo_HNVC5 z6o0&(R>Uv)U;Wti5jTj{`~f?hB7B~lek)Pd%h@%zQ{Bkv77@&id>K27bo};y1U-pA zxR<llX!b24?#dp)RAHyl`0vU1@9bhd#O+W{!&Dpf)lBmW-+o39N{Ys%D#mdt`_3cH z(ljS&s2Hejo6x*_!PzeT^4vxSTt;Q8EDUtOY9DRn?;Ye2twx$<fH}>u1ZT}vJ`A@7 zu?&pTLlvWwG}hIb^m7UPq^yB~<NN^tYhHqpQKKZeNI>d<r%<U;R|vMaA8Jw+7Dp0` zRa3uZxpWF*;Sh|@G5t1q|Ffb~E!FQ5IAQHPK$Pf4-VgvkFM-z#i(VG1T6%2UsFA>^ z5erIQ>9c^-cL;Yg!Kfx;v$)<M!cwT;651O_+qS?+206HzNc&`J_1AA@Kddu>Xcxki z1N!&K{hd`;EDMCh$y{&%3Ekg6d}7$SGZ=snI#Xnw$rM1bEw;|II+Htzi1}TzEjn!- zj+J_+M;Tpg6?-(J;j=cyF^fBa&d4#~sp=5IA#<!`O1zP}DPlbsoqB+yVP5C&$0DU| zW%;0VN4zaAZ8uE%jIBiIPQ&C{pBssmeX7Q!{;+`A%$3OW!$)~P8VB^3-*B1KB5=iB zfxzf0F&V9|JV}#IdU-kBI}>jppDC3)j!D289po5I+oCa#iG0pk@wt*Tp~)qeGu78T zv~;fNCECLe$XTj_N-p~<wnP1UYBbBH#+sJHmqvHNq0J>8?LB*f-&bftZNtO%xnIYx zF$e69(Q`dvo@17n(wvPvsE=5sulF65km2yN%Cvsmm9iG|OUUCBtx@Al{nB&aYdAPR z;*J35#@G1s8Pm%W4Z{hY#oY*(zZ;&!CM{Z(xvM&YiNWgEyx*4yg~(lBu+Sd4TV}v7 ze{ElA$x&AwwoN~Mqy-EkfG>#)qxJF5OqVk=*T|ppO$}l#1!KQ?9wpWtfNn-XAf4u~ zi3HND;r8ep%f=*e#n0%5L!Ar%GLTYz5gU{KF+{lFi3IaTyBHC7mTeuul8!xUSoK@w zasVy-zT-0ld5H~^J7lBik>c~0J5E~4lw;Pqwp~uGE8mng#aTK-?8vb{e>7q3oHD-$ zjyC&qF}Yu|OthF`XAN)a+?f!{!m*-b0hK9%J%F8gGleybp*Sqr{MSp3BaohiI*K`m zTzuTJd6CwPZ_Tfl#WdDrBAz)f++3G6KYej>S=Z*+^4L1S+i!|r8_Wv1N-R8Oh>fx4 zDnUDWo=3N#$5Kz5*Q_ymw!X?OXZaHQO4RyA%TBQLOMh03#*$L{qo?eP){0UaQgyDX z!kp1cfgN8{4|0j_i%*4T7ajk$0^zmt*-sAgTMn|b2K2io$mU{~Cu3#FjeljpW$wM* z5>&4-pJ81R(?_IXID>soEYljJQp;}Y{Ip<-%}b@>IHPH{s+eJ28q-InVL78|?h=1$ zd+CyYYTbHSzTw67&SMI-$+A`?(bW<XKchfI#q|`Dr?C*>sz`+QVwMemT`bIPDWA}q zQZLN>bXFFBT`tURC7<8rRD!3m6yZxV>{|r$r_}ss{&FYdOCs#qn5U6*7V;GK5%e7t z)IXq?$dHK%3=0MhG!&H7KVBA-aL8p<vt$j-kc0_34Nexg1Z2VgtQXFZ;xG6lh&ijC zC&Pl-F=%=FBnTMcDa&fRnWtql6gmcJJ6NybI30Kj@I-u1pp^yr0x}Xx9gZew75Fmn zMF4!SxFIS4dlkeR#1_0c|H|#SCisl6EAgz<9T@9%p(B95SASQ<kPtxl3daS62ZjKg z3mO(c`itXl(A^;j7G!6OITqw+bLh@8+<_q$<N()hIr{(?)-bR^^<EJ>FAN@NEGVUa zVlQ<GX%CP*sD(dcKm*o;2GvK#nMDuqG$^LOLqHT&NANAnE&eUPBX}!}E~qb14JhaA zw?r^!fKIPnkspY!_$R_GtRrSC<R{E0#3m#G5MqG7uHXjlDd;2kJMdQkaPKceBql5@ z5IFEq;DG?-UP(hVCLAo#Xz*d+gE}8dqAVy`5DHKg|MFh8BsoZPA_3G<5Od&$HeY)N zchX=T_0xOUR)!v=UfBtgk|RvwZY=-MaYmtw;$9X*ZUD#|ZGc*@wIRH}{zT!*{b_Rb zf2nSCBi!+SbqBl=fZr=`hzY=51-%3Zu&<Uvy#Rj%%=m-$3hg4@ayw#iftZ4F|6=$H z`9Dh8g{_IoJ$@f*2=@ce0__fM`GDAK=@r{Wam0Q_cqM2B(LLgM$GHXFUkSkNjq8=$ z#c<5L7#9Q<1a<c}`0M^3g+#jb0KWu4>Ou;L>JxgbcLTXaFDRT^g9Zoz-B)zgR>EVf z`-M9i1hEo?YEzj}nT`7X;fg$V(!)23iSMN{Y@D6O$~VAAlm(&yNd&s=(4d6XGmV_B zl>;pSW%W=0g~0@y20saW4D#Urc4`3(K)Ms_H8zBP*QI&9m;io!dxtS32f%2;S0TUO z%nt74BU@xUGWHQJh9Z{16DoQI%{Fm@r2W2<1N!`RH3iiIlnbOYx8i0PHv8lTyS1wl z?M~SL)oalUyi4Q=<;n#4%zf;sc`AO(8Z#?F`Zx#WTY~&4TWEV|G9BZDRFErv%Neul zfYZ0b7T9A2=C?qFY4!)`Q@)UNG4yPBB-q{?j|(_Ul(5vF5lIj}OXyQ(m+ltb5#bf* z6@n|SPWKno&IkiE{>MM0&1?8q_OkCn?(*C_5Pc%ILT?JcbKIidvN}S(g15qMg8G8{ zf?k6n`iK2uEI082_3`iiC0L&M9OeTD1OXECmmt~^sz1M%X)x?R*4ayS7?aYzhBY`s zf*0n<*AERvNs<-p$if7k1|uu7?Gnt2G%cD2{x8pC4eA8QIpB}vj6^GtCKMeA2B?gG zG1kF9nC;Ud)qtrJcjIUVx!EAsfG(4E1Mi8KU)Uw^IKl$Z@2-?v*2!Hj_?16ylH1`g zd!__}SO3^;yT3@6@AR4fIbmfJd-e+N348U@+<pOc-r$RV{$JF&#!7B+K#qdn7a?%b zY-!kj3AjOuaQwU6@Sff9s<;rA@xpTUw^x8Ei{yVbiP|m=G8W16!Dj(?gO$B$1sedL z6xsj418J;$^9i###2sT6=?pE4bthj7<Zr^xFOQ`b$#=tfL*fys6r2rE=c#qw6uZ?( zXTvPWhgg*we-!;s>#tI&^HKKug>RdyYMYB_FZtE?57KAqEA6D6zeYBYBIrU%+)!Vk zwZF8^r>QQY>w69Imb{?+65mA*eZo5t3IAjD%?Ouv7cPgTq=H?w)mnmiJ=LuLK8Jr_ zFOp$dHUEHZOGaTw48XPJKiFogD8vy#52b;d!peA!5qkRF{0-$Qm;odUMgg1{Kuwit zeG#XG!L_yg^Xug&&uUV1gYIw&<aqUcXY_~j{_AAzT@V(CtN(|t(Y2Rk!w0<&MdyVW zb1UrAnb2w0XH@5<FPqV@?dp4J*=OwH*^HLD`Cs|>Ih;m4n~QI_dywS~PoNFS#t2K+ zISkd;!Q~*?5If0Bz>{KglqKgJK|z;jZUzX%x<uWVXf8575s#!x_^C4#x9!CqoH^QG zyNmcmOR|Y#O#)=^JxRAD;}Ki_KzvFbA(w<xv^mVbzt@)wS5rj+3H}YE+!hygWVUoc zL-h~M1Tze)>pwbW_bC6LL-i*N$n2%x{a=tnewtrITnfyA77-{HrOsEi2p7fmA7P#% z=Jm`1LqYzxR;ifaun?kw#6b%E=cE;kO^B`dn1Iv%%bh0WOqM9eSV_v2j>3tWE0e*- zBit%MnS(m}xApQF3ILEai(C%>d5e9S05fU94m?fZDlm26$^iCWZ9{wj!77j~7zc1> z0DUiIueu>F0KeW1h&Mp5*WQrvmKuP#is%i22ZR6;=06iK(2GojI|}R+z_&}~2-mAM z5lhMkoCPKiY%r@kLv&B>sD4I$Nn7A7YCw8{yUXXF&6ljF=!x@@a*8tNE~*h;gRCdf z^nVpF-WoHi&^yzMzl*uc;E4E&{tDlU$Vc?{i;)lYzs)>PkO>G23JwGURK)+Ei5qe; zfnY&LgNTEg_}6JGSR91!!jv}Ljs7=~fOUm#pF_}z1klqUWFgIgSOe$`F_>`EKqtYE zfgb|EdxZ^=0a%*C_x@(uJ?(hEW@-x#G|G3-d!-H00sq6pF})OqtN`#=uvHja&}L9x z|Lk4{LrwtXDy%omCFnC~E-<8jP%rVW^DdJk*eHaPzb+9X*Z;EXF6=JbE%`0_ExRMZ zD@H4rH>56b4e&|;;Xejd`1h_1x3rFg9#Fk^GlpGwiVl3=|0;J-T*xdUmVfsG84@u; zVZp)ucDcxh3i}Uz0@bkElJzU~S{lMJEzfDeg>BI}2{){9L9*pog3O5)NQov!lpx8G zXNa?9TSCmq7SI)FA!MD{7I2F?M;s$hQELBc{A-CkXAF7m3_Yh@U?w^dp^j8drY+W( zVF^E{UEnHu5y63EN4757m~II>M=7ck(Tr?I**`b!#^s5yab(%E6`M2hEDW3r@`2AE zhcA8z^%oQmd>9t<r=t{q<?5{60;%(${iglet7c;v-u+3~Z5cS*x$$#&?)lFyNQMur zZc(6(s36f3shB<T7Dc<vqi<{OM>in}BK}MMLD-Y))+>qR%kP1&x=03fr2m>_6$UWZ z==Tnz>}hxV#}ux594h3Gdi~PLLuHC#DM9Umoy?n?7Vk=~Nzp;J2W?h4V<^}X_0nyL zyST~O|Gw55A;h+z#&oC$FKPw9q?OJQ>3HY`-_==>GyUr|2alMSu1vTRVc&X9r4IJs zwpBNIkg~TIa81uS+jRxe-(5beSKw9ifz;OZ39{8jH@Y?L;Q{!VA+h)CLHft_;oA`s z1|CpV8H;SAGg}P8kM?{n%%YLDdzu81yKC*4+oiB;j)LwaI0`@|0Hl{d+=7?c3l~Iw zk(XIqHF;8_hOf=qSsf+O)K@Ea@Wq~a#IItIN~-z{ss*`|y`jw}-=WPX-=STtPeU>t zCHKr+uMA9R#_P~j$DTd%ewp@S;Q^U84KeDMFXd~umZT^RQ;#&<p@D%d-@ve+A$$^3 z4=Z<u)N8DwT&KdbGaZV~XkM|hTPTlcmTP6=E!V(~_)VXTuvAy~sM3MlJ}8CMIgQm+ zv)Pw57Dw{lHWqE)nA?9=>j?bX9IYeN7B=_olrH(gyNed*(j(yk?jbmTMN2Y3y7e{M z85wQUi`WalE3sP<?&3hw3BC#83*rl5z8-&#AKU{}EYpg~^y<WAJ>L2<k>t!6;j|uv zn-3ZHdnSlw{Q>C|J)i#t`B{AR|IM?3_$>H6raf=-UV3ZzT5Y^;`5xecF(AZRTg13u zLM)nix%#)ra^3EB9mB-=P7pwAQvWb~a=UXGlLGOH^yj+Ubnn`yDukuFyN|)_DP2d+ zSCb9Uwo4S_e;0Ux=#KepA;h`$B-3=(fB2fcR2=T9ZT47UV%ooCsVrp=RkXJ{hV>k7 zC$e<npmj6$4^3?h(Qn|J^Il_ptlg$={SKD{cYJgGz4ouc*IrLIn0dt9W?Z9eAovaG zA^5U?)?RxC*M|H|Z3W!AzA(Ms>^1e<bojM2G=?w^x5njs-Y19myTZgb(#tRpv1@EP z1@{Powknqd3VT5-24>=Ak_n(N&`*ajU47n~ac_@<`#c1uFoZj!qJs~@vIyDxh7vV1 z8TOHfITG|nqQ!@k8c9Vb2^%GB*Y_{D(r3U@41E1n7ErWEL%}q(e}fj)Br=)I=f<WH zJ~YV70E+q;KD(Pvi})o8Ejl90r;Jc4<cU_W>AFR!o;UjlQxc`*tj1)ucTb5)J6h`W z@>^~`wOO~CzmK@)i0UVhF`S|g;o)VbHavo+Y*<5l3;TJBtyjVvA2{=_KzNSi{f+tP zL_;!po9CL8={cM)BqAlsOIJZ4lN;?FCFKH|!=#_-=LgL;#b4+HX&S&`4h|L@{s~to zcD!ItywDUGU$svz{q8c7M~?PEJgr$aW>N(<=CtLFd+XuL*}bC#O{fG~(^yjUnBr;X zdH>M>6Vy%}Pw2zuIepK|3F=XlvUBuG^@!+B__JkCYglMlEhB_md<u&mVTl?b7n+*R zA?#x0=^fEVqrWRW*$=a`%-XR(2NH(<_n{R9i#ErhYP-lRVaVzrl=PP7FIdCvKEP=~ z-s9!f#3ptRC#_l?Ft&KWapG!YAK>mS3}wP*#rWq1=>>kQ$j6yFO~0-`@qYSJp&RRG z!eo5r1?v1o&1h{~U&@pD`y>RsgE*}|%kcW69`7T*a<^|7HdS3AKT=IQnZqFNPohMJ zO8@#+f%wsniE3Q|nubf#;YiU)p=7Eo)E@O!6uN1BpzIDpafBgVNZBvFKE7PO#uk44 zDgF|rPlM2u0iWny=CQjpOJ6yavT%~{@pgsq@fV54rK#R;Lp2yU$u}C~AHK8Ntxw&X zJ|evpAGR9Gu5QO4Lt%v<Utxv69>Y(qY%nX7Uvuh@jyM>!>3%@(czN#Hm(U8YCo1;{ z`fYFcmM?yU^n5aVzhT>Mg$>Lv30zL88cu>&pnd=X9GxdGC!2-Fdv|>0Lz1}@iuxj? zsgryPr$qpm)3p0TLX{ssJSDo^5GZKs6Z{w|2^j*}rLl@?!yT@BrRlJK4d~X?epZX( z<TG{X+<~DwIgSCD{0y{hksv%nE5<aI!_2ldeMiuw5otjuAF`M~<i8j8<I>h?ypz?B z*AKR8$;6nPD17y|sB^WYD=p5j=rl{`Lm151P>vqv55w@yy>4jH>Lfe%nZx`MBIl80 zN4B{?6_i+C=4o<a&=ir7$KmQvO_rnuGgfmG81x(&%04-;@@VvsZs+5yG2upCPOp}D z!zE}Z>0f4M;J)!5jjAO6c(*8T^5!<WuRjh;3Gm!jV|r70JMs<iA9c;2{z5|cNMh8A z&k#dO{#QfzK_Q9K2mA%i1LgbBr>bD4`p2p%kY-!vF?X4^b7qsW#jQJ7u;w3K)eru} zfjLf;CVU>u(v~shTvqIAPy9^GIsAbs60m+WfV5dobdGa(I5yb#=6(8^PsC_tpGmZ_ zuosl}(5TIWS?hQHbbopzT%>*Vyi=H#dpZREI1Skq@T~F7!6NgU58NE#b2fU>vXl>b zz92<B+E|Zclp!X3C>t+KiG%(mJpa@`E#&D0`2VQiA%?wMG(3OWn`M25DG(oFG;=u1 zGu<7&3;wZrV^v{T#SfWG`KgVPV{Em|Bt{ec_DJ;1DZA$F_tB7~Km9ggy<*19|1pC5 zbmurpp80m<`WdPJ&TM$EUAWyo>RqP<;<RDS{rrntqO#@V^!c+Cfi8Q~WBCc4)=&9f z_Bh(|Wl;TM(*4-O)o%GMQSn|2albl3cJr>)OzvT3UPr?vU)g;a3b3wNolq_0c|1AM z7;u#H#Of4&q-l6GNI&X?5c`hN@~vg99NkGjGKkUwZemX2nS`mDsLJ!4go!m$m-I15 zxAX7N6fs9{!EZ@JufU0a<g^T9r`TBzPLP|>E;gRfHku;Kc|@<~%<LeqKE0<e>m*NZ zA@PhVW1q@{AKIqy41;)L7}_Saw$a*C=biA}iEOCaSLY)~EhCCA<G1@H^ZZw}d;p(r zVn({IO7#NNT<v<aiY7Zv4c*xT+PQ2wU5l}lxzZEUeCc$&Dua;aBH6{fE2FQf$DHM& z$$rwai?`u>J4mZZz*zk%XR_jfx#9dfNbBi)tSZe<swHV@^1a1nfcbKN{wwN9zSqz4 zvkNPxh_f)$mQY;f)TWh?_5At<9>OCiVA~KW5XlCxM;u)QH?;7+Wkk2>Gj4r?Enjka zBiN4{ZEEgY*x6_4nWb$~Hi{D^eiLV6R^56RKBNFdE(=C357LwiC!G;Ee^?GzGB>~q z&%o-+<0O~{e9ncsNDaIA2ZPFvqih0CaiuK}@g!jWq)==aWRL|ju$>;n)nKwnCxzaK zFjddmynAxeyDfbPW`GvHmZfw37H7Z)zLvFf`xb)KsBlj%lru3f5^ZgNX@PRvu#$7t zd>yVY?Yj=2^z8&;w#O~-y9@R)cPmfd7O|Cc1NM4A0Q|-5qxWmom+fLkK*Sq}7l>dK zArP`o^-KWt3KH+$?US1&tZra;?-I%bYyu+AlzUexx*dPY82d3d>QpnuBIUgddzE=$ zL~a^-d6V8k&a_D(V^Srw$Ix`7RPGZFYvP_fuwn9j95?F$!oVxYAzFAEw;PDT$Oo3e zN{`LlIbtr1A(goj`jXcKekU`FmckdkFmtdbfRZ)s2kkisd)d`g){HY6`YZgE&23@O z0lVAo_1{quz8vt^dGjvMupST{usLJb0S&9QyIPl=x3+GCd|~`RGiHc=51NeARc}?C z9>5o%`lIiGn03CJ9pryCCn5Y(0Rg+kwn!e>xN!Q@q5c|x{M}?*EDv;C1pO&-{}n)d zRwF0OU6U=F2R<&S{>*S7C*b#Pj4huBY9`S3Bt>8gKx$WE)$~@KBShxq4=pAy<)%-7 z!qLJj^Ygh&yM{Jv^lf?;TaKAW%2@i;*vsTzqqlP(C6m-QTZ*T^E0_+D0JJZ}MJLi7 zWABV%HfDYgt1WY>nLdcXL`nikm6`adll(<@;6nWheUzBNnAMaOZ>~ipQctgX5Mw?2 zu8b}HD{q>SR@DQKKE_2Sfp@YUkbwzqpCCYP_rjL-6=DPAJKhJRd#pLoq0Vl1!<P4z zvKjP~bpyJ4z%9VfsH4w)jo_BQ8S|6&1ML%c1G0POIfxK2(~GzU?8xYgw3$f=^0Jp3 z^l?A1>-z~cyq0Sb1nH<98<3S^f`pi44{k%H!M&NL!N9pq7YCw&$389~?rF5&x*{!a zm|alydlM$!Ud)Chm6ALCgjzClum?!txU^5@6`NpRy}rcR7X~yfprBr}-|$Ra=i$$5 z>SBOv%7V@-?Z63~pWQ3&zzMto(`U6-epD!9Jn+MWY40a5i{UrF7DJD4%0z9#(Y=jn z+b3kbu5UJ7>JHJwk>J0fOw%^JZ)5l#F^UO?#IgI_QPZ~>)A~<@60M>2;cAxoYU`_m zeM}R4claIFPuhx3Q~co$WcyF97C(^zJYT}B2}1veSCLz#Z5gIhK+E6jiK;lRAy<R< z=s|@?k#EdS!B;jyAc29K1iAZw@Z91#pCQPI?f&Xm_0+a=#&KchpuUqgB-OUwu)v;w z#kDcH<)3A|x!%jH^p2Jn=6#0VLn@I2A$aC*sRjz+<CyuDZ@JjsX(>=y^k=OVnE2*y z$p#YP6`1*!ZrKJF;Aem&G+E&-jHr6<w01wteF$dh2=3QLPsxw{y`tK71&j&aKXFTz z48X9=s0yQj1Cy8)|FS?pD-Rno6W$RU1ACNdLJ?441cXHm?IIgPdlYHH5RlI<S^iFm zU4#x~6j|n~!{gJh`eo6yD$T3~R>i`qZ|4z%{jEK4(L1Ag(U)8WU*b|Ywqsh-w03CA zEL*}M_IFJV-6&QE^1&iEtvz2SL}w*~yvpvBaX5prs<cRFN1N)irO-PJ;0DtW;FiAL zVYR+YFyPe%o@?9~H7IavER>;3BAF-yZsmk6=g_u_I0yfGv=&3b&pgz*_f}{i49?H~ z6>xSK9$|qy&75V!HFK+^a+0=p5UPMBVD*~Bgs=yDvnn4&8T+mlh_J;Ti15)ADF5La z82j-QIQ;?tPh$Rw+&`K6C-nbB0WAAP2R!>_<6pla#(dUobcGUNQAT<p^Zp#+n$q21 z?E_AJiE+~FHMp|dwU2vSy3gm4+X}SJ*bVMS!4QZTF0&i7&+_{B72&h(mB3q57ddtV z%-T=`9DlU9cmplb;m;IZA7W@NERU|#&WO|w<sf_`AGI%7E()hrN2E@p-jLmT%|7LA z>b}Ll9^sYt4)!&x74}<u6V#8?7d#iUXU-S>#i}O~ve95j<d%F+hUKFE6#iTO5!jFM zJ+L<1cek=Y@HGPq=vxR5%m6c}mvhkYHqIF8wZw9~O2?S0pk;{ZR>7F-)q)wsTOu0l zheSN!CUS1q%NXuegBjy96AQANaX5%D9C|m*n4~o|3+$SnGU#KJdbit&rZqPU;+nBE z2yzsAH^7O)<L@L0VoF1=$f516A~z(T>@+xH>O(K-q4%w18xWr~16W*2RIkaQ@vUYX z7@s@?cwFj4FY2Mwt!NtvpHvN)LP}+?%AxhGY8wQfTn)HF>P0W>q35k^8*mmmXQ0KH z-0qqat4Hi6NDi$a@Jymk-@_3H^%iF}`TTmUq_a(1qZ`qDzcyucc6eiBvy=@ciGx<I zRI`ruXw$RgYl<!Pi*P^j8}+Mm8=|-7H6&p?^1M%`55+?fk3R%@bx;BaY+GaDJ(4wE z;&45gRtg>Mmb=m$EcR_%x%aB70EyLw+5f<5V1($rk*v-ldX+I|wSnxJR*OL7Q$6xv z^hZ1gC^wa|5A%>(wgmq}SU=^y@$4K9JNq*sT0V!oFmWdDir&gk?3rRFzH6*@Z2OSY z)Zw0QEwdAd`|j^UL(&e$Xq(C5r}5!ta&l3o<rl>lV*%fII~aq59)w`Xw<X99Znv@a zf|l=&>~26OO~@0{WVQY?d%e&HWW5k>%|dhN>a%OT;0MH7?icA=u216SOt;vx#m?|K zAotL5qkndj%va)@L}KmRy|`_5^8unKqsA|B+$k#j!!+y}v|i+ccr9P95ZfZrnb0Az zFxPNllww`+imW4*ehXn)7WW{*9%edm&FvRqCdJ%}qhO}#3nyQ1$%AR0+{qY`3eV~x zB5-O`==!lR`y{TSgtKg^^VJE{!ca4o{XwWn91vrpL?GrGqjf1Mt{G#)lO?Hr^j;m6 zj(r?xEnp-_V_s5Y4HZexPN9+Cp0ZKPqEf2l`7n^<LM{{j-@Z7mMwr|)nk>z~ZAKjx z<g>uSKVj7{_*1-gJW1|rrfwZ}JQ**F23}WIW!vaomn~l<xNod*B67DyT7Q4i`C+|# zOMDSCd=b-q1xug9?ArbC>nud1d5R9#ukjXqYxe#$|B)|r%dz^in1d)_&Im+U`b-&J zmiXK-yTQGoA3kX1h5Q=bh4oq71?Ii15AzK(o@h^@W1riyh0dWzqk*Gu=FzmrUgpuK z5BrVb8I;?+62!QM+nah7gEWf|V!z6v)@e+m&yHT39x0d>xpE~}whD^wup!2Q+op4R zl3t|%=fK`Mb?Y{;_((~5Z$Y{ni~JUa?6<NFY{XA|$*FV2)?fDSqw*C{{ynRDSzM;% z&;+~m1FZI}GHqWt$RGN${&>Z!Z009cftM!$j;N}*Pb4OdSM|Kc$nzsQVHDX%7Fzk` zX7yqq8K->CN_y}s062z&WteIa*G4-|1=3De?FV!qz3fuN{OPQpN6N=OwTYr~po}%7 zLO~6EQXo4YaGAWRA8oeLkkqC)rEX3;`}ep45AC_6AAGw%q{I;!ok3km^LhzpIWQ`I zyihJLcAi3U#1ggaU{~!N3~es8F7cV?$bI~>n{g5#k6TjRy*;{_XPM}rx;R1M3Sq}t z$=Ih;R;#e61y@7}Z`zTdY8GbIsNxes(@{2V?>a4D)B);9sWiPJ{>m9TgWGNBb%SPB z?Q>dC)kJtTp`9DgfKcW@>`esyvq}32K=u{Y3%<2Q$th$o!U@9WyM`toogh#<{2fPq z-!!shmV}Tm<(n_u&C2iN@EW;S8Cr||9iX~<qwpYL=gDny{8gO(qUD<19NrErQ0fcj zS8X5Iqtw!SvShc8c}}^G$yO0{Zt=()E&i09;C}h(yz3=K+G#xOruG%cLuzTeqBg77 z0Q4d|WdQ$U?sSdo3)IJn27e{Nnw4qp-!cj0P#M<9ugVy5mG2pJQzo!$plWcdhcnH; zq#)&vcChxWaNj-uh5oh$9O=yj?PCbW&sN}Ng|ekns71)HsOBV*`2JyIVhSp|_lpLv z2vX=6t9CM98&TaQ4PMD5Eq>A{0|$;1UfeQ4Fd-vmer_ILB9Wl<NioEE*Z@xD4T5u< zEyEB;jxB~lsc_7bt2vGrI2C8gYDIPh5R1KD(33_iK{fPhmKmcO`G5h}p>*rMYFSKH zJMgV+8x4a|JW^r!w#bEUn~Gf<s5S_OUNs@F+BdzBs0#R)G)2F}<Q4<Rq~=ru{}AMr z1Bc|})C2d*$rUQNS+}K#_+B#CqT#v?`FAV(v_;3$HHw8nh{%ZgKBl%2co$Kmb_8xZ zD_s?C?2BlJ8lw3{CtinQ9HHENA71=e?Lm=3U98lsOATp8o8+XHr&*qq3xiUbL{nf} zr%mlAYZ#*p92wOl{YN!ivs5*Yh6ac0qP+HZq}{LNP+aB_@l2)>P7agUm=?oWIe|XB zO#2}`lT1uiU!owfD7*3f*3ELHHK(xp;3#-p<CgVey0Nb>_e!Lv<qdTm+M#o+8zI)L zey=&8nQR3m?3*kfRb(BtHG5{};I-WNOYlhiU-Z%ML=;yTm|;w3%H)G5WhK>jRsXX0 z%1CFUaf#~7m8If?r0vT_$+e0_3D6EgBMN8XtO<h56(6|nb&2JFvDQdr1)D3n!DI-n zVa~#h%Zsquw{n3_wfZ3&(4ZftU|67=e<!_4mm{@AbCs$pX++j0(}c!0H{3dO4shS# zPlMOv4tJAxtQD?`;V9&_iXK3tr6!9Vyv6$dO02s%>u3&GMeg(1)(cp7udkf_U~q$v z+?n$AKYjsRD1Pa?fs(!^eL;b4jM(@0KR4(w)y=Hht*!z;qefp6ePFA*hHnLE`Aqu; z=;52Y8)-_c@}ujrWqwrzMjZhPqrb#?1gck&>|=I${Mc_q4sdw6G@}m@nUj<{QxTda z9=3TjWA=Pp00a}1jy5<g=D*i8Afwhga}iRUJ^WNNCm{3I`MM)M;1~b8E7b|%l=y1Z z_2Hbq5_DOe9#j~MG}XT$M#3*BuD}n4iY%H7fD|;}#v?sJ6M`%0ydivFTG~DGh~sv} zh7i%sa*h6O!a`)W4^9K3w%1}-GD!~_B#SX?9x{zF`*#*cLEU8WkZOz>18IpBT?5)e zoziCM5L}FzulVGm=CNXYaor^O5FI?ZMC2OIgs%MLoaV8`knX?d@{<b{t(Ak`)zYo8 znt5tcFMsM3D-~bB>m)0c?f=wiRx-5&Y8I%q;*zIWKVh~MXqKp1y@=K6R+er`8;7Wy zZCchT>maXdhos*eRx&^6c|*cByFaj}y%BU{v~N$WoEe^|d}CvbR))aq)7w5;=l4Ki zK)wPY(;?o=&8`!7W7`RytZnj)Ro8NF1gck{U3bRTeo<~e>vUbU#AEOq^I9oI;TH`- z)g?A+YF%3N?Zf;8q;4MBwQX!T@i&<oBR0Vyd6gF<<tod>txC{0tt!07A?3z@(?)?7 z{nYOhoe3)9k$gc$%;fvo;V-91=t$)GU-03=wDygx^*zV&K_<42Ip_Q9@FBvqPQ{%Y zzEJu`Kz@8p#+~hwxWDq-?-ke{Qx0hg<z~xa?sKJYx%J8aUAw}(Bun3dn5q|zP}#Qf z7FqxdS!IY*{}#t}xFeS<EaH+`C?n}EuzHPfW{auVx5*fm*~iTgXaAj3a#!E_YoUyx zo1E2CUYCqjc^@S`o|GXGo1d|pl+{xx%ao}QXWxsZFk>GkeU5gYCVj4QA18gz;4HDd z&auFG2*juwZS#cqC6Vxt+$EZNb2?<<pF`*$Ld$WrL036J1y|4o#pSrP5E@2%V~pY| zoiwmWdlM9@59mZHyRT^Oq~_~ia}q<0Y2nS+#suTqRB!yI3<*8wNNt)7iNrlh5*DW| z19`m-8T>@xIfXlD1KnTZ4f+VrSKw2gyr+jGU)^c*pY`TG_(!)l>=!3gsRqS6cq+c1 zkndN_YQZ~sJ47nqpA=sh=`g!3XPje6VL!4xawQ;dj2ZB*sz}5Aq1-fSKjJ-FCBm<u zQ|i)Q!6wWeWAX9;O>znDKBp$UE3gflLo7<o>SWv^)@7wjaGR{cSJ-6_)eS1jymg8{ zSQ}OiS>e_?hh&tRek7E<ebee~B0M9OD`V7U%`);KjayDQ6<6_@{|2TSFAiNOSGOoF zxcYoQj)bHL;j<E!R}ykoSBjnz8*e*@Xwh!Q<C{Yux8N^_%b#UOv&=^zHWsev!m|Qi z(%5&JQ`VQYaS>6uVS|>IZQ;~)oE;TyY-$RNhBtygs{Z6qZ&>JXb8O}R?ieC}NRh*A z>mJ9csEQxY$XoR$wQR4iKW#UpS6kLT(lTVh#DR{AejE~iM+rliD%NDTqjx@~oKH0w zYHR|zD6URMSR16NN^Vv+DQ;{U&WCTus4Sw#yXLAvq^4ESDoyH`cB$KTsq1s8n{%lf zbE(7D>+Q1_B3!-B%|qq;YAL|fpuzJJEJN9tCD1qSKn4szty+OyvVgc{GG8;BY}yRJ zmSZ0XunvF4hA{gcYkm`4_?v)xR0YKz-D!s5(axIqTx$vuTmVeKy{brK(f8}>@hi(l z#(!5C6VR^IMS4~7jnD*K0P5eZY{Hv|XKxO{4>Z#&s+$wu_Gt71)smw_San#wNo*+S zm2$hN>E9?mQX4NhE_ddYZ|e5v=kZsWDCkc}InaUmaWP0A2YlGnw8fg4-9V&%_{4W? zO@M>Ay$rN-jIzL`F}SiCOc3lV64O|78c<vL!R3zVM78_0va*F(Oi3;J=4slq8+au# zHfFT2t<VMg*J>qGxcaojtdjXgv>b@56?I)8iRoNp8up+DuAQD68~Z<CI1t8qqLFP0 z`sY!>d83&5+2sJ}lBwy`nrV$B=CBwv)%6eH8=;$}8c@$kjX7(?tG;BUaZu{A+DA^{ z9f5}puw=CK)h8*^&2rKgZ|Dhj60iN9V{SxrJ$m)oP_%e|Z87`H@W!5rsyvkIp{%+* z<m>C_D_oT7@8=`CY1XLG)Vt}|Sm!I86zX9m)vs(Cq~|@Y%BI|)#wua}Yj&nRd~4={ zq*Xa)4f>i{I1jeV8!75j<Q!h|+O@k8-vy2OTeb}mqhA*snMNwwwR?N~=zBZ$b{z0~ z7p1lwxO+Q2&1((w6;IhyDa2a1wSX9?i(P!TEDVa}Hj4(i(ekL18HC`cP^!?CZgf`R zIv=QuI1O9DeuxD$-f+O4>=2D)<s9Eotf7kooe~}7TFeR+26;7w4*CG<nswyFt`^z| zf$AmR{FD>=2gVtv0!?TOrH!u?b0<qR#tPa%<&5=PrLo6aox0DSZnCKZd?>5fswZ0( zbw7s+TU5&O2J0Scy!T1f+njRW`GSKK^YB-FVPaJ{hkB%e&pR>oaVrULyTI2hlk?Yn zePP2+y|Oh*SHjk8B=4Rm`?S0-`*YC%^)?yrTkYE&XG5aR$w;OJ<;qxG%cy)!CSlx^ z{I`y8r<t2m*XOGM<#UpV$b43ZFHYBSQD=0K$XEe|7C$8zBH*%YCTn%2oWdTx#=z9g zWf7RNMoWHDbEv)mfk$hkp%8()O{)y`B_t8oYhCgQ6$urFnQG!L`AAXgxq+HpQGZbU z57>BOWmpnmlCB}U*&?e<S2d3NfGIk*Fh_8SHW^<I{r<a57$~*I;N<c&>yR<Kl@s#W z^TC6*GL<hrHPL%H;%9tpBId0(Qc%K3g7(`6hF9xuhl_hGhD6%Pt!#zrn=ip3*h9r( z(BM}^9pe($w@y*Up@tob-`tpu|4V!B!TZqMsSVh{TTV9a2ae5$xxBr;n{0G45$fgT zX4*TQkQt}v!qt=cn<&2P+O)hq5^u;InA}7nVf6#mdn{u{wk|8B(1?K(Q`@f$U8pA& zR?4@8g^MXvEI<5EWU${=VvhL<2W@q(^lZ>2Pq0OS8lGb&!;wykg2A>I)Teb?Zub++ ziH1K0JX+g>SXCyIW+TGY1&!89suOgKN1hk2C_|ARspqtm5W@>Zk>bkH;>rnd6eSoJ z<Z*=uo2xf~**bVDzM=917<0a{5sxCZXf1W0guAAAWIRJHyB_!YMBJ1PdgC5W8Eufv zL28LiGI{Y4_PAKs$k>j3(Gfjx{r(@jv=>ZM<%$9)sp%s4LR(H@p)qPw)sA?Y3bAp? zCHtgEPfD{SYff#kB`NG`MNZXqkIV(d2NYA0`0UJisJ3ET`^w0*IlDSh3+4pzwXqeX z&t0~9GsX{1c@Eosc*ld+;a!S5Imuk3tTmVovhxIy>iPl(UM<UvkOD_I1B@v`f#J)> zJ*=vjns1WyzqQ5yR@Fs4Ub9eZP8G2)>(WWptK*912h~M)`NbE6O>~9y%JnHuEe)9Z z;{`lvSn9(Gz)M-B{G@A`*nL&>LDHyLq(#<fly@r`*hEqyuJeG=R}fz0G4b?=iLHXt zMQGXuR<a1}&X0%vqATf0V^MDQWK8?sA-0xK4X0UTIi6cwmgEHOwFd)OO1$)F&Dz`y z<@ygN3u*PFs8nEX7<SV5R8%mv@hWq%CMVSAoD{&sH#k0TqZ#J+kRrcO!46c$C|hW( z!l5`_x}pG>lR>ym3T}|jYVcqE#WA^B-zwupGcwL|F8P!g8se)-QDb`^xC&Zz9BE-J zDS8i199QPoOFS&w7E~eR?fx_!q~ZFqMd;g?V>A4R<$P?#6b1G+E&ZdUBi&I<fVz3` z>0wA#S*~}Xwz9zLj_GGsZUBUWO3>^lL=U51sQK3W-2TG&&Yz-l^rPQeb^4<ddgAA! zgmOVyWd`3PJm$0#FvMh8(()Tpn$lbmn`s+V4OLFcuz&E^V;fP_(0Jn$dwP#h$z8;b zQ+i4w5(=Ob)Dti|y$j%R!<(!0IAU<3B-<ij_(|o*Bj3jA>YY%~$`e@X-*chPkCq$) zAIB8`-U-~Jpp89MYyYiH=qI?965G5_VttTSB!e><dmkVG=`?vp{}zIHd$X|G{$Rcd z|BgTbjOqVN@JMv83@0ak{6hr`k{SK|6?ZZ?rMNwGuPVe)h0G&5QRXFI5YyHhB>r`d z_VkbvQ_R9kY0sd=@woY;DZeS3-1C1Z`^uoWnyp>jU4sU9cY?b+!3l%A6ExW1?(XjH z5@1MhcXxsZoj~x*aeL2q>Q>F{s;>FfYp;G}O?NNV)Ut@SRlavZ^nKUUU-k}6V>egD z)MO81xL4bEaFDNR4|FmCXv_>_6cINKpM;Oor(K)-o$uj7i%wxzg~;h59<`On;p);Z z{F%;;I$$IA_={T`giGceF}#^Yc<&<43`T`>lN3T91jC9LfkEG^DiMdhL9HR*br;=J zM@M3YqcTd&{L|6pM;ZOe<D84kNoEviK7_ZcG0pg;9^@RQ&Cxc<s`6WOAWC69<%*ZU zNFCm|Mlc|V0b`!wq-c)z^GIxR@PRGkQ4bR%^3%G{K`@m^FY)PQ)(Sgl3EQ)<K$mR3 z9-3mP5m~3A+j1sN#9J+~5z)$pp=OT<Gqv|(x;Sx+Ei}apQYPnoc%Q?y#NygGI_^10 z9M^73Yjm)$6$@UnG5C(kAC>dS9<;84p8RamJH3@;`4;R2^wn$t4p0XeoceMaW$E;p zAry`;Opbmj6n-F?_kwPbe13>L&H7@vm@Y)t_(3iOx*~ZN*1DWn+&E`2A@MG+sui)? z6}8%VXdKU^$1_t^GQBW&38c@YOGS_Aq#NSiZa2S<9>7P!vqg`xaSnlsZ$ECxc!N`( zJ4iQ%z(<5a56=mthK@Zdab5M9h@HO&;L&3UizLJ26<{jVd{C3Y$1<8Q3}ARDx*ah! zG7QNGKxb4x<-y<+u*2%Nc#$iI4at1An*&kR0FKGIw=G`+9tWB56x}^3!KqMQgEYs5 z$?qQoLIvY-V7IBG5%kRVi)rIKrV?PLiJspy%nw0JmlqplHIeMXuV(j>=+;7@x_8}b zz@g;VkV!ytns>Fqk2G4@OvbZYURwVTu^F)$KGX1?WTK{!7U00;iokFwCVCa=U*h7T zz}IDSa_*68Y^d#TN;PCCw8OmFfH^}|b~{6x$5h<cCW$my`R2l46^a;R?SKUBFTcNq z4gb2$*be*9<p%Xd;_6<9(}fAw8M6uZ+~<Jif#Tep0s-wvJB-DmF@`Q@7Ue$W2yO*g zxn~0kcR{6uN%be5Nr*aLrF)dqdNGswPrSWwIo;}2X0-$g^9Y-^<k7TB_p$OX88&U% zqiI#{jGDzsjp8l(6%-rEDU}-xxW{RY@-6x`qlSz0RUf;!E7!Sm8l_wGt45#mH5Mz~ zPc=Klrb?=dstlV?8x{ymO}^<A`n&6^P{yUq3U}Gt6lpS2!!`{oJG~tqF<*qzUP|q7 zZYQ~Qw{1$_>aN<sKbv)}sf!v~7SVMFzCZiXlQbJ?l5OH>PTLkqc*IgbP2VO3<4k-% z!GRTJ*5(WGEG0i5;kCWpj#$blE}k%+yg0uo?U#2Ap7w%d=|Oi%<_)uvY7o|?w{OHY z8(mxRqQ5YsJxcay!p=^<!?JT7OkW7=bQ%RjZbIU4PK|<Cq*vVE*>XkeraRqB`k%q! z3gFd|zqSYLhqddJWx=bt1O4y62PFRvhM*-Yu~)_Cav<8ITbH*!#a;?<i5$uSjL6_T zCy`(}N9kVsEtIz_M~o@Q`tbCKaEghfZrnagU+B~SY)o1c+LUtXO8%X{%hlJag43)N zQKm@iRXnP!eA6>cw=y=Ne4d@hl(<eb!g4S7x-Ts2{)@UPso?iw!`?d~2L?_-;$fO3 zeZcZZB=r&5kIb^vWWN&QD~GL@%}=j~ijs6BUnhyz8S9%AP55(kv4WswO>)wm(Q~{o z3=G`e9KZrv_t-y*7~L$KNeZRg8$LP_S*xyMu8h2mI?Jb;Oc*sxIch)UPD1Izkou%c zD2zBqX*OsxBjZ95!Rl>pei&&tXAq9lh^`!Z|6>Iw?s!mqdx7#jPrku#Ae<O$B2v;G z9^<;>PJ&VXcdVA+ZdbpYvvRfO>vwp_zNCw*Qz_4eQO>pjw$2WyZp=Fka2=^A&6V^e zkp@`BKuLn1IbU<W=bl!FyI}>pxqn$5rv9~`abMrQ5NU^A`x7?F;KtL1B+-pRl-Bt* zGIRSuvriV4Ju4-6Hc`iK;<~x2JSRN!h~;=w*E^+oU(V|)BqdbLy>U<Lcd`YMxrD8> z{B|O(G~RWxT5OITL;Z44wvfE{Qpjw>2A%$F3#R}-Y29;vWAiRzVyfWGq|uF>1J=d& zOPm2Fd4X1Ij>U0qn?~EzT$*~-A0nC8meXIGMhDePs3;{uws}_E>(>@zE9ZtS8iEY* zz;LLunx7th?A^--hE--viqxg!?GHjcK3RrY1Tf?fbC!@&&`k>zS8x^EEi1^o`0@ai z_BHOcW~M%ewu8e0zpHW}4u*ngJjsaAiZILw-SfHoON<)dAM5Gq6dkc%=&%Z`YVGmc z=b@>M@s0fI3{&5%waBbZT>Eh`+`i0h@Khipc)rolK|_QVOFGkTLdjT#{t0e{U5I)8 zWUtgId+lmV%wEG27_Fb*Xj03Nnkai$b++#@K17nM+G!|h4kGEs<{pb7_}N%p&cB(Z z=zi_f426hOz-8xLOT;}j5t)h31c3(?6)@0;1*otkw4`ktQ3xRE$;o+Q*+k1qInOH} zuXc_Y<7kQwqva<0Aj}gsbF6LXqW?Tll|AyQWX{A=BrGM;L<e)e0u)knGA;Dfz9cRC zS#6B{HdR-RalJZwpZ#4Q1Mewc$7uP2ziRxO{wDW*(N2<FU}dTVQ>xis>%SeY5+Zk) zcpu>tau}Dp9&wL?h&>V*Vfk&><WxOJJEbNvdMV_ogzhp;c6m^r4tq&BFCk|=*MTi( z=dFF%8qwN%pV#zhe?K6RqU4p=OhE3Sqc%oBA##7YmlT^9)d@!$jtQ}#spu(UznCNf z%N9FtUseWY;i}alL@#Qq?e;Ng5j;Y1P9SGNHJ~?Z(eE`coXCS)=5aX$<ys7D=xC6x z>lOLktR7HAVKlHD$F?*amQF*Azev3H(3sO(N92-7H21Y{DNOS))%ikINlYPWGDHl2 zV7qC|x2}k%z2UQ$DyB_L;hx}IN9!W`_K|K_FTq(|-CW@WZ*mcm_Vp^LB;ir(yeFTM zw)>AHEhAh;->UPG+Q}@_x(bVUvZ|S=l5|?@J{JEf?@$X?n)8N_gTGDzyIYyLa)M~5 z23kLl87`^m%hF5^<cHpW{bJ(nH2{X{%@%9L8W`3>3LSxt!RI<|Z*aKg<Mo4NeW&{5 zxx^<XKXA64nxM+|>66YR6Q`MyZQHkB%vlup5u7xwwFr7qV{U$9%Zhkh_{aGf$#R67 z#}AqrQk0}-x?<1OU+nY3^((LYouno8#3pva((cun%30LMeCbKS9O{f;=rO^9>Wseh z1URAd3`V19{hYzV`{9~{0t_<2uX{TZ3s<0Taqk7ky)G};gTmhK4RiPZjnQG|D3GWF zYs$Lb)fHKH|I^slT{}8)kBa2h8_K{ZfP1Dy1dB6*xa*>R=*rO*Io60C+0V%Jv~IRx zL31lh;c(j5gLT7W;&jiG<&*p!$>>uu2W=M<U-)^-qk*Fwp23hS>bM7V_f);??;rVY zbnLu=wap_M>#|kv&g;c4bW?ewf`8s277?}F%0G=S^)s@wie*K89@ktFPi}R3@}(7f z5zZ>4TQ%R~R?0jIyNiteuw6)ZlCRH|ZuY)^?7_87ly4M0>l(V}nv!nHuUarn9kkE> zb`N5?pyy~&ANvyT2S=~hYc~oiJZ9;I*trIb&p@<TNyi%C-Z5_Sp;S&W4L#!Uz{1=k zVyZt(iy$m-dOYqgSZ-f8znt4&znB~<>Jo~=EN?fy#2)s$IGv766eyUmIE0tq@J=?P zMNlpo;>cL9b;1fiekSr*&!X6}Bq~$XhPx0Rp%P$lQww=b1<P}9qSZF6OW@t0lXeZQ zrxmjc?tv)GuULoQ-DG91rDJA&J9bg8O}pC*m#vB$2{B5#!OO5BHQG-aqI|_w9rt0E z@Ev8N7JF^%ez`MZ98F@nHtF8oO8sH9tIB)5b<bhvTDUEYuer5Wxx1w$Z1ZvC6W;Yx zk=84{6|VIFDZ@PJ71@o+2Z=iiqt@_^F=WHNl&QeqT=@RckMkcFs#sF|J?>5{Sr*r{ z*M7%5PVRapX~;&!72|GQUy>`!x*~4avfxt9;FmnY`r_R0ukDSA-%Yn}3HZMfS^WC? zEw5;3xCwQcxIy9!(``v(`GsjZ6E199Z*JnJKO8Zz(W0N+vzmzyChnKV!{UUNa-OIq zjeK4MiNr3S6GfsW>793$fj?hGI2`{$_V?!e<q+`i=&2o8uLMJU>CyYEju00p{C*3i zL|g~A>_bbb7fa)Vi0)y3{3>zM>xj*GIzH5L$RW9a_{o5AY_SO@M%HVQ0m8z9t=GmK zcjcz>xE$GdK97mD7u!aNx!B-+^LvY^cUpq9uNi^626e^6u53Sc)G3elw)BYgm}It2 zB5h{EdradjKPo>FA#(8cI0MI{9DxG<z+w&TTfHpz+oqKn{vOBGd&L-w_`Q@|<PIhQ z-V!03r3N(txWcb3u|Fi?29m<~Nn!9GUfJ(RFP*9GaxUD`&ojgbsRm&o>oUi17jWqH z6My)4sLZYs)&O+l%`Uug*`VM%J6e5vx(=yJnl4tL#DEzvpltr|w+E*zkPPtv5vETO z_%`YzzVdsm%oT&qZkfD0zY)H;$+&RSK6=ppsIC3V=$68XGq_jzjHbf45LQl7u4!1N zZodB$GoOl}pgTWc*Ua<UitKvIjLx+eXeSR3w&s80KRK^cm#wCePHrsVA7cx7a?Spc z3#T?+$JUa0mVdMTLF+;qDkH37wL~z|3pHJ6YO6@rQbJLz16xETBuTnJ@Hs@4A6R~= zS=buHi*B8VsOUkHlGH=_B#e4(PS){J*){&UEN?_tcTgoB_r1oUhlx|LM^xdIRy6Z` z+YPjC>VBTakWZ+jePkTH<q^?$IClRoEF=w)PRlNe;?3g|ljom#ZR#qeoQ-uU>y+$w zYlemiPDn)CrbXrp%C46*OSQ6%pv{z;OjJh!{Q{w4<QYG|lPi@+QX+h_x~WBk(=O9Z z*=Kj@FIEBDQ$~BmJ+BGeOs{XP+dhs8cQdHkOZ9`Hwj~h#S>zL+ra+huTrfHBznl?Y zH$3tp^houdqhvk6bD=$lT4TxXO*>}NB)kn$trPz=$fDsdbzaUGH1-IGX6))b3#?^1 zMc3k4L%05Y<zXNeZ|bGMs_(4_+f<_TAc}elcRm*SoNf&Db=&+PiR&j@NFRqOTM7rE z*VjGp3iN|vx4}IG6Y819dHoE|XtwMD{W|mhfKj-++zSRb*(i0KczH4INvI;Tj3rgD zv}sUu8Pg5<IqB1@tU7f^ytairUl?9IiZAgZ?9Hx<eY*(x*t1t2{+B72vH;|34@P0? zvz3}~yHqGWZ(b6hSCd|6Cz7Op4JYlc?oM$*mms@ZA}fMHs;jJVfBYhO$Tfzmow;U# ze8w;SQx9e3!=OScbE^W48})AC{QiBHDS%B<)KCEMYNTSTc=EITyI%*s0kVD(1YhU3 z&NL427|yvYt`yE$G^}6q*#aGy(87aQ$zwVXe_CJ6K`2?p=qme8a&w6vYxo6)H$w8J zRAMY!rY{Q9o)H=dr%feTU!pua$K`J3N&0x7W_On@<(fZnp83n8TXg;afqCfoDT$8m zpA_Vh_Z?wmaJc9yj2c$9Ri_z3pBQs;+dT0IaU8#j)4Y<Jn|<Bg7(;zbPVhP2?FI4? z%p#xQe*ckFZq1q@9Fs{n3|<o2z1QW+ts3`a&>igUd$Dvw&BF$}&XBLY1{z|Z5cEoz z>odVn4(bGY8$?|?@(ykrvC|;U|N5XSYAv3Cj%Vh>Q^n&MyKhF7*r9yM`?iI?eYR|W zI^3S^wL9JCQ}d2|c@>Wz?kTSnUHlWW;3iiC#dvNd#_AR9)c5!@O<Y-)$#1#VeD5Xv z{0WqXZ8*toQWFp#zno0(BVNfT7NSXrU;i*zu}DS)ePPF+wM6>Dq`osn&p>Z;8`>|D zKRIhe-ZXDC6C9H;cUefF8_6q~(f7gX**07CEF<B;Bm*<%FdNJz4}Ty2d+7t^%je1W zH6HT9{9zUS<8>;b$S>{K-*HseUm{QLti_e#tQWm*uGB~M$y62nxb!jPV#KLRHK;5Q z#F`#GVlOFAcDMzi4_&ZfvA}6aBD>FDDXG=e!lot5%i7;=1~`Q0(lCQl&J~L(6(*d` zPyc=~$B4QqB3|>rZF2_u%o&nTQ?I=!o82#~lzccZxw0+!EqgFAsG!>mD7xS@ZKT9$ zhvxQ~yjUDZ;4@8(AwG3;V*mt$W$7j|q|&vW+bk6t(&a~{zG%noTI%|jvvn9qZE5h5 zvb?&ezeG%cdUkh7QU(sCC0l>ya^}Q%%@_EwOau5+{q3uQ&qA0o9EW6>;j4AHRM!H- zN#|b-E^vG{#|~7L9|Dd@kat2uoO;DylmJcG1WI$(FQ!b{%Ov5U*o+hnPQwSt{a0N$ zQqRH_kW}Mm9cT@VTBT|N2;`w)naTQ>cQp+x$s}Kge>C}1#2M@rr{N;W1%E=baC${l z3HmCM8_KM<0U-0s<F^`})s*kW%t8CTW&MGSE95Cfe9}_c6m#X7@}v4iQKC(;SgG`Y zkLW#MA9BD)m4M?qf7f0eE>10)rqcaIF4_Dsz-pr&4mLuwBD^8ft??ty;wxKK7Lca$ z%Znr7QDKD+?8ReS`Ji5)@@>`NhwN^w5gY7DLN)eXWdeQr0RK`5E0=tK=Hqv-Se&(# zdlx2yN@h%0RBElSB#6UkUbje4_+m@m7HhCHVOjm<e2YVh<mo)$70UptboFFBJI%Ry z4&RSy@Kmr4=o4T}Ii;MU$;3;8CbZYZ@dkj=2KQ2mnD&V9&uh~-h5jegAIRkES0?RR zW$dpl707*~8M6iS6C*7YY7VB?w8*i*j=0U3UXX|`9?0eTN8}{7n4K}fu}wO5v{6`r z*B=P@MU;>pPGMD&MESWs#jE9LBR4F!9B3<aEF{*H>rwdTDTwMK`FsiwFI171w`U5) zE?JMb<W*y6mXM3;Tq`=Cu}#2=bO}cfovG4hb$@%jKaETu34#Na%t<26KSwO?&vZd_ zHmOLt(IPaCZVF$SJoaLZAs`Dd7$QDsBdZ!wl+#>0L}8&~Ct5L7VZ(BZwTb}d@Dj6J zF;4Nan}OmPFK6Jg={$M=*zCd<EYy*`Eh&Qdq?cSkREyMR$4-F<Qb>mL1E2QrcxkFt z<u^LmEZEUvtMMOfpL_3`o5Ct6{Uy)^sLwRK*D8$j2*HkL#)k-x#Oz%ySVHjNa5c5K zX`I0Imc)Ln<uS~Z*j-mi!-Pw2px0iK(~yFde4l69)IDM*0vq4RHV~9EYJmBUr3JJo z_!x{cC<itVOODLyS@@FC)foMOVLmD3qR>W9g7QZgu5X74u~xSX&II>$_^wbT%J~Ua zbW!A8df_wtUiz+3t<~0o@a&7`Bj-<_Oqs`?+lE_cX6;AUpJA&;RwA?4&F^hRX8bCj zOK!DSX;;fujcoI*Mc#Jo#ms-vE4a1&DPrUIQ^Y#-mUnf_#=q>@^5;d%YvHr@&tB`- zv|IGm!&N%_=3AnlZL9uEFKUmp_QAK@Khax6W_xOVB_2y}hgRE~MP_;mQD4CVgU*$- zWe6OkgNlMVnf6E1D%3?%`3y-m3hm0~xPvO5-{kOh@}M-@gI>ZhZXNfOLKfqtYS5Co z!p5Z4Ca?;2lL(|GZZ)znF^6|Gh${Bx8PW%H-RB1>RnLpCkz`szXPRY9aTr>2m=ZtE zK{9|HGjt@U{N)jL8i1`rE?Jy>&8mS?1pWpxxmd)N66CD=xZ0T#$;GclE1ii4J1{r> zP(*^;x$zE2VUtdzLbxjxDlDc+48FJH?269mETXurmby067!~x!w@z^1=9R<h)WzIK zZyz=zifC@>GOwU|Wy^DDHIFk?L-Zn3gI5#A1alY^%D$C75GN~#NyH>64_Lk$$(4~D z7fUB5>PBm2sYs(F#1d4WOqQlC(`U9@#=)KNvn<PJ>e3fv>C(v3Np2l9-PCxjCda-W zY!Sf^XVFh9Bz?jqE%!z6Obuu)`WhXhf9+Ip|DM^y4rmQhqoyWMLgYfu8j%Q3sR!;M zrNQ?Gbp`7wRfARplruG}MivBx_iqA4zZx+r)-f%JUpr;$S$&f1X~uyWV#PWx{Ms%X zVfdW}7oS+e1TuUCni+B$ceTxS&s&(LIHB<!m=`&d_}wZ{5m`X;XBffA{%JVY#Eb=l zX;X+*mr0i{F0~nfKXe%X*)Kgg?is|{L}j|oBre>;;$2h*z4w&mo)VF%*1<TAIx`&6 z^}8{2dPbCmo+=5i8j)=oPrQtqc$uAZg`WD>HY!!SLQ3alxEBO2B1N84iI2JiKFTv# z(XNqq-G$VIhqxE8QVJX_bemC;sdfgM-Dx<?1MS70tc;m!?bV)ZvJONaMO<NRVA;8k zKhQ(o1S<~IDkgZUb%yQVK0AuNeo;FO`sOM&_T9L)+<co&<w=r<R5i5k5~>X@j6w~& zgB~(Ww=i{BN$x3r9G(7YdvK?wuiu;b%Pz-<iTcgU`t6|plM~EEMpCVa;r>i|y>H&e zh^GmnSN@|Xv+=JUg9MPPQ@XQ4YZVdRG2Cj7;+6s>?+&f@ucK9+)O9lk2^r#oDOM&d zINWx}J);YqwJufgkTR*7MLB0Hd{?PvxI2`le@!BvGc{IX4@cfJ?}o99JLHgoB&Jb7 ze5heJKucKWeN8JF<dxoU)&}{djs9hG?NsTb8@SoUKT21k(HLxQ3KAF*w~7P94=^n> zIdz&2G5~uL;p8Wq4m%Rz$dV`Pllo*xx@cFA;x>7Z&n)4su=DdOzhlk0D33(KcpLcv zB;Yn=ue%UF%EZO(;tOP7S1uUZGnZ8ueUn{r$%)#0kYGQdUb73@kMtaa@rIDnr0$_k zXD;J?2TL7nq=);9az0eN;AE#wH#I6=OuT~ZXq;;~;mLS(-mDGQ;HO{8Rj*`{yKu8^ z81Pwe=B?*FDyPAfXk9K5w$|OWxgTne<5W^g<>D(JeF;r|ZDIe8pWB)<?4|X*<+JCy zQu%f9S^dM)WA;2Q?q$UboF&qPy#;4<MuPI_&8<KQS#*rDL)sn2=Wpa$G19^9Mo2;T z;+MiHABD$u4*0hv$HjAllp~2nc7F)8m&?90*hiz9Q(W+SFl#=78jeJ^aS%Bw<iMqP zH6j1X>YB5AJ(9V0^PN0CZw~4{ViO2-p_L2G6wohi)2CBxnxdxwS5kark{xA>ax}{I z^o_-tnF<%~>XAY6r?~1&*f2OjHl9!kE+4VgAljN0@M;j<d*53^;Ut!S!LKDDrDWmT zo{VzRAwgBvIb0}!DfS9YzR$mJ_PMqT-RDpkqkL+sm+0bo*aJ39v0&e<+2eD9K!5r< z(&w@pCS<tlGz9QhWM3`K>G9lyZgvWK6$MyWSh8Vng2a2<Pe~JEh^4H-vi_Izb98Vb z)A-QmCu}h>)%jfkZ4er0%iIY-9_pM>gJ*4++dv2duhWAD3}eLm0AgcND5P25(h^rn zdCZmH^OqhWtOA-I918Lk$a3uyj+UCryYH6^C!o|7Om5GLv&{<b6N2=-2?(3qVdW{I zGIt6I$uW>8ERfl>d0m-Zb&20P>T%Yf;i_`}98EtjeLJVvH%=J0=?^xu;nI>&q2Q!W zj1aV`-eoL{zGA|cTyFt-BmMxSGR@wQeot}~E`mma`>nsQ4*A6$ekwv2C&_gtOP7Ay zQ#w^QubJ+++C6e1QgaWaog!+=mgF_n&~hb}!XWikTK17z$gs;<+tj`Uqgjj)FVRGa zx+%5vz+{s-_g!ALHvRXi5>^@8<5sJ2OOHeHUCoJIO(5S@S-PgvOY$vy-s~m5U@?=4 z9-2^cw=i9@DJm~drx+yz9gNlZHQ|1DpX7eZO?YF}HA6djqV|N_HX$g%LffJV5sBlY zf5-?pP2AsMv__M3Vo~<OO1g(Elgpd>F0x!`F|+vhRX79KDgON|BJ#&32iJOGzY~w4 zqQ0WI$brM)-Ba+)lXF9&wCgfA3nv(6jC;Lyh#N73#L&=N;Ir3+^F5Al?O53fGAtLR zxqM^!4;U`;fJ_bS<5P$aDhSaGie?;%Um{>V_x;35f~D9p20=Nc<O718MaK%L_v#5O zyjA>3+$)D@$$NxGST+hqNFUQ2G}C%CKN<Ch)m%K(*4DFHn_;&o4619Ncw*L_MN~WJ zO(H2Y0SJ<~9HC=|579?S%oqvHtoJGBbVU$0wiw>}X`p<@+nx@GQ>8nBhV9e=P%390 z6_FPsAtl7j7uP2c64I^yq*4%Ol8E_^M)>o4is^mIcp}jEu5s{F+Epf(j#KDDT|qbf znQl3*726%rL3q1`Ep<yjL7}%3&S^e?<g4KbcqQb?IS9wtwDz#8vzbNHkr76>g|SYq zf^81_`n|h%q}0_-YW-pgD6i)p3^wH0qCN~^^C2A941SVn_b0UCd}cj57Qylbh`$sm zkIv~#F!uV;dMCT$TrO|)RpG=Def})zh(L<zE<+dBE|RA>BjXcx_<<lUUHY0fi{Der zIOA=H#Gx@s6=DVZZ6JHB_o;Rx2WODBw?PJbijwB?hL8~d@m5gfcl{giXcE+XaOrZp zZ6PMn?N2>CTpub6f{2w6>uVTP+ZoD<8YjDb8N3D8Vc6SG97Ub-@ddDcHC=2Pi%#b6 z>VoyWshiW4z(5700S3EI9eVi{dCyGRcm`)`iHr4(Y?O;jJV!Zi-2xs?JX8D!EgqLh zX$7f_N{Fx;Z^13n0F(?8`)>4j{J2q_g#0f(ES{^>C(l&OG#4~oQ;RR+UG$Dl3F0mJ z_raIzly&Q#0bhT5NgK3U6#nong7K9H`xb#cx0ueZdUVGc&&R-}&1O74uRJn&qphS~ z9kKK_BH`vd^!hUt!*RxuY^eCdFZ`+|D`N+d)08hDAQ=+F@g{<^&>UTlU5+m3^CLh^ z2az3C=*SVf+&C%7Z95XRq0NlRl~Qq`(Ol;t^CjKpOD>2@05~suv$A^}nXU$42~=Yi zE@ew=62|#(+7&j1%vN`|viB+G$j*%V&UEy<!nC1~EmeZG$WpNCCOaWjCygZXtg}P$ zERn#{a!{%V)O|8<(8seZ7+t^|5Aa1jGhuWA{~^2^MS6-k%qlr?djbgKPZ^I&o3qb- zQJnOPIXSYlR*CaW)?D61La0M-#$p0bi}_0wj3DN0XhJN%ve{nZ(=!L6f4A1KoMkKa zusg%Ek_G!Z!*bHuC^8+K#1V9t+Pp}P;>H|7$lD#~{lVFVHYIHA`zzv=*qW&}rBXHT z7Dhj1H9l^=P2F)xTn6m7`?f)R;*d-VnL6TElOz`+>$Xeg9iYfZqW=v1wbqADd~7No z3FnuwL#Sv2!h^si)|7z4lrq}m;F#145zhNM3eLF9x+*O4n3%09E{YMmL1ggffT>&& zK9Bl|IU2;?KvTH9p-m9c4{X>ebGhKJDoRylV-86C78fM0?`fYxKSTdM8~h9s%B%gg zo@h6m@nkcQCMuDrVDP{^BvaTkFLlE_Y>{D&4dC(ah&nJMqYT5NNrX_rm<T!-eVvc~ z2}2$e`WpI9#bovBINe<^T<9i+g+(}Dy8{Xbr@}l<m!xmd*WZz?`=s1`fJFjxc~DPr z<ZR&@CeC70F(Go%U5m!+F~9T?D$71B;Ew;4diRv?%q;FJni6kaZseLd`OudU7LNfv z3$ezso)}_H^L8uzs=A)lnw1+yo36TW=^iQ6A7~g8*F6HageX_m8NcSQXf*a{=Y_t~ zvonraQ7;KWp{l*G@%8(Jd>E^x+pL<`b^mzP%Ge2LX56%m+$?{$gyRVBi?LMTSvPmp zx%*X2LXa2dLdlcZOXF%2_&lsXuh6QU@3@6ccHN3~y{=VWW|s#D+j!r1UXr*~u`d!f z<|+j+ENn|gQYoYG{=|`o%AQx(GkwT-;Oakq7{v;{2j1IBejO-{iU-qj$(+h5ZU&~W zF-8%kIk6qw_U{gU6a3!Dl2W2>v{UGI@|oqqq*KnRSpFI^B30eVS10|_*myTAMD9Hv zjlWL%wQ&JajhGMW<4sr&mwvO6&R7LQ-6wXAv*2GIP?=5ANmVV$qVIU_D{uD<QY^es zA19B*y_P*>@K?ipxjIz|Zp(_07mWRRGBc5i3U24%6okFp$a4c`LJ)<`-HZ!WNKgkh z*fUo-ijXzdW5BtxGwTt@TpSs|%%L<aQ7W>~Qa2F8YX;C?i6M+RhwrF3b~4e_(!sqH zG1oFu7V}Hx^aTL}(S)-5eH@Mv(y_1b!i{W#kc#oZ({JK-;w6r(q04=*Y6LSfY5Y$5 zPPE8&`GS=eF&sj5`HIHJb=j}1B`C+D#D3ZLziL^K)Nea}FK}d>^}skZCdpk<yI^D2 zJ|wownX6q<yCy*|)CsU<>d7irZ7RRdzFvML&YL~#4}%?H8F>w~P_dv=@6{{j5uBo3 zs{PGZ?3-SmD3zm98+5!yOXF(k62rz?nl<4bEQCYpN!M<*d?(%2bS&VH_nmFZig2We zY#97J!!;>zwl~v$Sq|`!y{1uz9EWn4{5`i`U01FVp`##3tY)@eJxr!yQs&M873qVZ zf%~O>4C(xP{rmYHG<6##R~Al2^P-gp<nwV1mk#U>lo?~JpAmcoO(Vt{fB*%_RCo1* zs?wY7fIb6^M^mO7Vadh<wlFb(n=OJK*JcPeq<R^2kryu({XC-+$oY1ddgjZFP4jI3 zkFk;4;QZAUuC6cK+5>O32yC)m@5Li6`P6Z)d^X%Z!k4(_d{j8r6s}9WiUk*^Ps&Ot zH)5u-kPPZBTz6JBT+F|m)m6z(%Rrc&)B4fMCP?2^HAA_Ve<bLs)My|iH-nM%$0~*e zzXzc;nmw61NrF&sLwb55(U0ofQ<{%}^!xB!Q*4SHk~BeF>>ZT`u9|N}CuxO>62xNX zjiT5<QMoD=mebfkg9P0n%cT$)sg!+2DE)&U-^8Vi_D)i}lXwj$oJeNk8Eux+agy;{ z@nq30-*x%&RchCd`FLY8f6|JM#ry8)nzNu5=n@Jvv%ukoqk!eb)f*^cRZ^GY&L_ix zU9#Oeh>%z<gw>o}pXAb}pQfzY+O<YS7AifGDwI(D!u|ffshG)>oIb5=`7y`hzDEn} z8hMrZ$z*~HA|LHQ!-Yb+WZB0dYm^2#BM+)gG`X<z$o{J$SX*Pn@n>r##xu9s=S<y( zog!*8;h+y<YL9RG1QaAYxxQP(!M~mA$^F>_pf_}ybOEW~0QKHJo!i~{vXKue7J}vW zt<e_GmSD;a%M7d5rDeG$StPd(tB2y_voQc2z7i|>E6c61_%s;cT{5%nAmuZRaSey~ zMl~G=_&UT%>8;K3C<ishHee}>EFipRVv-8kUgr!o7X(fUH-y%m9Yl2u4}_LRKg%8| z{mIfeT;GWfG=Q6;Mc(ZF#(kBJ^1f1$BLS3PM0;b5p&V*2InMjvK(m|N;szCMMoAv~ zxL77-Z5@MqoP2qtU4|1XsRgS+O5bWya1qzOeW8U_$Z!+{@-5n_oqd>Id3o>tUCx9! zWBUHMY0?Uybj4GP)Ay4D_gsGT!nozPj}b3jP)%d(40O^<`6iW>MFBD-cqBlIs>u}! zRGpUCdOsPG$D`1ajEUW`{fbgYS*|SW3GXtmA@)c}5k$!-SrC0$?}~MP`IRFv%56n< zdQ!q@Pcl#M($oY_B*!Z$ejCzLRs+%u4&{C;Qlr{HV}y0J*eg<kzxouDp0V1JJxFBQ zXvJQv<1)t<iuZ0b<y=*nGMTKG&b?|yC_!&o=D0>VE8SXPLRg`LW!SU(I0*qKNOS(f zeHXQF5u-;Nl`Ke=YTVKBtaKA_pQEn&?ZDc@@=>XqQS;D-{Z`i}?qcbc`Ccv$0ZBS! zxoBQ;j)<9C&9!-6a$-gu`lk1|N4EE{N5=)c<y7SG<tnD|5%}l~cW0XKC8D?N5!9xm zbXdwWr^kM)ND=1983yyI$crb6Yid^+y^DGLoTaN^WIZ-?K{eOdw=QhSe*E;A)O{^u zBZ$;pAbmrEP}YK*0C~1aqxeI6Q`nT@_+b&LdsoKB9;y4U@pC!!E}`*rYxJ(K<LA2Q zUB2Vz{8cGf6U)<rCOSQURJ$wDZs_Y<jb8pbC!<?8>oa$+0T+f1H$)j7-jw6p&cl!J zU3K1bb3atu#@2FKlr~rlnV{zi6R**t8t%jrmiG!NH}GSQk6oJAO4`fD8XVFZgbZUn zx}r~yT?*GqB%{CPsR=vQ^XC+PH>v{zyO=Z3I@(IC9A{zFFeV;>mBu{CmL}+3{NoN- zHEw{ifXUl*-fYF2P=BeLi|^+bP^IBQ^T`!%HOaFzrNw3h^z}ows3{n|W*_XeL}Mb( zr`~VUJ<z1}tdux9<3_8AV2(7WWJDT2XcFItHfO4<xy!rIpW-;0GZx^M%&oUP%gZT< zRUP>kUFsS>>7$xOB&|JYzVes7b~jP+5VJQ=UwKtc?KoK(a?!7y`~)`9`C1h`FYOF_ zNLhWeSt%FqNuT=AOcSZ8l3-IbfOXCK;ahH$@{Z|jv}Jg4+N7ls>@}--KXpyP7K##) zGE!Ox1qrj)Q0gkOjJ9HQ?N|K8wl2k}4r^L#!zL0IQiUI`R-us+VqDn2O<Xwiuj~1X zFP?lqS@A%?J`IygrlEqz69%R*JI<*Qlipey&2}nQ$FWq)9Z-Y_BINnv)#BYZdYy6M zO|x+B9xJJu{|pbx8Vc<BQsnh)><Zb%fmug$GECpT4>oeQ;h_<qzpEDx%b;L}PwNxD zLT)mLuy56+@U6HNu3`5C8F0s^rUbQ)<E0|oM%aZ&KQKACPjT?cL@N{9b6VPVG(~Hv z3m)hpoeL(EI#9dd-eYGuX9|wvTcgqVY%53YJVnbogtA>DDW&O)8fC1^S@+##niUOD zDbKYHjN2E;YTRM{{z(3cOS{`F8?8*xWg|_&J(iymhSxNF&BuiKb9zJN(US`NmF2CH z@8|>zerEZK83w&g#s`zxXY2$+3_H~4>;#n!gPz<MH@S^C7sMj8Kl@EE83C<Kl+y~b zW}Ax-dJ^cmE!Yk~-6>S)-JfOsRv8oK0vlG9PhtQ~vi+eQ(gCQy&C?Ej4)j@^KJNm< zvpkbme*jUE?wS(k29#|Rc%pp@NZZEsMBNN%%SXnch5@tZzl(<5!(zgc#{yp=D)z(Z zs3-!kXsF#3(xY$a0SxJIr9tX!(8+-r0K9b6gMbb|?#KQo7+=V=dW^-W_TT|LL<OfF z942T;RWt@s>T;-sd^jff765EHG-Hjr7WGjTHki9!4|&m<{?vIH;%(C!fO8qD6=)9- zScY^58UlFB;eGl()lV@Lkc(|iQF8&I4f@5Rf+g}1iQ#1W)SOU=;c5HiFkr|r;4qy= z84CEt9Ob6<y@t!2{}v7i7NEj5hZVGT-^XdR+CVfXlJ)w~FPvh%DfwHW3nZiuhQ#NA zkNe1iFge?po{D#Hg0NG4u9r~VFm-*lmvG&%hWT_e6xND`pR&v2l+4rzcT}MUgAf4} z=`b`w{~iK+R4+7SFBWvKT!QN_ocpi<avc~veeq9FsX%iwh*2@v^5#&3lyYs_lebx; z*>nWs^34aP0C06s7Xw-VBs%Ck0WkpVWt7%{8UWEUntMPF0Dsv&MQ4iYJaA#tA>xq< zi)a{$D-#Pn9Sa@!pHZm#?o>!BH?QI>H^J8&pyfoE4QUDDcOo@_zz0z~5j5+*wM7>1 z9F?#c+95oGSOTFsVXs5Afk>S2*P-uf3IqWKg8Kz-nFWFz5?*$p*D!yJMRVvigPMO% zd2ir?{`7zZqrA8;d?ZMR4J|p~3ILUkKod9uKuSj{4Lr`r8JBI8rBk3l<V^CGO^4mH zV}l=uz3KZ>55WWz)n`%<!vs4f3XvRyq-vd_IzXj)m#k`9YV1|VHGeDr4M`PV3-}Sh ztpmNNM_p}-wRo3Y4@$0YNDEf_N@WM9-=|fNY6maSr(6|mDEDwq^;I|`q#Hi$E8x8q z8RIkpT0qb?h80v82-69N7<veV=!69$Hq1btT`ZFb)-Vv~L}Sj!&4#n=({)12hR5%d zazZqKv)@K)M_L7OPeaxQ5oyu$CV}<k9_)2CAU!&oefZXCjkd{;VB-2rFaI8mKWE0^ zTQhPzZ|$e)s6?+LQzs_ngMv7vx9_(Gm}q$HK1nu2Mdj9DVd@O=yi8(Vu(id}oHG-* zM4oZzr_en`Hso<Q*FFQbDZ+;*GheaB9t^FhzFjlC2Q1>hrwYu+e8TyFANhFI5T60m zI<Q(n+yG`B_{AVptqLMjb>7bk8`0nJ>T&I$*Fb3X*k@2(VnWN%w?N<gKo;1TZ3sRD zv_KlCcf<|yLWD`))%qt_pR9#2X=`Z3FoH$jN%qlCqv(k;@}W)!G;L>pJh@MwN8V6p zy-oC{kDVXOM>T-d787WP`_T8<33?r-t<Ro;RI9=x>6g#QgKuLYMSqF=?q#?<qUYOL z0zv305b`BfH&h*n=n~H@un;HNNEKxe=$!xeSiothGc}}V7OE)BlzppwOA_M-x^Kob z>97_-`~a48_=BLoES4Z@0KE<zR6en0fV?Vfr5LIXnhoerX`yG@$RK5uQ&<<cpjRI| zWF~}0)F)k!_|HjEk9-FA3xK|id>aUdg!G0Le}>QMS@g0a8Fg@x!3jHYY!LP~6#@_g zAqasFHd74Q3bqVH;DnbAeF%benqk;ckM!f0a@@nJ-FCul4zkUMHh|~tQ*uIHhx6z& zbV6Tm3%w<i@=q`HxSl56xqK%GsRH7>B<_Y_1u<R{c0)Rf;nTV3f0k+cbhbFu*>N5& zf%p!cCLjWUm5x#xPyrxJN0SGk@Gkj}Ib|bS=t|`NTs&<5)gid=#WxO*+$Ua-zyznz zr(TZ&Bg$V6k&=(38AMo5JP*XxLj3IDTcU3?KwUj(jH-jY82AeSy^O#UH~~OjMrsZG z34mWlbPwFfCq0Ao0SQlwiUmnKz1{pDCYr>J7q;<$+2)r#wEGV2uWJG!SPUi`Y9b%D z8929%!iPK=c(V=Fj=&N)mOsJpGg80zWO1#d*_O{O;B*_V9nm3hZ5yo}`3)y=pga-Y zDC1veUiE#+;W<X%e~j8a<Csxiss{SMVgrOZfF?b~hMBwKAzRbefq|8~!eI3aF@8V@ z08<AgHJ}7QfDquK3dtabTMow!;4g<|6eZC?)Y7G{RbE&sKCv}!iB&L+`vzB!X$O@B z`s3^eLd9$cd|U--KC4!4!c=`TSj8lc1)3&n{fR)uFrbVvApc65&D`^x6%Q+#&&1V} zLCAvpQG5naX<|g}P_;mJC+3u1<_}KYXbu5c+j#A$=K)>Yr0pt+eG=1%q=7@*5!`P_ zpMF_ZJh<w7g<$nU`LuJnit-cH6S-4VjZhw)4$c(B_ur_(I`!SS{G4Uku`7U^Hztjg z_<yH=Nlm1j{zOSlgRDOCV#e$n3IHgVVR(X=0gTIVtwAIJ+GSYxAn$tkGuTH_=m>~^ z27tjct4u#8C6P(f&ro4WWC-G9C=}Z`R&f6d4Yz69&0|LF@sb1tX*?#<-#B6-T@V6Y zjMfu|G>CSa-V-i22!ET}6ZTUO?>6%hl+-bu%%2s5=)a`>aq6;?!ctk;X~{8@JEs#J zG$)9Ojc6Rw6(q>^o(TdOL{a~a2~q*XfdTO&A4(I*po(hK=c0<N74V@RtGaQ}>~m)i zjiJ$)X(L+L|DE=LlzI_E$7ne|fc)Rk4^jri?nImoK`S;*s7_~yUUrH}I!jOd?`t3P zpI-a~p|?z@pO9fMNTN658?zJ9I;00k$SJS(52kLOa{0l~KdH9&2Ue#O-KZ!5)Bu3# z9sJvd1U5)Tn4~^4Hdsa2xjr{lB#QtJHtcbzCJ-?j{y6jv2&x`~X|(>knR93lm4s0y z$fOY^41x|GHy=wg&_)$HHAt@>>VJrX#q_h$X%EYzU-hfsOq=k--jF{!ACnjY9z;G3 zgBC=&O~(iKABfi?|8`+c-pKTnOU0oRA394w)HYT-%4#3(A0gfp!w=!4>i`8WEBRxX zI7Rw65cVa`f55$BGQoeqeV-;9^Bh*uafY{bsW4?yTZdCRqD9~e04*K)An+alse^zQ zH~>J@K}rq$>#TK4G%ZiKGag)Y7J#@0dL(F!AclIvYDinqhk8;w2qF+oJ%JshK8UBD z<P1WfM%{g&`0PAY-7%q&Ha7Xs65t|#hT_Mccu%*4%lssa^B1KcxQOpf2~6z$Sql6c z?AfB;8<hG9YxF`?MG1lx`2I}`yk$SELy*8WOFR6z#?jwYz;}jU)}=2@8vbZi_y#8m z8Yh1T{*$0<xc>rt&2dsE(Zb%k?|o^iEq)oRTi)%cA8zygJrAIwKv?zvDU(A!+8Dm~ zHGl7mTm2XEA2O{c4f{VAJ}fLD2+|3I7)oiIjSuP#^|tx=AeDh+2!U+j0UG%P|4@at zk&|~btd}^3FlltyUN);t6&-(uJ=FgIeJa?$h+~vLatXhrtRep;<sbCaIXmjOxiudA zjBQH$t^dS%kM3m_aFb&X*Yo4wo&Vw#e0@qSNXKs;2URA3JsTCe%V}j6n6ZC~!@T0e zUy(phLt{a^?tDyL1N!twek<#H!;`g~%n#XhFjajv^>Ed&r+wbn@c-l*e29DB#Nj{d zfJi7S82COhCj_p*Z`%lbNU?!?i}Tq>37JVo*_nTiijIjV5?5A*^<O>It-R8_WA}V^ zQ}ID}Mkew$MY~z)!QcIZuEyMqcJHQkFJx7gAZ}ave86AwV-8o-M)WVRsA<#3dAUw_ z64mpdkSkMH4iWf<jcL>7BV~nto*bg=?~n%rvDiozA+SMIYy^ss8Xzt<l4Kx3KFT;u zQ{R7=%ZQbt+~tsCV)h~mm0IAr(}!b5<Ce<z7d!p`e}Vv;4<$CBXgi(TV(9Uh_TiY; zs~X7nAA*qhe<A~6(Ib=eTYqxWO!gBlQ!s)coJya@C5j+CYoEd;Qa7AqpZ+v#<U>($ zfC(F3G&D6pFdc#hC<ow3hbjeH13u8fa*7h9uWYSK%oycu80F374Y;tN{h?F%>(pxs zmTs4{!Aqx}M~u$@tcU-N3Cp<m4WEB)G{nDo%D?F0YB5Sa2(*7SOx5IhXLaz8r{ttA z@525-4gL!wFq9Pa`hD&73sUI#UySfqGHjG*BFv8?5S@*L&wmQ|7bW}#ji`$7B>=cT zW<ml$1ZWoWp%JoL3IAQ+tYNIg0K{f#)%S+KtHFG<>VH}R^^kTjS$!7uuy!r-n>?ah zlhhXhzm!>g@zJ`Jj(_o;|49%wQnLPcVhHDkD}y_l5QJkk@Mc6SQ#!VE68vXL_@7q5 z34XmrzK2(|cZynMJMsPQw6(ln@ZP^$q(7TyE3y;ve~<&59+e7lxe8&qO6WpzpyIVA z@n1aSpLOB?@B%gL<pkxd@5`6^ZD8ZXatMO{mvKft0g=9S&W1)^e0TP~oEL2#z3+sK z@4M+t@V9?1sr-u~{CgSY59$UGyr4gcfri1~=c3P>qR+y*fnkD!Iedape@u&as#^bR zxu4xtbAmSK4tvU`f)<|cbeQnFhi*QT$G(}tJI`Dekn<m#p}%@$;Aeo)Th&C*wCPyL z_r~V|Psjd8!-~bAg+T1T!C)uM-|P?_%3rHV^0<76Kr)zqGLx8ziaYSZ9T-oi<$5`L zaOYnQ!5_YT*t=@=i{3I3PZj@R>Ng|!Usivks>MGdFx<Gvs{2sP<m-Z$!~+}lH#ejT zX93g&@T5Z@0KEXhIuLk3DFD09pDQMS6s_D6ZL{N)`bww;0~=JdF)?Nx$df+#dZcPN z+djQ|v}*YO!v*S^&c&BpOwAG*e-0ShCqoMp{VVpp<3m#p`2W+ve^&zf8kb}W{^=zi z*UKXPJM5lNxj@Tpeox3xK<#aAPv}h`iz*kw4;Y-F1^_V~<Q@oy4O0;+353Ijs|Y<O zMxV|;{Grz|Uf4BW7+I?czTw>j$oMbz_v`q77s5K!OQ^HjXtUZN8}77jxUm0PEBbrN zYgs?SAIAiNp$%rmi2qUt_=ghyPyhd)aKEVY8D+K(eb(_(XXE?MKm3n&K-k-5MgN}h zHR_uvvs-AhUG2$*b}Z&7vHv0o{!ImQuB{f1E?Qcix;A=6jV5!T5B;GY{F@5yXQ*J& zH_E7~+fCHk`z!1J1M^_l`rPZG&tSUx98}S71HNvkTN%nea7f4@#pbh8-I+JBQrWkf zQ`B(IuJOG6kxFE3nP*qLbEm7PmFk=;j7s13ML?Z=m0IpxC2!s$gzFBzLs$JpAD;RC zZjszi(x^@|GD2DI<$W`wgEHLDb&MfXKdoSWUn<|Hy3Rw(8Q`ab3?cW@@00V(8Q)N5 z0{Ju}T!2)(4Id$!th^qW^j>JAQeQ_H+%k~OTSB=CLbdS6X6*Ri%Sly8AphJ@d*=n& zfN;a+oUr=>4?1vrjqmYmJB-to5bhA%C8&#}WQ`K;dTrE+|KVThtu+Qzxw1=wb@;;6 zpprA2xV*Z3S<)dWV7|lK+=cd&`NyZFg&&hKp~p#H;r2KtYp<H&_7}nJfj!dKMh9X} z7jn>6pWiR5?fSYO^cVhgb=FN?)^Puotn4LYYvZ{%eLpRJC@?ef+qM-;r%m%Wv6q@z zlrx`@0HB?U!-H6Jic{{L7qx{u+s((BXguU#!&#QbeMP^h{q0z-A~uC<8E_8HXxfBU zycBXqdH>Kx6nrRGMd+dsrw5-XX5&?s403?A_@;xdnf~ljc%nw@V_}xN0Zx$d9`IfC zyEI8XmitE8G5nT@=AtRw1b1);!>q%}&*tGJDR$aUYu9OivEr%4Xf&524F7NVF{l(D zFHH@KXf634XCr+zKdXTSQ|vMcr-@U-C7E4EOptez+}aN}Fzi9Z-^=npP5M;K-Wf1T zE=&r41qKgMtnEr--<(H+<p4PYKjp@MlDg^>3@NeI>EqHWg1Q}AWc7ugQbl;4j0;*S zh`}58z6rwoD07k4FBa@l#*D;snb5hX6kFdTwaAgbd^&RS&(P}Xzj-*VkBf}GHe4yo zoJ!yX+veSLRg-m4c%f4T8XgP_PA+~mqFlu`e<TJGLh7|LlH*0ezuq+b4j-U+s<o1z zuk&TW<BV7gQwXm;osT!1p7IZ>nZ>!Ho@PL@0P{2))!_b=YxE`7FLA`E+0t14%0_DR z*@&LbqcP{#7kR3D>SP~_vDriBFZ3_EdH9vhV}zu)!_@^vdI`|vc^}-23Ql?th`lC` z0qlB1ciWLxh_)J(U?!Poii{<zAE@kmDwx`?AZa}KR=&3r-0>v2E+e=6KZz|AdL27H zfQG23B)J+K-+i7W6Vy9iET=*_$1)NA;B7#kv|i4hAQMD7^0}~h^p@F<dO3qbf3J4P zyH=J{|1sLW0pD=mj(aOCy(ZWl^_)>-&59Z$)%3@Xu4lVC^Lavw>)dkXcT}Xf+D4C~ z!w|t@oC6GGx;>7>w>@rVs|_tLO!#J>jNjWdE?m>SoJv8W1EY_=)r%Dm1d>?j0UFpJ z%u9Mihz6AkqX~~Nu&Q`1=H?>XBy%B{L5LrP1^O7Edvcj{x#6FezBhM(g-$_sm6)Br z@bcT98QPTHJyw(wt6TAh5-a<Q-&|!KM`=X9GNFIFmUkSZtnXf3yQX7W^U<Xc%aU59 zAC7_1Sl4|98x-E%;i-wYGFjN>Xb6UtRy0`6qRo)Yqu=y?#IsIQ(OfS7M*rcAxK&D* znY=S#t>~fH)ne;Jxv<VIW9TDT*cwshw?c4hVX;(;d0?SJUHp0cJtK90-S(Qa11|M? zo!3T#;`6Hp6DG*Mp$;H0UI&<Kiq_QgQ!f5B^g_kgFCexcfMW_g$tgeK#W5{B$tgZ* z!!b=f$*Df^z%gw+$tgU!!!eCK$*B}?<1DR2Fb+HF@47S^XU4y=EQiIO2S(Tob4sLG zA*#1#7*7tt`uwObn<vL<leXF#FwiRqyKU;DuFyPutZO=ffuBMxi_zNK;o%Tjz8Bkp zPkB$ww_o~U`FdSoh0!Yl5HUp60=T}28rqWF6FZez!61FSS$A7Sx*dL5vgy1xXhN{* zAJ$if0T42Sx|bOFc}T?qrBo$hHkgRB?9*hCH4^?G0GmK$zix{NPEiY{c1IAdyG2=* z<X;3wL_+LKYl>7?h8YE_$nh$XBVp6c9(7}Tjj@l-hoa7}R`g@p`tj_KL|tH8Q^IZ^ zDk0BY2Ni|-o*!xNiG8Gf+bkIwS;jL`7xuEEQH=MCw@igzP^Nlks+wTTRK2rQO*Cey z-r1@q8?#mK996R$b5!qKRa1?*s&}5MIgNR$cfP8*jQOf}fvP1M3smnyRZB7!s@_Ga zmSQYYy^EnR8;e!%5>-nxmZ;vPs+MjnRlUnpEz?-0dIPG~#|Wt2a#ibRl&jtfRU2Sb zsNPCd8)#Ij-YQiaZd9q>YE>IyRIA<^Rr{Jzqk5OC+8-LrRd21T{gF|tdRM60*Nqjb zw@%f*VbrPKm8$klW2Nd{rD}g{tWv$JRqb2GYSp_&)&9g-qk7k>+P^c_s`=~Hy!A}= z*~gYjpcQew>(ozeBiE?~_?4-A=QO@qTER34{~t*s?>NbErp0k9bKKSKILqR=t+(TB z=6E3fm2)hP?_4>LIUb7dIG;H-#doY?jz{A=)-lIp?>Zu98d!lI5*z`KA^#m`Q8qnl z>3k`i6WP(d16b3Jiuit!R<KJ(dE0=Zzd;rJ	hoe8>6)Ss!@E`XyN(WVfR)gw|2I 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 G)Bgi3fUR8s literal 0 HcmV?d00001 diff --git a/cb-tools/java-source/src/main/example/web-socket-js/index.html b/cb-tools/java-source/src/main/example/web-socket-js/index.html new file mode 100644 index 00000000..ee8db6e6 --- /dev/null +++ b/cb-tools/java-source/src/main/example/web-socket-js/index.html @@ -0,0 +1,29 @@ +<script type="text/javascript"> + // Let the library know where WebSocketMain.swf is: + WEB_SOCKET_FORCE_FLASH = true + + // Let the library know where WebSocketMain.swf is: + WEB_SOCKET_SWF_LOCATION = "WebSocketMain.swf" +</script> + +<!-- Import JavaScript Libraries. --> +<script type="text/javascript" src="swfobject.js"></script> +<script type="text/javascript" src="web_socket.js"></script> + +<script type="text/javascript"> + + // Write your code in the same way as for native WebSocket: + var ws = new WebSocket('ws://localhost:8887') + ws.onopen = function() { + console.log('open') + ws.send('Hello') // Sends a message. + } + ws.onmessage = function(e) { + // Receives a message. + console.log('message', e.data) + } + ws.onclose = function() { + console.log('close') + } + +</script> diff --git a/cb-tools/java-source/src/main/example/web-socket-js/swfobject.js b/cb-tools/java-source/src/main/example/web-socket-js/swfobject.js new file mode 100644 index 00000000..8eafe9dd --- /dev/null +++ b/cb-tools/java-source/src/main/example/web-socket-js/swfobject.js @@ -0,0 +1,4 @@ +/* SWFObject v2.2 <http://code.google.com/p/swfobject/> + is released under the MIT License <http://www.opensource.org/licenses/mit-license.php> +*/ +var swfobject=function(){var D="undefined",r="object",S="Shockwave Flash",W="ShockwaveFlash.ShockwaveFlash",q="application/x-shockwave-flash",R="SWFObjectExprInst",x="onreadystatechange",O=window,j=document,t=navigator,T=false,U=[h],o=[],N=[],I=[],l,Q,E,B,J=false,a=false,n,G,m=true,M=function(){var aa=typeof j.getElementById!=D&&typeof j.getElementsByTagName!=D&&typeof j.createElement!=D,ah=t.userAgent.toLowerCase(),Y=t.platform.toLowerCase(),ae=Y?/win/.test(Y):/win/.test(ah),ac=Y?/mac/.test(Y):/mac/.test(ah),af=/webkit/.test(ah)?parseFloat(ah.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,X=!+"\v1",ag=[0,0,0],ab=null;if(typeof t.plugins!=D&&typeof t.plugins[S]==r){ab=t.plugins[S].description;if(ab&&!(typeof t.mimeTypes!=D&&t.mimeTypes[q]&&!t.mimeTypes[q].enabledPlugin)){T=true;X=false;ab=ab.replace(/^.*\s+(\S+\s+\S+$)/,"$1");ag[0]=parseInt(ab.replace(/^(.*)\..*$/,"$1"),10);ag[1]=parseInt(ab.replace(/^.*\.(.*)\s.*$/,"$1"),10);ag[2]=/[a-zA-Z]/.test(ab)?parseInt(ab.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0}}else{if(typeof O.ActiveXObject!=D){try{var ad=new ActiveXObject(W);if(ad){ab=ad.GetVariable("$version");if(ab){X=true;ab=ab.split(" ")[1].split(",");ag=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}}catch(Z){}}}return{w3:aa,pv:ag,wk:af,ie:X,win:ae,mac:ac}}(),k=function(){if(!M.w3){return}if((typeof j.readyState!=D&&j.readyState=="complete")||(typeof j.readyState==D&&(j.getElementsByTagName("body")[0]||j.body))){f()}if(!J){if(typeof j.addEventListener!=D){j.addEventListener("DOMContentLoaded",f,false)}if(M.ie&&M.win){j.attachEvent(x,function(){if(j.readyState=="complete"){j.detachEvent(x,arguments.callee);f()}});if(O==top){(function(){if(J){return}try{j.documentElement.doScroll("left")}catch(X){setTimeout(arguments.callee,0);return}f()})()}}if(M.wk){(function(){if(J){return}if(!/loaded|complete/.test(j.readyState)){setTimeout(arguments.callee,0);return}f()})()}s(f)}}();function f(){if(J){return}try{var Z=j.getElementsByTagName("body")[0].appendChild(C("span"));Z.parentNode.removeChild(Z)}catch(aa){return}J=true;var X=U.length;for(var Y=0;Y<X;Y++){U[Y]()}}function K(X){if(J){X()}else{U[U.length]=X}}function s(Y){if(typeof O.addEventListener!=D){O.addEventListener("load",Y,false)}else{if(typeof j.addEventListener!=D){j.addEventListener("load",Y,false)}else{if(typeof O.attachEvent!=D){i(O,"onload",Y)}else{if(typeof O.onload=="function"){var X=O.onload;O.onload=function(){X();Y()}}else{O.onload=Y}}}}}function h(){if(T){V()}else{H()}}function V(){var X=j.getElementsByTagName("body")[0];var aa=C(r);aa.setAttribute("type",q);var Z=X.appendChild(aa);if(Z){var Y=0;(function(){if(typeof Z.GetVariable!=D){var ab=Z.GetVariable("$version");if(ab){ab=ab.split(" ")[1].split(",");M.pv=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}else{if(Y<10){Y++;setTimeout(arguments.callee,10);return}}X.removeChild(aa);Z=null;H()})()}else{H()}}function H(){var ag=o.length;if(ag>0){for(var af=0;af<ag;af++){var Y=o[af].id;var ab=o[af].callbackFn;var aa={success:false,id:Y};if(M.pv[0]>0){var ae=c(Y);if(ae){if(F(o[af].swfVersion)&&!(M.wk&&M.wk<312)){w(Y,true);if(ab){aa.success=true;aa.ref=z(Y);ab(aa)}}else{if(o[af].expressInstall&&A()){var ai={};ai.data=o[af].expressInstall;ai.width=ae.getAttribute("width")||"0";ai.height=ae.getAttribute("height")||"0";if(ae.getAttribute("class")){ai.styleclass=ae.getAttribute("class")}if(ae.getAttribute("align")){ai.align=ae.getAttribute("align")}var ah={};var X=ae.getElementsByTagName("param");var ac=X.length;for(var ad=0;ad<ac;ad++){if(X[ad].getAttribute("name").toLowerCase()!="movie"){ah[X[ad].getAttribute("name")]=X[ad].getAttribute("value")}}P(ai,ah,Y,ab)}else{p(ae);if(ab){ab(aa)}}}}}else{w(Y,true);if(ab){var Z=z(Y);if(Z&&typeof Z.SetVariable!=D){aa.success=true;aa.ref=Z}ab(aa)}}}}}function z(aa){var X=null;var Y=c(aa);if(Y&&Y.nodeName=="OBJECT"){if(typeof Y.SetVariable!=D){X=Y}else{var Z=Y.getElementsByTagName(r)[0];if(Z){X=Z}}}return X}function A(){return !a&&F("6.0.65")&&(M.win||M.mac)&&!(M.wk&&M.wk<312)}function P(aa,ab,X,Z){a=true;E=Z||null;B={success:false,id:X};var ae=c(X);if(ae){if(ae.nodeName=="OBJECT"){l=g(ae);Q=null}else{l=ae;Q=X}aa.id=R;if(typeof aa.width==D||(!/%$/.test(aa.width)&&parseInt(aa.width,10)<310)){aa.width="310"}if(typeof aa.height==D||(!/%$/.test(aa.height)&&parseInt(aa.height,10)<137)){aa.height="137"}j.title=j.title.slice(0,47)+" - Flash Player Installation";var ad=M.ie&&M.win?"ActiveX":"PlugIn",ac="MMredirectURL="+O.location.toString().replace(/&/g,"%26")+"&MMplayerType="+ad+"&MMdoctitle="+j.title;if(typeof ab.flashvars!=D){ab.flashvars+="&"+ac}else{ab.flashvars=ac}if(M.ie&&M.win&&ae.readyState!=4){var Y=C("div");X+="SWFObjectNew";Y.setAttribute("id",X);ae.parentNode.insertBefore(Y,ae);ae.style.display="none";(function(){if(ae.readyState==4){ae.parentNode.removeChild(ae)}else{setTimeout(arguments.callee,10)}})()}u(aa,ab,X)}}function p(Y){if(M.ie&&M.win&&Y.readyState!=4){var X=C("div");Y.parentNode.insertBefore(X,Y);X.parentNode.replaceChild(g(Y),X);Y.style.display="none";(function(){if(Y.readyState==4){Y.parentNode.removeChild(Y)}else{setTimeout(arguments.callee,10)}})()}else{Y.parentNode.replaceChild(g(Y),Y)}}function g(ab){var aa=C("div");if(M.win&&M.ie){aa.innerHTML=ab.innerHTML}else{var Y=ab.getElementsByTagName(r)[0];if(Y){var ad=Y.childNodes;if(ad){var X=ad.length;for(var Z=0;Z<X;Z++){if(!(ad[Z].nodeType==1&&ad[Z].nodeName=="PARAM")&&!(ad[Z].nodeType==8)){aa.appendChild(ad[Z].cloneNode(true))}}}}}return aa}function u(ai,ag,Y){var X,aa=c(Y);if(M.wk&&M.wk<312){return X}if(aa){if(typeof ai.id==D){ai.id=Y}if(M.ie&&M.win){var ah="";for(var ae in ai){if(ai[ae]!=Object.prototype[ae]){if(ae.toLowerCase()=="data"){ag.movie=ai[ae]}else{if(ae.toLowerCase()=="styleclass"){ah+=' class="'+ai[ae]+'"'}else{if(ae.toLowerCase()!="classid"){ah+=" "+ae+'="'+ai[ae]+'"'}}}}}var af="";for(var ad in ag){if(ag[ad]!=Object.prototype[ad]){af+='<param name="'+ad+'" value="'+ag[ad]+'" />'}}aa.outerHTML='<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"'+ah+">"+af+"</object>";N[N.length]=ai.id;X=c(ai.id)}else{var Z=C(r);Z.setAttribute("type",q);for(var ac in ai){if(ai[ac]!=Object.prototype[ac]){if(ac.toLowerCase()=="styleclass"){Z.setAttribute("class",ai[ac])}else{if(ac.toLowerCase()!="classid"){Z.setAttribute(ac,ai[ac])}}}}for(var ab in ag){if(ag[ab]!=Object.prototype[ab]&&ab.toLowerCase()!="movie"){e(Z,ab,ag[ab])}}aa.parentNode.replaceChild(Z,aa);X=Z}}return X}function e(Z,X,Y){var aa=C("param");aa.setAttribute("name",X);aa.setAttribute("value",Y);Z.appendChild(aa)}function y(Y){var X=c(Y);if(X&&X.nodeName=="OBJECT"){if(M.ie&&M.win){X.style.display="none";(function(){if(X.readyState==4){b(Y)}else{setTimeout(arguments.callee,10)}})()}else{X.parentNode.removeChild(X)}}}function b(Z){var Y=c(Z);if(Y){for(var X in Y){if(typeof Y[X]=="function"){Y[X]=null}}Y.parentNode.removeChild(Y)}}function c(Z){var X=null;try{X=j.getElementById(Z)}catch(Y){}return X}function C(X){return j.createElement(X)}function i(Z,X,Y){Z.attachEvent(X,Y);I[I.length]=[Z,X,Y]}function F(Z){var Y=M.pv,X=Z.split(".");X[0]=parseInt(X[0],10);X[1]=parseInt(X[1],10)||0;X[2]=parseInt(X[2],10)||0;return(Y[0]>X[0]||(Y[0]==X[0]&&Y[1]>X[1])||(Y[0]==X[0]&&Y[1]==X[1]&&Y[2]>=X[2]))?true:false}function v(ac,Y,ad,ab){if(M.ie&&M.mac){return}var aa=j.getElementsByTagName("head")[0];if(!aa){return}var X=(ad&&typeof ad=="string")?ad:"screen";if(ab){n=null;G=null}if(!n||G!=X){var Z=C("style");Z.setAttribute("type","text/css");Z.setAttribute("media",X);n=aa.appendChild(Z);if(M.ie&&M.win&&typeof j.styleSheets!=D&&j.styleSheets.length>0){n=j.styleSheets[j.styleSheets.length-1]}G=X}if(M.ie&&M.win){if(n&&typeof n.addRule==r){n.addRule(ac,Y)}}else{if(n&&typeof j.createTextNode!=D){n.appendChild(j.createTextNode(ac+" {"+Y+"}"))}}}function w(Z,X){if(!m){return}var Y=X?"visible":"hidden";if(J&&c(Z)){c(Z).style.visibility=Y}else{v("#"+Z,"visibility:"+Y)}}function L(Y){var Z=/[\\\"<>\.;]/;var X=Z.exec(Y)!=null;return X&&typeof encodeURIComponent!=D?encodeURIComponent(Y):Y}var d=function(){if(M.ie&&M.win){window.attachEvent("onunload",function(){var ac=I.length;for(var ab=0;ab<ac;ab++){I[ab][0].detachEvent(I[ab][1],I[ab][2])}var Z=N.length;for(var aa=0;aa<Z;aa++){y(N[aa])}for(var Y in M){M[Y]=null}M=null;for(var X in swfobject){swfobject[X]=null}swfobject=null})}}();return{registerObject:function(ab,X,aa,Z){if(M.w3&&ab&&X){var Y={};Y.id=ab;Y.swfVersion=X;Y.expressInstall=aa;Y.callbackFn=Z;o[o.length]=Y;w(ab,false)}else{if(Z){Z({success:false,id:ab})}}},getObjectById:function(X){if(M.w3){return z(X)}},embedSWF:function(ab,ah,ae,ag,Y,aa,Z,ad,af,ac){var X={success:false,id:ah};if(M.w3&&!(M.wk&&M.wk<312)&&ab&&ah&&ae&&ag&&Y){w(ah,false);K(function(){ae+="";ag+="";var aj={};if(af&&typeof af===r){for(var al in af){aj[al]=af[al]}}aj.data=ab;aj.width=ae;aj.height=ag;var am={};if(ad&&typeof ad===r){for(var ak in ad){am[ak]=ad[ak]}}if(Z&&typeof Z===r){for(var ai in Z){if(typeof am.flashvars!=D){am.flashvars+="&"+ai+"="+Z[ai]}else{am.flashvars=ai+"="+Z[ai]}}}if(F(Y)){var an=u(aj,am,ah);if(aj.id==ah){w(ah,true)}X.success=true;X.ref=an}else{if(aa&&A()){aj.data=aa;P(aj,am,ah,ac);return}else{w(ah,true)}}if(ac){ac(X)}})}else{if(ac){ac(X)}}},switchOffAutoHideShow:function(){m=false},ua:M,getFlashPlayerVersion:function(){return{major:M.pv[0],minor:M.pv[1],release:M.pv[2]}},hasFlashPlayerVersion:F,createSWF:function(Z,Y,X){if(M.w3){return u(Z,Y,X)}else{return undefined}},showExpressInstall:function(Z,aa,X,Y){if(M.w3&&A()){P(Z,aa,X,Y)}},removeSWF:function(X){if(M.w3){y(X)}},createCSS:function(aa,Z,Y,X){if(M.w3){v(aa,Z,Y,X)}},addDomLoadEvent:K,addLoadEvent:s,getQueryParamValue:function(aa){var Z=j.location.search||j.location.hash;if(Z){if(/\?/.test(Z)){Z=Z.split("?")[1]}if(aa==null){return L(Z)}var Y=Z.split("&");for(var X=0;X<Y.length;X++){if(Y[X].substring(0,Y[X].indexOf("="))==aa){return L(Y[X].substring((Y[X].indexOf("=")+1)))}}}return""},expressInstallCallback:function(){if(a){var X=c(R);if(X&&l){X.parentNode.replaceChild(l,X);if(Q){w(Q,true);if(M.ie&&M.win){l.style.display="block"}}if(E){E(B)}}a=false}}}}(); \ No newline at end of file diff --git a/cb-tools/java-source/src/main/example/web-socket-js/web_socket.js b/cb-tools/java-source/src/main/example/web-socket-js/web_socket.js new file mode 100644 index 00000000..0561d10c --- /dev/null +++ b/cb-tools/java-source/src/main/example/web-socket-js/web_socket.js @@ -0,0 +1,389 @@ +// Copyright: Hiroshi Ichikawa <http://gimite.net/en/> +// License: New BSD License +// Reference: http://dev.w3.org/html5/websockets/ +// Reference: http://tools.ietf.org/html/rfc6455 + +(function() { + + if (window.WEB_SOCKET_FORCE_FLASH) { + // Keeps going. + } else if (window.WebSocket) { + return; + } else if (window.MozWebSocket) { + // Firefox. + window.WebSocket = MozWebSocket; + return; + } + + var logger; + if (window.WEB_SOCKET_LOGGER) { + logger = WEB_SOCKET_LOGGER; + } else if (window.console && window.console.log && window.console.error) { + // In some environment, console is defined but console.log or console.error is missing. + logger = window.console; + } else { + logger = {log: function(){ }, error: function(){ }}; + } + + // swfobject.hasFlashPlayerVersion("10.0.0") doesn't work with Gnash. + if (swfobject.getFlashPlayerVersion().major < 10) { + logger.error("Flash Player >= 10.0.0 is required."); + return; + } + if (location.protocol == "file:") { + logger.error( + "WARNING: web-socket-js doesn't work in file:///... URL " + + "unless you set Flash Security Settings properly. " + + "Open the page via Web server i.e. http://..."); + } + + /** + * Our own implementation of WebSocket class using Flash. + * @param {string} url + * @param {array or string} protocols + * @param {string} proxyHost + * @param {int} proxyPort + * @param {string} headers + */ + window.WebSocket = function(url, protocols, proxyHost, proxyPort, headers) { + var self = this; + self.__id = WebSocket.__nextId++; + WebSocket.__instances[self.__id] = self; + self.readyState = WebSocket.CONNECTING; + self.bufferedAmount = 0; + self.__events = {}; + if (!protocols) { + protocols = []; + } else if (typeof protocols == "string") { + protocols = [protocols]; + } + // Uses setTimeout() to make sure __createFlash() runs after the caller sets ws.onopen etc. + // Otherwise, when onopen fires immediately, onopen is called before it is set. + self.__createTask = setTimeout(function() { + WebSocket.__addTask(function() { + self.__createTask = null; + WebSocket.__flash.create( + self.__id, url, protocols, proxyHost || null, proxyPort || 0, headers || null); + }); + }, 0); + }; + + /** + * Send data to the web socket. + * @param {string} data The data to send to the socket. + * @return {boolean} True for success, false for failure. + */ + WebSocket.prototype.send = function(data) { + if (this.readyState == WebSocket.CONNECTING) { + throw "INVALID_STATE_ERR: Web Socket connection has not been established"; + } + // We use encodeURIComponent() here, because FABridge doesn't work if + // the argument includes some characters. We don't use escape() here + // because of this: + // https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Functions#escape_and_unescape_Functions + // But it looks decodeURIComponent(encodeURIComponent(s)) doesn't + // preserve all Unicode characters either e.g. "\uffff" in Firefox. + // Note by wtritch: Hopefully this will not be necessary using ExternalInterface. Will require + // additional testing. + var result = WebSocket.__flash.send(this.__id, encodeURIComponent(data)); + if (result < 0) { // success + return true; + } else { + this.bufferedAmount += result; + return false; + } + }; + + /** + * Close this web socket gracefully. + */ + WebSocket.prototype.close = function() { + if (this.__createTask) { + clearTimeout(this.__createTask); + this.__createTask = null; + this.readyState = WebSocket.CLOSED; + return; + } + if (this.readyState == WebSocket.CLOSED || this.readyState == WebSocket.CLOSING) { + return; + } + this.readyState = WebSocket.CLOSING; + WebSocket.__flash.close(this.__id); + }; + + /** + * Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>} + * + * @param {string} type + * @param {function} listener + * @param {boolean} useCapture + * @return void + */ + WebSocket.prototype.addEventListener = function(type, listener, useCapture) { + if (!(type in this.__events)) { + this.__events[type] = []; + } + this.__events[type].push(listener); + }; + + /** + * Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>} + * + * @param {string} type + * @param {function} listener + * @param {boolean} useCapture + * @return void + */ + WebSocket.prototype.removeEventListener = function(type, listener, useCapture) { + if (!(type in this.__events)) return; + var events = this.__events[type]; + for (var i = events.length - 1; i >= 0; --i) { + if (events[i] === listener) { + events.splice(i, 1); + break; + } + } + }; + + /** + * Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>} + * + * @param {Event} event + * @return void + */ + WebSocket.prototype.dispatchEvent = function(event) { + var events = this.__events[event.type] || []; + for (var i = 0; i < events.length; ++i) { + events[i](event); + } + var handler = this["on" + event.type]; + if (handler) handler.apply(this, [event]); + }; + + /** + * Handles an event from Flash. + * @param {Object} flashEvent + */ + WebSocket.prototype.__handleEvent = function(flashEvent) { + + if ("readyState" in flashEvent) { + this.readyState = flashEvent.readyState; + } + if ("protocol" in flashEvent) { + this.protocol = flashEvent.protocol; + } + + var jsEvent; + if (flashEvent.type == "open" || flashEvent.type == "error") { + jsEvent = this.__createSimpleEvent(flashEvent.type); + } else if (flashEvent.type == "close") { + jsEvent = this.__createSimpleEvent("close"); + jsEvent.wasClean = flashEvent.wasClean ? true : false; + jsEvent.code = flashEvent.code; + jsEvent.reason = flashEvent.reason; + } else if (flashEvent.type == "message") { + var data = decodeURIComponent(flashEvent.message); + jsEvent = this.__createMessageEvent("message", data); + } else { + throw "unknown event type: " + flashEvent.type; + } + + this.dispatchEvent(jsEvent); + + }; + + WebSocket.prototype.__createSimpleEvent = function(type) { + if (document.createEvent && window.Event) { + var event = document.createEvent("Event"); + event.initEvent(type, false, false); + return event; + } else { + return {type: type, bubbles: false, cancelable: false}; + } + }; + + WebSocket.prototype.__createMessageEvent = function(type, data) { + if (document.createEvent && window.MessageEvent && !window.opera) { + var event = document.createEvent("MessageEvent"); + event.initMessageEvent("message", false, false, data, null, null, window, null); + return event; + } else { + // IE and Opera, the latter one truncates the data parameter after any 0x00 bytes. + return {type: type, data: data, bubbles: false, cancelable: false}; + } + }; + + /** + * Define the WebSocket readyState enumeration. + */ + WebSocket.CONNECTING = 0; + WebSocket.OPEN = 1; + WebSocket.CLOSING = 2; + WebSocket.CLOSED = 3; + + WebSocket.__initialized = false; + WebSocket.__flash = null; + WebSocket.__instances = {}; + WebSocket.__tasks = []; + WebSocket.__nextId = 0; + + /** + * Load a new flash security policy file. + * @param {string} url + */ + WebSocket.loadFlashPolicyFile = function(url){ + WebSocket.__addTask(function() { + WebSocket.__flash.loadManualPolicyFile(url); + }); + }; + + /** + * Loads WebSocketMain.swf and creates WebSocketMain object in Flash. + */ + WebSocket.__initialize = function() { + + if (WebSocket.__initialized) return; + WebSocket.__initialized = true; + + if (WebSocket.__swfLocation) { + // For backword compatibility. + window.WEB_SOCKET_SWF_LOCATION = WebSocket.__swfLocation; + } + if (!window.WEB_SOCKET_SWF_LOCATION) { + logger.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf"); + return; + } + if (!window.WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR && + !WEB_SOCKET_SWF_LOCATION.match(/(^|\/)WebSocketMainInsecure\.swf(\?.*)?$/) && + WEB_SOCKET_SWF_LOCATION.match(/^\w+:\/\/([^\/]+)/)) { + var swfHost = RegExp.$1; + if (location.host != swfHost) { + logger.error( + "[WebSocket] You must host HTML and WebSocketMain.swf in the same host " + + "('" + location.host + "' != '" + swfHost + "'). " + + "See also 'How to host HTML file and SWF file in different domains' section " + + "in README.md. If you use WebSocketMainInsecure.swf, you can suppress this message " + + "by WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR = true;"); + } + } + var container = document.createElement("div"); + container.id = "webSocketContainer"; + // Hides Flash box. We cannot use display: none or visibility: hidden because it prevents + // Flash from loading at least in IE. So we move it out of the screen at (-100, -100). + // But this even doesn't work with Flash Lite (e.g. in Droid Incredible). So with Flash + // Lite, we put it at (0, 0). This shows 1x1 box visible at left-top corner but this is + // the best we can do as far as we know now. + container.style.position = "absolute"; + if (WebSocket.__isFlashLite()) { + container.style.left = "0px"; + container.style.top = "0px"; + } else { + container.style.left = "-100px"; + container.style.top = "-100px"; + } + var holder = document.createElement("div"); + holder.id = "webSocketFlash"; + container.appendChild(holder); + document.body.appendChild(container); + // See this article for hasPriority: + // http://help.adobe.com/en_US/as3/mobile/WS4bebcd66a74275c36cfb8137124318eebc6-7ffd.html + swfobject.embedSWF( + WEB_SOCKET_SWF_LOCATION, + "webSocketFlash", + "1" /* width */, + "1" /* height */, + "10.0.0" /* SWF version */, + null, + null, + {hasPriority: true, swliveconnect : true, allowScriptAccess: "always"}, + null, + function(e) { + if (!e.success) { + logger.error("[WebSocket] swfobject.embedSWF failed"); + } + } + ); + + }; + + /** + * Called by Flash to notify JS that it's fully loaded and ready + * for communication. + */ + WebSocket.__onFlashInitialized = function() { + // We need to set a timeout here to avoid round-trip calls + // to flash during the initialization process. + setTimeout(function() { + WebSocket.__flash = document.getElementById("webSocketFlash"); + WebSocket.__flash.setCallerUrl(location.href); + WebSocket.__flash.setDebug(!!window.WEB_SOCKET_DEBUG); + for (var i = 0; i < WebSocket.__tasks.length; ++i) { + WebSocket.__tasks[i](); + } + WebSocket.__tasks = []; + }, 0); + }; + + /** + * Called by Flash to notify WebSockets events are fired. + */ + WebSocket.__onFlashEvent = function() { + setTimeout(function() { + try { + // Gets events using receiveEvents() instead of getting it from event object + // of Flash event. This is to make sure to keep message order. + // It seems sometimes Flash events don't arrive in the same order as they are sent. + var events = WebSocket.__flash.receiveEvents(); + for (var i = 0; i < events.length; ++i) { + WebSocket.__instances[events[i].webSocketId].__handleEvent(events[i]); + } + } catch (e) { + logger.error(e); + } + }, 0); + return true; + }; + + // Called by Flash. + WebSocket.__log = function(message) { + logger.log(decodeURIComponent(message)); + }; + + // Called by Flash. + WebSocket.__error = function(message) { + logger.error(decodeURIComponent(message)); + }; + + WebSocket.__addTask = function(task) { + if (WebSocket.__flash) { + task(); + } else { + WebSocket.__tasks.push(task); + } + }; + + /** + * Test if the browser is running flash lite. + * @return {boolean} True if flash lite is running, false otherwise. + */ + WebSocket.__isFlashLite = function() { + if (!window.navigator || !window.navigator.mimeTypes) { + return false; + } + var mimeType = window.navigator.mimeTypes["application/x-shockwave-flash"]; + if (!mimeType || !mimeType.enabledPlugin || !mimeType.enabledPlugin.filename) { + return false; + } + return mimeType.enabledPlugin.filename.match(/flashlite/i) ? true : false; + }; + + if (!window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION) { + // NOTE: + // This fires immediately if web_socket.js is dynamically loaded after + // the document is loaded. + swfobject.addDomLoadEvent(function() { + WebSocket.__initialize(); + }); + } + +})(); diff --git a/cb-tools/java-source/src/main/java/org/java_websocket/AbstractWrappedByteChannel.java b/cb-tools/java-source/src/main/java/org/java_websocket/AbstractWrappedByteChannel.java new file mode 100644 index 00000000..0481a6d4 --- /dev/null +++ b/cb-tools/java-source/src/main/java/org/java_websocket/AbstractWrappedByteChannel.java @@ -0,0 +1,75 @@ +package org.java_websocket; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.ByteChannel; +import java.nio.channels.SocketChannel; + +import javax.net.ssl.SSLException; + + +public class AbstractWrappedByteChannel implements WrappedByteChannel { + + private final ByteChannel channel; + + public AbstractWrappedByteChannel( ByteChannel towrap ) { + this.channel = towrap; + } + + public AbstractWrappedByteChannel( WrappedByteChannel towrap ) { + this.channel = towrap; + } + + @Override + public int read( ByteBuffer dst ) throws IOException { + return channel.read( dst ); + } + + @Override + public boolean isOpen() { + return channel.isOpen(); + } + + @Override + public void close() throws IOException { + channel.close(); + } + + @Override + public int write( ByteBuffer src ) throws IOException { + return channel.write( src ); + } + + @Override + public boolean isNeedWrite() { + return channel instanceof WrappedByteChannel ? ( (WrappedByteChannel) channel ).isNeedWrite() : false; + } + + @Override + public void writeMore() throws IOException { + if( channel instanceof WrappedByteChannel ) + ( (WrappedByteChannel) channel ).writeMore(); + + } + + @Override + public boolean isNeedRead() { + return channel instanceof WrappedByteChannel ? ( (WrappedByteChannel) channel ).isNeedRead() : false; + + } + + @Override + public int readMore( ByteBuffer dst ) throws SSLException { + return channel instanceof WrappedByteChannel ? ( (WrappedByteChannel) channel ).readMore( dst ) : 0; + } + + @Override + public boolean isBlocking() { + if( channel instanceof SocketChannel ) + return ( (SocketChannel) channel ).isBlocking(); + else if( channel instanceof WrappedByteChannel ) + return ( (WrappedByteChannel) channel ).isBlocking(); + return false; + } + +} diff --git a/cb-tools/java-source/src/main/java/org/java_websocket/SSLSocketChannel2.java b/cb-tools/java-source/src/main/java/org/java_websocket/SSLSocketChannel2.java new file mode 100644 index 00000000..ba8d8f88 --- /dev/null +++ b/cb-tools/java-source/src/main/java/org/java_websocket/SSLSocketChannel2.java @@ -0,0 +1,380 @@ +/** + * Copyright (C) 2003 Alexander Kout + * Originally from the jFxp project (http://jfxp.sourceforge.net/). + * Copied with permission June 11, 2012 by Femi Omojola (fomojola@ideasynthesis.com). + */ +package org.java_websocket; + +import java.io.IOException; +import java.net.Socket; +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.ByteChannel; +import java.nio.channels.SelectableChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.SocketChannel; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLEngineResult.HandshakeStatus; +import javax.net.ssl.SSLEngineResult.Status; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLSession; + +/** + * Implements the relevant portions of the SocketChannel interface with the SSLEngine wrapper. + */ +public class SSLSocketChannel2 implements ByteChannel, WrappedByteChannel { + /** + * This object is used to feed the {@link SSLEngine}'s wrap and unwrap methods during the handshake phase. + **/ + protected static ByteBuffer emptybuffer = ByteBuffer.allocate( 0 ); + + protected ExecutorService exec; + + protected List<Future<?>> tasks; + + /** raw payload incomming */ + protected ByteBuffer inData; + /** encrypted data outgoing */ + protected ByteBuffer outCrypt; + /** encrypted data incoming */ + protected ByteBuffer inCrypt; + + /** the underlying channel */ + protected SocketChannel socketChannel; + /** used to set interestOP SelectionKey.OP_WRITE for the underlying channel */ + protected SelectionKey selectionKey; + + protected SSLEngine sslEngine; + protected SSLEngineResult readEngineResult; + protected SSLEngineResult writeEngineResult; + + /** + * Should be used to count the buffer allocations. + * But because of #190 where HandshakeStatus.FINISHED is not properly returned by nio wrap/unwrap this variable is used to check whether {@link #createBuffers(SSLSession)} needs to be called. + **/ + protected int bufferallocations = 0; + + public SSLSocketChannel2( SocketChannel channel , SSLEngine sslEngine , ExecutorService exec , SelectionKey key ) throws IOException { + if( channel == null || sslEngine == null || exec == null ) + throw new IllegalArgumentException( "parameter must not be null" ); + + this.socketChannel = channel; + this.sslEngine = sslEngine; + this.exec = exec; + + readEngineResult = writeEngineResult = new SSLEngineResult( Status.BUFFER_UNDERFLOW, sslEngine.getHandshakeStatus(), 0, 0 ); // init to prevent NPEs + + tasks = new ArrayList<Future<?>>( 3 ); + if( key != null ) { + key.interestOps( key.interestOps() | SelectionKey.OP_WRITE ); + this.selectionKey = key; + } + createBuffers( sslEngine.getSession() ); + // kick off handshake + socketChannel.write( wrap( emptybuffer ) );// initializes res + processHandshake(); + } + + private void consumeFutureUninterruptible( Future<?> f ) { + try { + boolean interrupted = false; + while ( true ) { + try { + f.get(); + break; + } catch ( InterruptedException e ) { + interrupted = true; + } + } + if( interrupted ) + Thread.currentThread().interrupt(); + } catch ( ExecutionException e ) { + throw new RuntimeException( e ); + } + } + + /** + * This method will do whatever necessary to process the sslengine handshake. + * Thats why it's called both from the {@link #read(ByteBuffer)} and {@link #write(ByteBuffer)} + **/ + private synchronized void processHandshake() throws IOException { + if( sslEngine.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING ) + return; // since this may be called either from a reading or a writing thread and because this method is synchronized it is necessary to double check if we are still handshaking. + if( !tasks.isEmpty() ) { + Iterator<Future<?>> it = tasks.iterator(); + while ( it.hasNext() ) { + Future<?> f = it.next(); + if( f.isDone() ) { + it.remove(); + } else { + if( isBlocking() ) + consumeFutureUninterruptible( f ); + return; + } + } + } + + if( sslEngine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP ) { + if( !isBlocking() || readEngineResult.getStatus() == Status.BUFFER_UNDERFLOW ) { + inCrypt.compact(); + int read = socketChannel.read( inCrypt ); + if( read == -1 ) { + throw new IOException( "connection closed unexpectedly by peer" ); + } + inCrypt.flip(); + } + inData.compact(); + unwrap(); + if( readEngineResult.getHandshakeStatus() == HandshakeStatus.FINISHED ) { + createBuffers( sslEngine.getSession() ); + return; + } + } + consumeDelegatedTasks(); + if( tasks.isEmpty() || sslEngine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_WRAP ) { + socketChannel.write( wrap( emptybuffer ) ); + if( writeEngineResult.getHandshakeStatus() == HandshakeStatus.FINISHED ) { + createBuffers( sslEngine.getSession() ); + return; + } + } + assert ( sslEngine.getHandshakeStatus() != HandshakeStatus.NOT_HANDSHAKING );// this function could only leave NOT_HANDSHAKING after createBuffers was called unless #190 occurs which means that nio wrap/unwrap never return HandshakeStatus.FINISHED + + bufferallocations = 1; // look at variable declaration why this line exists and #190. Without this line buffers would not be be recreated when #190 AND a rehandshake occur. + } + private synchronized ByteBuffer wrap( ByteBuffer b ) throws SSLException { + outCrypt.compact(); + writeEngineResult = sslEngine.wrap( b, outCrypt ); + outCrypt.flip(); + return outCrypt; + } + + /** + * performs the unwrap operation by unwrapping from {@link #inCrypt} to {@link #inData} + **/ + private synchronized ByteBuffer unwrap() throws SSLException { + int rem; + do { + rem = inData.remaining(); + readEngineResult = sslEngine.unwrap( inCrypt, inData ); + } while ( readEngineResult.getStatus() == SSLEngineResult.Status.OK && ( rem != inData.remaining() || sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP ) ); + inData.flip(); + return inData; + } + + protected void consumeDelegatedTasks() { + Runnable task; + while ( ( task = sslEngine.getDelegatedTask() ) != null ) { + tasks.add( exec.submit( task ) ); + // task.run(); + } + } + + protected void createBuffers( SSLSession session ) { + int netBufferMax = session.getPacketBufferSize(); + int appBufferMax = Math.max(session.getApplicationBufferSize(), netBufferMax); + + if( inData == null ) { + inData = ByteBuffer.allocate( appBufferMax ); + outCrypt = ByteBuffer.allocate( netBufferMax ); + inCrypt = ByteBuffer.allocate( netBufferMax ); + } else { + if( inData.capacity() != appBufferMax ) + inData = ByteBuffer.allocate( appBufferMax ); + if( outCrypt.capacity() != netBufferMax ) + outCrypt = ByteBuffer.allocate( netBufferMax ); + if( inCrypt.capacity() != netBufferMax ) + inCrypt = ByteBuffer.allocate( netBufferMax ); + } + inData.rewind(); + inData.flip(); + inCrypt.rewind(); + inCrypt.flip(); + outCrypt.rewind(); + outCrypt.flip(); + bufferallocations++; + } + + public int write( ByteBuffer src ) throws IOException { + if( !isHandShakeComplete() ) { + processHandshake(); + return 0; + } + // assert ( bufferallocations > 1 ); //see #190 + //if( bufferallocations <= 1 ) { + // createBuffers( sslEngine.getSession() ); + //} + int num = socketChannel.write( wrap( src ) ); + return num; + + } + + /** + * Blocks when in blocking mode until at least one byte has been decoded.<br> + * When not in blocking mode 0 may be returned. + * + * @return the number of bytes read. + **/ + public int read( ByteBuffer dst ) throws IOException { + if( !dst.hasRemaining() ) + return 0; + if( !isHandShakeComplete() ) { + if( isBlocking() ) { + while ( !isHandShakeComplete() ) { + processHandshake(); + } + } else { + processHandshake(); + if( !isHandShakeComplete() ) { + return 0; + } + } + } + // assert ( bufferallocations > 1 ); //see #190 + //if( bufferallocations <= 1 ) { + // createBuffers( sslEngine.getSession() ); + //} + /* 1. When "dst" is smaller than "inData" readRemaining will fill "dst" with data decoded in a previous read call. + * 2. When "inCrypt" contains more data than "inData" has remaining space, unwrap has to be called on more time(readRemaining) + */ + int purged = readRemaining( dst ); + if( purged != 0 ) + return purged; + + /* We only continue when we really need more data from the network. + * Thats the case if inData is empty or inCrypt holds to less data than necessary for decryption + */ + assert ( inData.position() == 0 ); + inData.clear(); + + if( !inCrypt.hasRemaining() ) + inCrypt.clear(); + else + inCrypt.compact(); + + if( isBlocking() || readEngineResult.getStatus() == Status.BUFFER_UNDERFLOW ) + if( socketChannel.read( inCrypt ) == -1 ) { + return -1; + } + inCrypt.flip(); + unwrap(); + + int transfered = transfereTo( inData, dst ); + if( transfered == 0 && isBlocking() ) { + return read( dst ); // "transfered" may be 0 when not enough bytes were received or during rehandshaking + } + return transfered; + } + /** + * {@link #read(ByteBuffer)} may not be to leave all buffers(inData, inCrypt) + **/ + private int readRemaining( ByteBuffer dst ) throws SSLException { + if( inData.hasRemaining() ) { + return transfereTo( inData, dst ); + } + if( !inData.hasRemaining() ) + inData.clear(); + // test if some bytes left from last read (e.g. BUFFER_UNDERFLOW) + if( inCrypt.hasRemaining() ) { + unwrap(); + int amount = transfereTo( inData, dst ); + if( amount > 0 ) + return amount; + } + return 0; + } + + public boolean isConnected() { + return socketChannel.isConnected(); + } + + public void close() throws IOException { + sslEngine.closeOutbound(); + sslEngine.getSession().invalidate(); + if( socketChannel.isOpen() ) + socketChannel.write( wrap( emptybuffer ) );// FIXME what if not all bytes can be written + socketChannel.close(); + exec.shutdownNow(); + } + + private boolean isHandShakeComplete() { + HandshakeStatus status = sslEngine.getHandshakeStatus(); + return status == SSLEngineResult.HandshakeStatus.FINISHED || status == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING; + } + + public SelectableChannel configureBlocking( boolean b ) throws IOException { + return socketChannel.configureBlocking( b ); + } + + public boolean connect( SocketAddress remote ) throws IOException { + return socketChannel.connect( remote ); + } + + public boolean finishConnect() throws IOException { + return socketChannel.finishConnect(); + } + + public Socket socket() { + return socketChannel.socket(); + } + + public boolean isInboundDone() { + return sslEngine.isInboundDone(); + } + + @Override + public boolean isOpen() { + return socketChannel.isOpen(); + } + + @Override + public boolean isNeedWrite() { + return outCrypt.hasRemaining() || !isHandShakeComplete(); // FIXME this condition can cause high cpu load during handshaking when network is slow + } + + @Override + public void writeMore() throws IOException { + write( outCrypt ); + } + + @Override + public boolean isNeedRead() { + return inData.hasRemaining() || ( inCrypt.hasRemaining() && readEngineResult.getStatus() != Status.BUFFER_UNDERFLOW && readEngineResult.getStatus() != Status.CLOSED ); + } + + @Override + public int readMore( ByteBuffer dst ) throws SSLException { + return readRemaining( dst ); + } + + private int transfereTo( ByteBuffer from, ByteBuffer to ) { + int fremain = from.remaining(); + int toremain = to.remaining(); + if( fremain > toremain ) { + // FIXME there should be a more efficient transfer method + int limit = Math.min( fremain, toremain ); + for( int i = 0 ; i < limit ; i++ ) { + to.put( from.get() ); + } + return limit; + } else { + to.put( from ); + return fremain; + } + + } + + @Override + public boolean isBlocking() { + return socketChannel.isBlocking(); + } + +} \ No newline at end of file diff --git a/cb-tools/java-source/src/main/java/org/java_websocket/SocketChannelIOHelper.java b/cb-tools/java-source/src/main/java/org/java_websocket/SocketChannelIOHelper.java new file mode 100644 index 00000000..e0da2bdc --- /dev/null +++ b/cb-tools/java-source/src/main/java/org/java_websocket/SocketChannelIOHelper.java @@ -0,0 +1,71 @@ +package org.java_websocket; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.ByteChannel; +import java.nio.channels.spi.AbstractSelectableChannel; + +import org.java_websocket.WebSocket.Role; + +public class SocketChannelIOHelper { + + public static boolean read( final ByteBuffer buf, WebSocketImpl ws, ByteChannel channel ) throws IOException { + buf.clear(); + int read = channel.read( buf ); + buf.flip(); + + if( read == -1 ) { + ws.eot(); + return false; + } + return read != 0; + } + + /** + * @see WrappedByteChannel#readMore(ByteBuffer) + * @return returns whether there is more data left which can be obtained via {@link #readMore(ByteBuffer, WebSocketImpl, WrappedByteChannel)} + **/ + public static boolean readMore( final ByteBuffer buf, WebSocketImpl ws, WrappedByteChannel channel ) throws IOException { + buf.clear(); + int read = channel.readMore( buf ); + buf.flip(); + + if( read == -1 ) { + ws.eot(); + return false; + } + return channel.isNeedRead(); + } + + /** Returns whether the whole outQueue has been flushed */ + public static boolean batch( WebSocketImpl ws, ByteChannel sockchannel ) throws IOException { + ByteBuffer buffer = ws.outQueue.peek(); + WrappedByteChannel c = null; + + if( buffer == null ) { + if( sockchannel instanceof WrappedByteChannel ) { + c = (WrappedByteChannel) sockchannel; + if( c.isNeedWrite() ) { + c.writeMore(); + } + } + } else { + do {// FIXME writing as much as possible is unfair!! + /*int written = */sockchannel.write( buffer ); + if( buffer.remaining() > 0 ) { + return false; + } else { + ws.outQueue.poll(); // Buffer finished. Remove it. + buffer = ws.outQueue.peek(); + } + } while ( buffer != null ); + } + + if( ws.outQueue.isEmpty() && ws.isFlushAndClose() && ws.getDraft().getRole() == Role.SERVER ) {// + synchronized ( ws ) { + ws.closeConnection(); + } + } + return c != null ? !( (WrappedByteChannel) sockchannel ).isNeedWrite() : true; + } +} diff --git a/cb-tools/java-source/src/main/java/org/java_websocket/WebSocket.java b/cb-tools/java-source/src/main/java/org/java_websocket/WebSocket.java new file mode 100644 index 00000000..a661eddb --- /dev/null +++ b/cb-tools/java-source/src/main/java/org/java_websocket/WebSocket.java @@ -0,0 +1,124 @@ +package org.java_websocket; + +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.NotYetConnectedException; + +import org.java_websocket.drafts.Draft; +import org.java_websocket.framing.Framedata; +import org.java_websocket.framing.Framedata.Opcode; + +public interface WebSocket { + public enum Role { + CLIENT, SERVER + } + + public enum READYSTATE { + NOT_YET_CONNECTED, CONNECTING, OPEN, CLOSING, CLOSED; + } + + /** + * The default port of WebSockets, as defined in the spec. If the nullary + * constructor is used, DEFAULT_PORT will be the port the WebSocketServer + * is binded to. Note that ports under 1024 usually require root permissions. + */ + public static final int DEFAULT_PORT = 80; + + public static final int DEFAULT_WSS_PORT = 443; + + /** + * sends the closing handshake. + * may be send in response to an other handshake. + */ + public void close( int code, String message ); + + public void close( int code ); + + /** Convenience function which behaves like close(CloseFrame.NORMAL) */ + public void close(); + + /** + * This will close the connection immediately without a proper close handshake. + * The code and the message therefore won't be transfered over the wire also they will be forwarded to onClose/onWebsocketClose. + **/ + public abstract void closeConnection( int code, String message ); + + /** + * Send Text data to the other end. + * + * @throws IllegalArgumentException + * @throws NotYetConnectedException + */ + public abstract void send( String text ) throws NotYetConnectedException; + + /** + * Send Binary data (plain bytes) to the other end. + * + * @throws IllegalArgumentException + * @throws NotYetConnectedException + */ + public abstract void send( ByteBuffer bytes ) throws IllegalArgumentException , NotYetConnectedException; + + public abstract void send( byte[] bytes ) throws IllegalArgumentException , NotYetConnectedException; + + public abstract void sendFrame( Framedata framedata ); + + /** + * Allows to send continuous/fragmented frames conveniently. <br> + * For more into on this frame type see http://tools.ietf.org/html/rfc6455#section-5.4<br> + * + * If the first frame you send is also the last then it is not a fragmented frame and will received via onMessage instead of onFragmented even though it was send by this method. + * + * @param op + * This is only important for the first frame in the sequence. Opcode.TEXT, Opcode.BINARY are allowed. + * @param buffer + * The buffer which contains the payload. It may have no bytes remaining. + * @param fin + * true means the current frame is the last in the sequence. + **/ + public abstract void sendFragmentedFrame( Opcode op, ByteBuffer buffer, boolean fin ); + + public abstract boolean hasBufferedData(); + + /** + * @returns never returns null + */ + public abstract InetSocketAddress getRemoteSocketAddress(); + + /** + * @returns never returns null + */ + public abstract InetSocketAddress getLocalSocketAddress(); + + public abstract boolean isConnecting(); + + public abstract boolean isOpen(); + + public abstract boolean isClosing(); + + /** + * Returns true when no further frames may be submitted<br> + * This happens before the socket connection is closed. + */ + public abstract boolean isFlushAndClose(); + + /** Returns whether the close handshake has been completed and the socket is closed. */ + public abstract boolean isClosed(); + + public abstract Draft getDraft(); + + /** + * Retrieve the WebSocket 'readyState'. + * This represents the state of the connection. + * It returns a numerical value, as per W3C WebSockets specs. + * + * @return Returns '0 = CONNECTING', '1 = OPEN', '2 = CLOSING' or '3 = CLOSED' + */ + public abstract READYSTATE getReadyState(); + + /** + * Returns the HTTP Request-URI as defined by http://tools.ietf.org/html/rfc2616#section-5.1.2<br> + * If the opening handshake has not yet happened it will return null. + **/ + public abstract String getResourceDescriptor(); +} \ No newline at end of file diff --git a/cb-tools/java-source/src/main/java/org/java_websocket/WebSocketAdapter.java b/cb-tools/java-source/src/main/java/org/java_websocket/WebSocketAdapter.java new file mode 100644 index 00000000..290e1049 --- /dev/null +++ b/cb-tools/java-source/src/main/java/org/java_websocket/WebSocketAdapter.java @@ -0,0 +1,104 @@ +package org.java_websocket; + +import java.net.InetSocketAddress; + +import org.java_websocket.drafts.Draft; +import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.exceptions.InvalidHandshakeException; +import org.java_websocket.framing.Framedata; +import org.java_websocket.framing.Framedata.Opcode; +import org.java_websocket.framing.FramedataImpl1; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.HandshakeImpl1Server; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.handshake.ServerHandshakeBuilder; + +/** + * This class default implements all methods of the WebSocketListener that can be overridden optionally when advances functionalities is needed.<br> + **/ +public abstract class WebSocketAdapter implements WebSocketListener { + + /** + * This default implementation does not do anything. Go ahead and overwrite it. + * + * @see org.java_websocket.WebSocketListener#onWebsocketHandshakeReceivedAsServer(WebSocket, Draft, ClientHandshake) + */ + @Override + public ServerHandshakeBuilder onWebsocketHandshakeReceivedAsServer( WebSocket conn, Draft draft, ClientHandshake request ) throws InvalidDataException { + return new HandshakeImpl1Server(); + } + + @Override + public void onWebsocketHandshakeReceivedAsClient( WebSocket conn, ClientHandshake request, ServerHandshake response ) throws InvalidDataException { + } + + /** + * This default implementation does not do anything which will cause the connections to always progress. + * + * @see org.java_websocket.WebSocketListener#onWebsocketHandshakeSentAsClient(WebSocket, ClientHandshake) + */ + @Override + public void onWebsocketHandshakeSentAsClient( WebSocket conn, ClientHandshake request ) throws InvalidDataException { + } + + /** + * This default implementation does not do anything. Go ahead and overwrite it + * + * @see org.java_websocket.WebSocketListener#onWebsocketMessageFragment(WebSocket, Framedata) + */ + @Override + public void onWebsocketMessageFragment( WebSocket conn, Framedata frame ) { + } + + /** + * This default implementation will send a pong in response to the received ping. + * The pong frame will have the same payload as the ping frame. + * + * @see org.java_websocket.WebSocketListener#onWebsocketPing(WebSocket, Framedata) + */ + @Override + public void onWebsocketPing( WebSocket conn, Framedata f ) { + FramedataImpl1 resp = new FramedataImpl1( f ); + resp.setOptcode( Opcode.PONG ); + conn.sendFrame( resp ); + } + + /** + * This default implementation does not do anything. Go ahead and overwrite it. + * + * @see @see org.java_websocket.WebSocketListener#onWebsocketPong(WebSocket, Framedata) + */ + @Override + public void onWebsocketPong( WebSocket conn, Framedata f ) { + } + + /** + * Gets the XML string that should be returned if a client requests a Flash + * security policy. + * + * The default implementation allows access from all remote domains, but + * only on the port that this WebSocketServer is listening on. + * + * This is specifically implemented for gitime's WebSocket client for Flash: + * http://github.com/gimite/web-socket-js + * + * @return An XML String that comforts to Flash's security policy. You MUST + * not include the null char at the end, it is appended automatically. + * @throws InvalidDataException thrown when some data that is required to generate the flash-policy like the websocket local port could not be obtained e.g because the websocket is not connected. + */ + @Override + public String getFlashPolicy( WebSocket conn ) throws InvalidDataException { + InetSocketAddress adr = conn.getLocalSocketAddress(); + if(null == adr){ + throw new InvalidHandshakeException( "socket not bound" ); + } + + StringBuffer sb = new StringBuffer( 90 ); + sb.append( "<cross-domain-policy><allow-access-from domain=\"*\" to-ports=\"" ); + sb.append(adr.getPort()); + sb.append( "\" /></cross-domain-policy>\0" ); + + return sb.toString(); + } + +} diff --git a/cb-tools/java-source/src/main/java/org/java_websocket/WebSocketFactory.java b/cb-tools/java-source/src/main/java/org/java_websocket/WebSocketFactory.java new file mode 100644 index 00000000..651e9743 --- /dev/null +++ b/cb-tools/java-source/src/main/java/org/java_websocket/WebSocketFactory.java @@ -0,0 +1,12 @@ +package org.java_websocket; + +import java.net.Socket; +import java.util.List; + +import org.java_websocket.drafts.Draft; + +public interface WebSocketFactory { + public WebSocket createWebSocket( WebSocketAdapter a, Draft d, Socket s ); + public WebSocket createWebSocket( WebSocketAdapter a, List<Draft> drafts, Socket s ); + +} diff --git a/cb-tools/java-source/src/main/java/org/java_websocket/WebSocketImpl.java b/cb-tools/java-source/src/main/java/org/java_websocket/WebSocketImpl.java new file mode 100644 index 00000000..669bee14 --- /dev/null +++ b/cb-tools/java-source/src/main/java/org/java_websocket/WebSocketImpl.java @@ -0,0 +1,737 @@ +package org.java_websocket; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.nio.ByteBuffer; +import java.nio.channels.ByteChannel; +import java.nio.channels.NotYetConnectedException; +import java.nio.channels.SelectionKey; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + +import org.java_websocket.drafts.Draft; +import org.java_websocket.drafts.Draft.CloseHandshakeType; +import org.java_websocket.drafts.Draft.HandshakeState; +import org.java_websocket.drafts.Draft_10; +import org.java_websocket.drafts.Draft_17; +import org.java_websocket.drafts.Draft_75; +import org.java_websocket.drafts.Draft_76; +import org.java_websocket.exceptions.IncompleteHandshakeException; +import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.exceptions.InvalidHandshakeException; +import org.java_websocket.exceptions.WebsocketNotConnectedException; +import org.java_websocket.framing.CloseFrame; +import org.java_websocket.framing.CloseFrameBuilder; +import org.java_websocket.framing.Framedata; +import org.java_websocket.framing.Framedata.Opcode; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.ClientHandshakeBuilder; +import org.java_websocket.handshake.Handshakedata; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.handshake.ServerHandshakeBuilder; +import org.java_websocket.server.WebSocketServer.WebSocketWorker; +import org.java_websocket.util.Charsetfunctions; + +/** + * Represents one end (client or server) of a single WebSocketImpl connection. + * Takes care of the "handshake" phase, then allows for easy sending of + * text frames, and receiving frames through an event-based model. + * + */ +public class WebSocketImpl implements WebSocket { + + public static int RCVBUF = 16384; + + public static/*final*/boolean DEBUG = false; // must be final in the future in order to take advantage of VM optimization + + public static final List<Draft> defaultdraftlist = new ArrayList<Draft>( 4 ); + static { + defaultdraftlist.add( new Draft_17() ); + defaultdraftlist.add( new Draft_10() ); + defaultdraftlist.add( new Draft_76() ); + defaultdraftlist.add( new Draft_75() ); + } + + public SelectionKey key; + + /** the possibly wrapped channel object whose selection is controlled by {@link #key} */ + public ByteChannel channel; + /** + * Queue of buffers that need to be sent to the client. + */ + public final BlockingQueue<ByteBuffer> outQueue; + /** + * Queue of buffers that need to be processed + */ + public final BlockingQueue<ByteBuffer> inQueue; + + /** + * Helper variable meant to store the thread which ( exclusively ) triggers this objects decode method. + **/ + public volatile WebSocketWorker workerThread; // TODO reset worker? + + /** When true no further frames may be submitted to be sent */ + private volatile boolean flushandclosestate = false; + + private READYSTATE readystate = READYSTATE.NOT_YET_CONNECTED; + + /** + * The listener to notify of WebSocket events. + */ + private final WebSocketListener wsl; + + private List<Draft> knownDrafts; + + private Draft draft = null; + + private Role role; + + private Opcode current_continuous_frame_opcode = null; + + /** the bytes of an incomplete received handshake */ + private ByteBuffer tmpHandshakeBytes = ByteBuffer.allocate( 0 ); + + /** stores the handshake sent by this websocket ( Role.CLIENT only ) */ + private ClientHandshake handshakerequest = null; + + private String closemessage = null; + private Integer closecode = null; + private Boolean closedremotely = null; + + private String resourceDescriptor = null; + + /** + * crates a websocket with server role + */ + public WebSocketImpl( WebSocketListener listener , List<Draft> drafts ) { + this( listener, (Draft) null ); + this.role = Role.SERVER; + // draft.copyInstance will be called when the draft is first needed + if( drafts == null || drafts.isEmpty() ) { + knownDrafts = defaultdraftlist; + } else { + knownDrafts = drafts; + } + } + + /** + * crates a websocket with client role + * + * @param socket + * may be unbound + */ + public WebSocketImpl( WebSocketListener listener , Draft draft ) { + if( listener == null || ( draft == null && role == Role.SERVER ) )// socket can be null because we want do be able to create the object without already having a bound channel + throw new IllegalArgumentException( "parameters must not be null" ); + this.outQueue = new LinkedBlockingQueue<ByteBuffer>(); + inQueue = new LinkedBlockingQueue<ByteBuffer>(); + this.wsl = listener; + this.role = Role.CLIENT; + if( draft != null ) + this.draft = draft.copyInstance(); + } + + @Deprecated + public WebSocketImpl( WebSocketListener listener , Draft draft , Socket socket ) { + this( listener, draft ); + } + + @Deprecated + public WebSocketImpl( WebSocketListener listener , List<Draft> drafts , Socket socket ) { + this( listener, drafts ); + } + + /** + * + */ + public void decode( ByteBuffer socketBuffer ) { + assert ( socketBuffer.hasRemaining() ); + + if( DEBUG ) + System.out.println( "process(" + socketBuffer.remaining() + "): {" + ( socketBuffer.remaining() > 1000 ? "too big to display" : new String( socketBuffer.array(), socketBuffer.position(), socketBuffer.remaining() ) ) + "}" ); + + if( readystate != READYSTATE.NOT_YET_CONNECTED ) { + decodeFrames( socketBuffer );; + } else { + if( decodeHandshake( socketBuffer ) ) { + assert ( tmpHandshakeBytes.hasRemaining() != socketBuffer.hasRemaining() || !socketBuffer.hasRemaining() ); // the buffers will never have remaining bytes at the same time + + if( socketBuffer.hasRemaining() ) { + decodeFrames( socketBuffer ); + } else if( tmpHandshakeBytes.hasRemaining() ) { + decodeFrames( tmpHandshakeBytes ); + } + } + } + assert ( isClosing() || isFlushAndClose() || !socketBuffer.hasRemaining() ); + } + /** + * Returns whether the handshake phase has is completed. + * In case of a broken handshake this will be never the case. + **/ + private boolean decodeHandshake( ByteBuffer socketBufferNew ) { + ByteBuffer socketBuffer; + if( tmpHandshakeBytes.capacity() == 0 ) { + socketBuffer = socketBufferNew; + } else { + if( tmpHandshakeBytes.remaining() < socketBufferNew.remaining() ) { + ByteBuffer buf = ByteBuffer.allocate( tmpHandshakeBytes.capacity() + socketBufferNew.remaining() ); + tmpHandshakeBytes.flip(); + buf.put( tmpHandshakeBytes ); + tmpHandshakeBytes = buf; + } + + tmpHandshakeBytes.put( socketBufferNew ); + tmpHandshakeBytes.flip(); + socketBuffer = tmpHandshakeBytes; + } + socketBuffer.mark(); + try { + if( draft == null ) { + HandshakeState isflashedgecase = isFlashEdgeCase( socketBuffer ); + if( isflashedgecase == HandshakeState.MATCHED ) { + try { + write( ByteBuffer.wrap( Charsetfunctions.utf8Bytes( wsl.getFlashPolicy( this ) ) ) ); + close( CloseFrame.FLASHPOLICY, "" ); + } catch ( InvalidDataException e ) { + close( CloseFrame.ABNORMAL_CLOSE, "remote peer closed connection before flashpolicy could be transmitted", true ); + } + return false; + } + } + HandshakeState handshakestate = null; + + try { + if( role == Role.SERVER ) { + if( draft == null ) { + for( Draft d : knownDrafts ) { + d = d.copyInstance(); + try { + d.setParseMode( role ); + socketBuffer.reset(); + Handshakedata tmphandshake = d.translateHandshake( socketBuffer ); + if( tmphandshake instanceof ClientHandshake == false ) { + flushAndClose( CloseFrame.PROTOCOL_ERROR, "wrong http function", false ); + return false; + } + ClientHandshake handshake = (ClientHandshake) tmphandshake; + handshakestate = d.acceptHandshakeAsServer( handshake ); + if( handshakestate == HandshakeState.MATCHED ) { + resourceDescriptor = handshake.getResourceDescriptor(); + ServerHandshakeBuilder response; + try { + response = wsl.onWebsocketHandshakeReceivedAsServer( this, d, handshake ); + } catch ( InvalidDataException e ) { + flushAndClose( e.getCloseCode(), e.getMessage(), false ); + return false; + } catch ( RuntimeException e ) { + wsl.onWebsocketError( this, e ); + flushAndClose( CloseFrame.NEVER_CONNECTED, e.getMessage(), false ); + return false; + } + write( d.createHandshake( d.postProcessHandshakeResponseAsServer( handshake, response ), role ) ); + draft = d; + open( handshake ); + return true; + } + } catch ( InvalidHandshakeException e ) { + // go on with an other draft + } + } + if( draft == null ) { + close( CloseFrame.PROTOCOL_ERROR, "no draft matches" ); + } + return false; + } else { + // special case for multiple step handshakes + Handshakedata tmphandshake = draft.translateHandshake( socketBuffer ); + if( tmphandshake instanceof ClientHandshake == false ) { + flushAndClose( CloseFrame.PROTOCOL_ERROR, "wrong http function", false ); + return false; + } + ClientHandshake handshake = (ClientHandshake) tmphandshake; + handshakestate = draft.acceptHandshakeAsServer( handshake ); + + if( handshakestate == HandshakeState.MATCHED ) { + open( handshake ); + return true; + } else { + close( CloseFrame.PROTOCOL_ERROR, "the handshake did finaly not match" ); + } + return false; + } + } else if( role == Role.CLIENT ) { + draft.setParseMode( role ); + Handshakedata tmphandshake = draft.translateHandshake( socketBuffer ); + if( tmphandshake instanceof ServerHandshake == false ) { + flushAndClose( CloseFrame.PROTOCOL_ERROR, "wrong http function", false ); + return false; + } + ServerHandshake handshake = (ServerHandshake) tmphandshake; + handshakestate = draft.acceptHandshakeAsClient( handshakerequest, handshake ); + if( handshakestate == HandshakeState.MATCHED ) { + try { + wsl.onWebsocketHandshakeReceivedAsClient( this, handshakerequest, handshake ); + } catch ( InvalidDataException e ) { + flushAndClose( e.getCloseCode(), e.getMessage(), false ); + return false; + } catch ( RuntimeException e ) { + wsl.onWebsocketError( this, e ); + flushAndClose( CloseFrame.NEVER_CONNECTED, e.getMessage(), false ); + return false; + } + open( handshake ); + return true; + } else { + close( CloseFrame.PROTOCOL_ERROR, "draft " + draft + " refuses handshake" ); + } + } + } catch ( InvalidHandshakeException e ) { + close( e ); + } + } catch ( IncompleteHandshakeException e ) { + if( tmpHandshakeBytes.capacity() == 0 ) { + socketBuffer.reset(); + int newsize = e.getPreferedSize(); + if( newsize == 0 ) { + newsize = socketBuffer.capacity() + 16; + } else { + assert ( e.getPreferedSize() >= socketBuffer.remaining() ); + } + tmpHandshakeBytes = ByteBuffer.allocate( newsize ); + + tmpHandshakeBytes.put( socketBufferNew ); + // tmpHandshakeBytes.flip(); + } else { + tmpHandshakeBytes.position( tmpHandshakeBytes.limit() ); + tmpHandshakeBytes.limit( tmpHandshakeBytes.capacity() ); + } + } + return false; + } + + private void decodeFrames( ByteBuffer socketBuffer ) { + + List<Framedata> frames; + try { + frames = draft.translateFrame( socketBuffer ); + for( Framedata f : frames ) { + if( DEBUG ) + System.out.println( "matched frame: " + f ); + Opcode curop = f.getOpcode(); + boolean fin = f.isFin(); + + if( curop == Opcode.CLOSING ) { + int code = CloseFrame.NOCODE; + String reason = ""; + if( f instanceof CloseFrame ) { + CloseFrame cf = (CloseFrame) f; + code = cf.getCloseCode(); + reason = cf.getMessage(); + } + if( readystate == READYSTATE.CLOSING ) { + // complete the close handshake by disconnecting + closeConnection( code, reason, true ); + } else { + // echo close handshake + if( draft.getCloseHandshakeType() == CloseHandshakeType.TWOWAY ) + close( code, reason, true ); + else + flushAndClose( code, reason, false ); + } + continue; + } else if( curop == Opcode.PING ) { + wsl.onWebsocketPing( this, f ); + continue; + } else if( curop == Opcode.PONG ) { + wsl.onWebsocketPong( this, f ); + continue; + } else if( !fin || curop == Opcode.CONTINUOUS ) { + if( curop != Opcode.CONTINUOUS ) { + if( current_continuous_frame_opcode != null ) + throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "Previous continuous frame sequence not completed." ); + current_continuous_frame_opcode = curop; + } else if( fin ) { + if( current_continuous_frame_opcode == null ) + throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "Continuous frame sequence was not started." ); + current_continuous_frame_opcode = null; + } else if( current_continuous_frame_opcode == null ) { + throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "Continuous frame sequence was not started." ); + } + try { + wsl.onWebsocketMessageFragment( this, f ); + } catch ( RuntimeException e ) { + wsl.onWebsocketError( this, e ); + } + + } else if( current_continuous_frame_opcode != null ) { + throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "Continuous frame sequence not completed." ); + } else if( curop == Opcode.TEXT ) { + try { + wsl.onWebsocketMessage( this, Charsetfunctions.stringUtf8( f.getPayloadData() ) ); + } catch ( RuntimeException e ) { + wsl.onWebsocketError( this, e ); + } + } else if( curop == Opcode.BINARY ) { + try { + wsl.onWebsocketMessage( this, f.getPayloadData() ); + } catch ( RuntimeException e ) { + wsl.onWebsocketError( this, e ); + } + } else { + throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "non control or continious frame expected" ); + } + } + } catch ( InvalidDataException e1 ) { + wsl.onWebsocketError( this, e1 ); + close( e1 ); + return; + } + } + + private void close( int code, String message, boolean remote ) { + if( readystate != READYSTATE.CLOSING && readystate != READYSTATE.CLOSED ) { + if( readystate == READYSTATE.OPEN ) { + if( code == CloseFrame.ABNORMAL_CLOSE ) { + assert ( remote == false ); + readystate = READYSTATE.CLOSING; + flushAndClose( code, message, false ); + return; + } + if( draft.getCloseHandshakeType() != CloseHandshakeType.NONE ) { + try { + if( !remote ) { + try { + wsl.onWebsocketCloseInitiated( this, code, message ); + } catch ( RuntimeException e ) { + wsl.onWebsocketError( this, e ); + } + } + sendFrame( new CloseFrameBuilder( code, message ) ); + } catch ( InvalidDataException e ) { + wsl.onWebsocketError( this, e ); + flushAndClose( CloseFrame.ABNORMAL_CLOSE, "generated frame is invalid", false ); + } + } + flushAndClose( code, message, remote ); + } else if( code == CloseFrame.FLASHPOLICY ) { + assert ( remote ); + flushAndClose( CloseFrame.FLASHPOLICY, message, true ); + } else { + flushAndClose( CloseFrame.NEVER_CONNECTED, message, false ); + } + if( code == CloseFrame.PROTOCOL_ERROR )// this endpoint found a PROTOCOL_ERROR + flushAndClose( code, message, remote ); + readystate = READYSTATE.CLOSING; + tmpHandshakeBytes = null; + return; + } + } + + @Override + public void close( int code, String message ) { + close( code, message, false ); + } + + /** + * + * @param remote + * Indicates who "generated" <code>code</code>.<br> + * <code>true</code> means that this endpoint received the <code>code</code> from the other endpoint.<br> + * false means this endpoint decided to send the given code,<br> + * <code>remote</code> may also be true if this endpoint started the closing handshake since the other endpoint may not simply echo the <code>code</code> but close the connection the same time this endpoint does do but with an other <code>code</code>. <br> + **/ + + protected synchronized void closeConnection( int code, String message, boolean remote ) { + if( readystate == READYSTATE.CLOSED ) { + return; + } + + if( key != null ) { + // key.attach( null ); //see issue #114 + key.cancel(); + } + if( channel != null ) { + try { + channel.close(); + } catch ( IOException e ) { + wsl.onWebsocketError( this, e ); + } + } + try { + this.wsl.onWebsocketClose( this, code, message, remote ); + } catch ( RuntimeException e ) { + wsl.onWebsocketError( this, e ); + } + if( draft != null ) + draft.reset(); + handshakerequest = null; + + readystate = READYSTATE.CLOSED; + this.outQueue.clear(); + } + + protected void closeConnection( int code, boolean remote ) { + closeConnection( code, "", remote ); + } + + public void closeConnection() { + if( closedremotely == null ) { + throw new IllegalStateException( "this method must be used in conjuction with flushAndClose" ); + } + closeConnection( closecode, closemessage, closedremotely ); + } + + public void closeConnection( int code, String message ) { + closeConnection( code, message, false ); + } + + protected synchronized void flushAndClose( int code, String message, boolean remote ) { + if( flushandclosestate ) { + return; + } + closecode = code; + closemessage = message; + closedremotely = remote; + + flushandclosestate = true; + + wsl.onWriteDemand( this ); // ensures that all outgoing frames are flushed before closing the connection + try { + wsl.onWebsocketClosing( this, code, message, remote ); + } catch ( RuntimeException e ) { + wsl.onWebsocketError( this, e ); + } + if( draft != null ) + draft.reset(); + handshakerequest = null; + } + + public void eot() { + if( getReadyState() == READYSTATE.NOT_YET_CONNECTED ) { + closeConnection( CloseFrame.NEVER_CONNECTED, true ); + } else if( flushandclosestate ) { + closeConnection( closecode, closemessage, closedremotely ); + } else if( draft.getCloseHandshakeType() == CloseHandshakeType.NONE ) { + closeConnection( CloseFrame.NORMAL, true ); + } else if( draft.getCloseHandshakeType() == CloseHandshakeType.ONEWAY ) { + if( role == Role.SERVER ) + closeConnection( CloseFrame.ABNORMAL_CLOSE, true ); + else + closeConnection( CloseFrame.NORMAL, true ); + } else { + closeConnection( CloseFrame.ABNORMAL_CLOSE, true ); + } + } + + @Override + public void close( int code ) { + close( code, "", false ); + } + + public void close( InvalidDataException e ) { + close( e.getCloseCode(), e.getMessage(), false ); + } + + /** + * Send Text data to the other end. + * + * @throws IllegalArgumentException + * @throws NotYetConnectedException + */ + @Override + public void send( String text ) throws WebsocketNotConnectedException { + if( text == null ) + throw new IllegalArgumentException( "Cannot send 'null' data to a WebSocketImpl." ); + send( draft.createFrames( text, role == Role.CLIENT ) ); + } + + /** + * Send Binary data (plain bytes) to the other end. + * + * @throws IllegalArgumentException + * @throws NotYetConnectedException + */ + @Override + public void send( ByteBuffer bytes ) throws IllegalArgumentException , WebsocketNotConnectedException { + if( bytes == null ) + throw new IllegalArgumentException( "Cannot send 'null' data to a WebSocketImpl." ); + send( draft.createFrames( bytes, role == Role.CLIENT ) ); + } + + @Override + public void send( byte[] bytes ) throws IllegalArgumentException , WebsocketNotConnectedException { + send( ByteBuffer.wrap( bytes ) ); + } + + private void send( Collection<Framedata> frames ) { + if( !isOpen() ) + throw new WebsocketNotConnectedException(); + for( Framedata f : frames ) { + sendFrame( f ); + } + } + + @Override + public void sendFragmentedFrame( Opcode op, ByteBuffer buffer, boolean fin ) { + send( draft.continuousFrame( op, buffer, fin ) ); + } + + @Override + public void sendFrame( Framedata framedata ) { + if( DEBUG ) + System.out.println( "send frame: " + framedata ); + write( draft.createBinaryFrame( framedata ) ); + } + + @Override + public boolean hasBufferedData() { + return !this.outQueue.isEmpty(); + } + + private HandshakeState isFlashEdgeCase( ByteBuffer request ) throws IncompleteHandshakeException { + request.mark(); + if( request.limit() > Draft.FLASH_POLICY_REQUEST.length ) { + return HandshakeState.NOT_MATCHED; + } else if( request.limit() < Draft.FLASH_POLICY_REQUEST.length ) { + throw new IncompleteHandshakeException( Draft.FLASH_POLICY_REQUEST.length ); + } else { + + for( int flash_policy_index = 0 ; request.hasRemaining() ; flash_policy_index++ ) { + if( Draft.FLASH_POLICY_REQUEST[ flash_policy_index ] != request.get() ) { + request.reset(); + return HandshakeState.NOT_MATCHED; + } + } + return HandshakeState.MATCHED; + } + } + + public void startHandshake( ClientHandshakeBuilder handshakedata ) throws InvalidHandshakeException { + assert ( readystate != READYSTATE.CONNECTING ) : "shall only be called once"; + + // Store the Handshake Request we are about to send + this.handshakerequest = draft.postProcessHandshakeRequestAsClient( handshakedata ); + + resourceDescriptor = handshakedata.getResourceDescriptor(); + assert( resourceDescriptor != null ); + + // Notify Listener + try { + wsl.onWebsocketHandshakeSentAsClient( this, this.handshakerequest ); + } catch ( InvalidDataException e ) { + // Stop if the client code throws an exception + throw new InvalidHandshakeException( "Handshake data rejected by client." ); + } catch ( RuntimeException e ) { + wsl.onWebsocketError( this, e ); + throw new InvalidHandshakeException( "rejected because of" + e ); + } + + // Send + write( draft.createHandshake( this.handshakerequest, role ) ); + } + + private void write( ByteBuffer buf ) { + if( DEBUG ) + System.out.println( "write(" + buf.remaining() + "): {" + ( buf.remaining() > 1000 ? "too big to display" : new String( buf.array() ) ) + "}" ); + + outQueue.add( buf ); + /*try { + outQueue.put( buf ); + } catch ( InterruptedException e ) { + write( buf ); + Thread.currentThread().interrupt(); // keep the interrupted status + e.printStackTrace(); + }*/ + wsl.onWriteDemand( this ); + } + + private void write( List<ByteBuffer> bufs ) { + for( ByteBuffer b : bufs ) { + write( b ); + } + } + + private void open( Handshakedata d ) { + if( DEBUG ) + System.out.println( "open using draft: " + draft.getClass().getSimpleName() ); + readystate = READYSTATE.OPEN; + try { + wsl.onWebsocketOpen( this, d ); + } catch ( RuntimeException e ) { + wsl.onWebsocketError( this, e ); + } + } + + @Override + public boolean isConnecting() { + assert ( flushandclosestate ? readystate == READYSTATE.CONNECTING : true ); + return readystate == READYSTATE.CONNECTING; // ifflushandclosestate + } + + @Override + public boolean isOpen() { + assert ( readystate == READYSTATE.OPEN ? !flushandclosestate : true ); + return readystate == READYSTATE.OPEN; + } + + @Override + public boolean isClosing() { + return readystate == READYSTATE.CLOSING; + } + + @Override + public boolean isFlushAndClose() { + return flushandclosestate; + } + + @Override + public boolean isClosed() { + return readystate == READYSTATE.CLOSED; + } + + @Override + public READYSTATE getReadyState() { + return readystate; + } + + @Override + public int hashCode() { + return super.hashCode(); + } + + @Override + public String toString() { + return super.toString(); // its nice to be able to set breakpoints here + } + + @Override + public InetSocketAddress getRemoteSocketAddress() { + return wsl.getRemoteSocketAddress( this ); + } + + @Override + public InetSocketAddress getLocalSocketAddress() { + return wsl.getLocalSocketAddress( this ); + } + + @Override + public Draft getDraft() { + return draft; + } + + @Override + public void close() { + close( CloseFrame.NORMAL ); + } + + @Override + public String getResourceDescriptor() { + return resourceDescriptor; + } + +} diff --git a/cb-tools/java-source/src/main/java/org/java_websocket/WebSocketListener.java b/cb-tools/java-source/src/main/java/org/java_websocket/WebSocketListener.java new file mode 100644 index 00000000..93478d94 --- /dev/null +++ b/cb-tools/java-source/src/main/java/org/java_websocket/WebSocketListener.java @@ -0,0 +1,151 @@ +package org.java_websocket; + +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; + +import org.java_websocket.drafts.Draft; +import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.framing.Framedata; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.Handshakedata; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.handshake.ServerHandshakeBuilder; + +/** + * Implemented by <tt>WebSocketClient</tt> and <tt>WebSocketServer</tt>. + * The methods within are called by <tt>WebSocket</tt>. + * Almost every method takes a first parameter conn which represents the source of the respective event. + */ +public interface WebSocketListener { + + /** + * Called on the server side when the socket connection is first established, and the WebSocket + * handshake has been received. This method allows to deny connections based on the received handshake.<br> + * By default this method only requires protocol compliance. + * + * @param conn + * The WebSocket related to this event + * @param draft + * The protocol draft the client uses to connect + * @param request + * The opening http message send by the client. Can be used to access additional fields like cookies. + * @return Returns an incomplete handshake containing all optional fields + * @throws InvalidDataException + * Throwing this exception will cause this handshake to be rejected + */ + public ServerHandshakeBuilder onWebsocketHandshakeReceivedAsServer( WebSocket conn, Draft draft, ClientHandshake request ) throws InvalidDataException; + + /** + * Called on the client side when the socket connection is first established, and the WebSocketImpl + * handshake response has been received. + * + * @param conn + * The WebSocket related to this event + * @param request + * The handshake initially send out to the server by this websocket. + * @param response + * The handshake the server sent in response to the request. + * @throws InvalidDataException + * Allows the client to reject the connection with the server in respect of its handshake response. + */ + public void onWebsocketHandshakeReceivedAsClient( WebSocket conn, ClientHandshake request, ServerHandshake response ) throws InvalidDataException; + + /** + * Called on the client side when the socket connection is first established, and the WebSocketImpl + * handshake has just been sent. + * + * @param conn + * The WebSocket related to this event + * @param request + * The handshake sent to the server by this websocket + * @throws InvalidDataException + * Allows the client to stop the connection from progressing + */ + public void onWebsocketHandshakeSentAsClient( WebSocket conn, ClientHandshake request ) throws InvalidDataException; + + /** + * Called when an entire text frame has been received. Do whatever you want + * here... + * + * @param conn + * The <tt>WebSocket</tt> instance this event is occurring on. + * @param message + * The UTF-8 decoded message that was received. + */ + public void onWebsocketMessage( WebSocket conn, String message ); + + /** + * Called when an entire binary frame has been received. Do whatever you want + * here... + * + * @param conn + * The <tt>WebSocket</tt> instance this event is occurring on. + * @param blob + * The binary message that was received. + */ + public void onWebsocketMessage( WebSocket conn, ByteBuffer blob ); + + public void onWebsocketMessageFragment( WebSocket conn, Framedata frame ); + + /** + * Called after <var>onHandshakeReceived</var> returns <var>true</var>. + * Indicates that a complete WebSocket connection has been established, + * and we are ready to send/receive data. + * + * @param conn + * The <tt>WebSocket</tt> instance this event is occuring on. + */ + public void onWebsocketOpen( WebSocket conn, Handshakedata d ); + + /** + * Called after <tt>WebSocket#close</tt> is explicity called, or when the + * other end of the WebSocket connection is closed. + * + * @param conn + * The <tt>WebSocket</tt> instance this event is occuring on. + */ + public void onWebsocketClose( WebSocket ws, int code, String reason, boolean remote ); + + /** called as soon as no further frames are accepted */ + public void onWebsocketClosing( WebSocket ws, int code, String reason, boolean remote ); + + /** send when this peer sends a close handshake */ + public void onWebsocketCloseInitiated( WebSocket ws, int code, String reason ); + + /** + * Called if an exception worth noting occurred. + * If an error causes the connection to fail onClose will be called additionally afterwards. + * + * @param ex + * The exception that occurred. <br> + * Might be null if the exception is not related to any specific connection. For example if the server port could not be bound. + */ + public void onWebsocketError( WebSocket conn, Exception ex ); + + /** + * Called a ping frame has been received. + * This method must send a corresponding pong by itself. + * + * @param f + * The ping frame. Control frames may contain payload. + */ + public void onWebsocketPing( WebSocket conn, Framedata f ); + + /** + * Called when a pong frame is received. + **/ + public void onWebsocketPong( WebSocket conn, Framedata f ); + + /** + * Gets the XML string that should be returned if a client requests a Flash + * security policy. + * @throws InvalidDataException thrown when some data that is required to generate the flash-policy like the websocket local port could not be obtained. + */ + public String getFlashPolicy( WebSocket conn ) throws InvalidDataException; + + /** This method is used to inform the selector thread that there is data queued to be written to the socket. */ + public void onWriteDemand( WebSocket conn ); + + public InetSocketAddress getLocalSocketAddress( WebSocket conn ); + public InetSocketAddress getRemoteSocketAddress( WebSocket conn ); +} diff --git a/cb-tools/java-source/src/main/java/org/java_websocket/WrappedByteChannel.java b/cb-tools/java-source/src/main/java/org/java_websocket/WrappedByteChannel.java new file mode 100644 index 00000000..83a3290b --- /dev/null +++ b/cb-tools/java-source/src/main/java/org/java_websocket/WrappedByteChannel.java @@ -0,0 +1,26 @@ +package org.java_websocket; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.ByteChannel; + +import javax.net.ssl.SSLException; + +public interface WrappedByteChannel extends ByteChannel { + public boolean isNeedWrite(); + public void writeMore() throws IOException; + + /** + * returns whether readMore should be called to fetch data which has been decoded but not yet been returned. + * + * @see #read(ByteBuffer) + * @see #readMore(ByteBuffer) + **/ + public boolean isNeedRead(); + /** + * This function does not read data from the underlying channel at all. It is just a way to fetch data which has already be received or decoded but was but was not yet returned to the user. + * This could be the case when the decoded data did not fit into the buffer the user passed to {@link #read(ByteBuffer)}. + **/ + public int readMore( ByteBuffer dst ) throws SSLException; + public boolean isBlocking(); +} diff --git a/cb-tools/java-source/src/main/java/org/java_websocket/client/AbstractClientProxyChannel.java b/cb-tools/java-source/src/main/java/org/java_websocket/client/AbstractClientProxyChannel.java new file mode 100644 index 00000000..bbac6725 --- /dev/null +++ b/cb-tools/java-source/src/main/java/org/java_websocket/client/AbstractClientProxyChannel.java @@ -0,0 +1,38 @@ +package org.java_websocket.client; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; +import java.nio.channels.ByteChannel; + +import org.java_websocket.AbstractWrappedByteChannel; + +public abstract class AbstractClientProxyChannel extends AbstractWrappedByteChannel { + protected final ByteBuffer proxyHandshake; + + + /** + * @param towrap + * The channel to the proxy server + **/ + public AbstractClientProxyChannel( ByteChannel towrap ) { + super( towrap ); + try { + proxyHandshake = ByteBuffer.wrap( buildHandShake().getBytes( "ASCII" ) ); + } catch ( UnsupportedEncodingException e ) { + throw new RuntimeException( e ); + } + } + + @Override + public int write( ByteBuffer src ) throws IOException { + if( !proxyHandshake.hasRemaining() ) { + return super.write( src ); + } else { + return super.write( proxyHandshake ); + } + } + + public abstract String buildHandShake(); + +} diff --git a/cb-tools/java-source/src/main/java/org/java_websocket/client/WebSocketClient.java b/cb-tools/java-source/src/main/java/org/java_websocket/client/WebSocketClient.java new file mode 100644 index 00000000..86eff794 --- /dev/null +++ b/cb-tools/java-source/src/main/java/org/java_websocket/client/WebSocketClient.java @@ -0,0 +1,454 @@ +package org.java_websocket.client; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.Socket; +import java.net.URI; +import java.nio.ByteBuffer; +import java.nio.channels.NotYetConnectedException; +import java.util.Map; +import java.util.concurrent.CountDownLatch; + +import org.java_websocket.WebSocket; +import org.java_websocket.WebSocketAdapter; +import org.java_websocket.WebSocketImpl; +import org.java_websocket.drafts.Draft; +import org.java_websocket.drafts.Draft_17; +import org.java_websocket.exceptions.InvalidHandshakeException; +import org.java_websocket.framing.CloseFrame; +import org.java_websocket.framing.Framedata; +import org.java_websocket.framing.Framedata.Opcode; +import org.java_websocket.handshake.HandshakeImpl1Client; +import org.java_websocket.handshake.Handshakedata; +import org.java_websocket.handshake.ServerHandshake; + +/** + * A subclass must implement at least <var>onOpen</var>, <var>onClose</var>, and <var>onMessage</var> to be + * useful. At runtime the user is expected to establish a connection via {@link #connect()}, then receive events like {@link #onMessage(String)} via the overloaded methods and to {@link #send(String)} data to the server. + */ +public abstract class WebSocketClient extends WebSocketAdapter implements Runnable, WebSocket { + + /** + * The URI this channel is supposed to connect to. + */ + protected URI uri = null; + + private WebSocketImpl engine = null; + + private Socket socket = null; + + private InputStream istream; + + private OutputStream ostream; + + private Proxy proxy = Proxy.NO_PROXY; + + private Thread writeThread; + + private Draft draft; + + private Map<String,String> headers; + + private CountDownLatch connectLatch = new CountDownLatch( 1 ); + + private CountDownLatch closeLatch = new CountDownLatch( 1 ); + + private int connectTimeout = 0; + + /** This open a websocket connection as specified by rfc6455 */ + public WebSocketClient( URI serverURI ) { + this( serverURI, new Draft_17() ); + } + + /** + * Constructs a WebSocketClient instance and sets it to the connect to the + * specified URI. The channel does not attampt to connect automatically. The connection + * will be established once you call <var>connect</var>. + */ + public WebSocketClient( URI serverUri , Draft draft ) { + this( serverUri, draft, null, 0 ); + } + + public WebSocketClient( URI serverUri , Draft protocolDraft , Map<String,String> httpHeaders , int connectTimeout ) { + if( serverUri == null ) { + throw new IllegalArgumentException(); + } else if( protocolDraft == null ) { + throw new IllegalArgumentException( "null as draft is permitted for `WebSocketServer` only!" ); + } + this.uri = serverUri; + this.draft = protocolDraft; + this.headers = httpHeaders; + this.connectTimeout = connectTimeout; + this.engine = new WebSocketImpl( this, protocolDraft ); + } + + /** + * Returns the URI that this WebSocketClient is connected to. + */ + public URI getURI() { + return uri; + } + + /** + * Returns the protocol version this channel uses.<br> + * For more infos see https://github.com/TooTallNate/Java-WebSocket/wiki/Drafts + */ + public Draft getDraft() { + return draft; + } + + /** + * Initiates the websocket connection. This method does not block. + */ + public void connect() { + if( writeThread != null ) + throw new IllegalStateException( "WebSocketClient objects are not reuseable" ); + writeThread = new Thread( this ); + writeThread.start(); + } + + /** + * Same as <code>connect</code> but blocks until the websocket connected or failed to do so.<br> + * Returns whether it succeeded or not. + **/ + public boolean connectBlocking() throws InterruptedException { + connect(); + connectLatch.await(); + return engine.isOpen(); + } + + /** + * Initiates the websocket close handshake. This method does not block<br> + * In oder to make sure the connection is closed use <code>closeBlocking</code> + */ + public void close() { + if( writeThread != null ) { + engine.close( CloseFrame.NORMAL ); + } + } + + public void closeBlocking() throws InterruptedException { + close(); + closeLatch.await(); + } + + /** + * Sends <var>text</var> to the connected websocket server. + * + * @param text + * The string which will be transmitted. + */ + public void send( String text ) throws NotYetConnectedException { + engine.send( text ); + } + + /** + * Sends binary <var> data</var> to the connected webSocket server. + * + * @param data + * The byte-Array of data to send to the WebSocket server. + */ + public void send( byte[] data ) throws NotYetConnectedException { + engine.send( data ); + } + + public void run() { + try { + if( socket == null ) { + socket = new Socket( proxy ); + } else if( socket.isClosed() ) { + throw new IOException(); + } + if( !socket.isBound() ) + socket.connect( new InetSocketAddress( uri.getHost(), getPort() ), connectTimeout ); + istream = socket.getInputStream(); + ostream = socket.getOutputStream(); + + sendHandshake(); + } catch ( /*IOException | SecurityException | UnresolvedAddressException | InvalidHandshakeException | ClosedByInterruptException | SocketTimeoutException */Exception e ) { + onWebsocketError( engine, e ); + engine.closeConnection( CloseFrame.NEVER_CONNECTED, e.getMessage() ); + return; + } + + writeThread = new Thread( new WebsocketWriteThread() ); + writeThread.start(); + + byte[] rawbuffer = new byte[ WebSocketImpl.RCVBUF ]; + int readBytes; + + try { + while ( !isClosed() && ( readBytes = istream.read( rawbuffer ) ) != -1 ) { + engine.decode( ByteBuffer.wrap( rawbuffer, 0, readBytes ) ); + } + engine.eot(); + } catch ( IOException e ) { + engine.eot(); + } catch ( RuntimeException e ) { + // this catch case covers internal errors only and indicates a bug in this websocket implementation + onError( e ); + engine.closeConnection( CloseFrame.ABNORMAL_CLOSE, e.getMessage() ); + } + assert ( socket.isClosed() ); + } + private int getPort() { + int port = uri.getPort(); + if( port == -1 ) { + String scheme = uri.getScheme(); + if( scheme.equals( "wss" ) ) { + return WebSocket.DEFAULT_WSS_PORT; + } else if( scheme.equals( "ws" ) ) { + return WebSocket.DEFAULT_PORT; + } else { + throw new RuntimeException( "unkonow scheme" + scheme ); + } + } + return port; + } + + private void sendHandshake() throws InvalidHandshakeException { + String path; + String part1 = uri.getPath(); + String part2 = uri.getQuery(); + if( part1 == null || part1.length() == 0 ) + path = "/"; + else + path = part1; + if( part2 != null ) + path += "?" + part2; + int port = getPort(); + String host = uri.getHost() + ( port != WebSocket.DEFAULT_PORT ? ":" + port : "" ); + + HandshakeImpl1Client handshake = new HandshakeImpl1Client(); + handshake.setResourceDescriptor( path ); + handshake.put( "Host", host ); + if( headers != null ) { + for( Map.Entry<String,String> kv : headers.entrySet() ) { + handshake.put( kv.getKey(), kv.getValue() ); + } + } + engine.startHandshake( handshake ); + } + + /** + * This represents the state of the connection. + */ + public READYSTATE getReadyState() { + return engine.getReadyState(); + } + + /** + * Calls subclass' implementation of <var>onMessage</var>. + */ + @Override + public final void onWebsocketMessage( WebSocket conn, String message ) { + onMessage( message ); + } + + @Override + public final void onWebsocketMessage( WebSocket conn, ByteBuffer blob ) { + onMessage( blob ); + } + + @Override + public void onWebsocketMessageFragment( WebSocket conn, Framedata frame ) { + onFragment( frame ); + } + + /** + * Calls subclass' implementation of <var>onOpen</var>. + */ + @Override + public final void onWebsocketOpen( WebSocket conn, Handshakedata handshake ) { + connectLatch.countDown(); + onOpen( (ServerHandshake) handshake ); + } + + /** + * Calls subclass' implementation of <var>onClose</var>. + */ + @Override + public final void onWebsocketClose( WebSocket conn, int code, String reason, boolean remote ) { + connectLatch.countDown(); + closeLatch.countDown(); + if( writeThread != null ) + writeThread.interrupt(); + try { + if( socket != null ) + socket.close(); + } catch ( IOException e ) { + onWebsocketError( this, e ); + } + onClose( code, reason, remote ); + } + + /** + * Calls subclass' implementation of <var>onIOError</var>. + */ + @Override + public final void onWebsocketError( WebSocket conn, Exception ex ) { + onError( ex ); + } + + @Override + public final void onWriteDemand( WebSocket conn ) { + // nothing to do + } + + @Override + public void onWebsocketCloseInitiated( WebSocket conn, int code, String reason ) { + onCloseInitiated( code, reason ); + } + + @Override + public void onWebsocketClosing( WebSocket conn, int code, String reason, boolean remote ) { + onClosing( code, reason, remote ); + } + + public void onCloseInitiated( int code, String reason ) { + } + + public void onClosing( int code, String reason, boolean remote ) { + } + + public WebSocket getConnection() { + return engine; + } + + @Override + public InetSocketAddress getLocalSocketAddress( WebSocket conn ) { + if( socket != null ) + return (InetSocketAddress) socket.getLocalSocketAddress(); + return null; + } + + @Override + public InetSocketAddress getRemoteSocketAddress( WebSocket conn ) { + if( socket != null ) + return (InetSocketAddress) socket.getRemoteSocketAddress(); + return null; + } + + // ABTRACT METHODS ///////////////////////////////////////////////////////// + public abstract void onOpen( ServerHandshake handshakedata ); + public abstract void onMessage( String message ); + public abstract void onClose( int code, String reason, boolean remote ); + public abstract void onError( Exception ex ); + public void onMessage( ByteBuffer bytes ) { + } + public void onFragment( Framedata frame ) { + } + + private class WebsocketWriteThread implements Runnable { + @Override + public void run() { + Thread.currentThread().setName( "WebsocketWriteThread" ); + try { + while ( !Thread.interrupted() ) { + ByteBuffer buffer = engine.outQueue.take(); + ostream.write( buffer.array(), 0, buffer.limit() ); + ostream.flush(); + } + } catch ( IOException e ) { + engine.eot(); + } catch ( InterruptedException e ) { + // this thread is regularly terminated via an interrupt + } + } + } + + public void setProxy( Proxy proxy ) { + if( proxy == null ) + throw new IllegalArgumentException(); + this.proxy = proxy; + } + + /** + * Accepts bound and unbound sockets.<br> + * This method must be called before <code>connect</code>. + * If the given socket is not yet bound it will be bound to the uri specified in the constructor. + **/ + public void setSocket( Socket socket ) { + if( this.socket != null ) { + throw new IllegalStateException( "socket has already been set" ); + } + this.socket = socket; + } + + @Override + public void sendFragmentedFrame( Opcode op, ByteBuffer buffer, boolean fin ) { + engine.sendFragmentedFrame( op, buffer, fin ); + } + + @Override + public boolean isOpen() { + return engine.isOpen(); + } + + @Override + public boolean isFlushAndClose() { + return engine.isFlushAndClose(); + } + + @Override + public boolean isClosed() { + return engine.isClosed(); + } + + @Override + public boolean isClosing() { + return engine.isClosing(); + } + + @Override + public boolean isConnecting() { + return engine.isConnecting(); + } + + @Override + public boolean hasBufferedData() { + return engine.hasBufferedData(); + } + + @Override + public void close( int code ) { + engine.close(); + } + + @Override + public void close( int code, String message ) { + engine.close( code, message ); + } + + @Override + public void closeConnection( int code, String message ) { + engine.closeConnection( code, message ); + } + + @Override + public void send( ByteBuffer bytes ) throws IllegalArgumentException , NotYetConnectedException { + engine.send( bytes ); + } + + @Override + public void sendFrame( Framedata framedata ) { + engine.sendFrame( framedata ); + } + + @Override + public InetSocketAddress getLocalSocketAddress() { + return engine.getLocalSocketAddress(); + } + @Override + public InetSocketAddress getRemoteSocketAddress() { + return engine.getRemoteSocketAddress(); + } + + @Override + public String getResourceDescriptor() { + return uri.getPath(); + } +} diff --git a/cb-tools/java-source/src/main/java/org/java_websocket/drafts/Draft.java b/cb-tools/java-source/src/main/java/org/java_websocket/drafts/Draft.java new file mode 100644 index 00000000..65b34de8 --- /dev/null +++ b/cb-tools/java-source/src/main/java/org/java_websocket/drafts/Draft.java @@ -0,0 +1,228 @@ +package org.java_websocket.drafts; + +import java.nio.ByteBuffer; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; + +import org.java_websocket.WebSocket.Role; +import org.java_websocket.exceptions.IncompleteHandshakeException; +import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.exceptions.InvalidHandshakeException; +import org.java_websocket.exceptions.LimitExedeedException; +import org.java_websocket.framing.CloseFrame; +import org.java_websocket.framing.FrameBuilder; +import org.java_websocket.framing.Framedata; +import org.java_websocket.framing.Framedata.Opcode; +import org.java_websocket.framing.FramedataImpl1; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.ClientHandshakeBuilder; +import org.java_websocket.handshake.HandshakeBuilder; +import org.java_websocket.handshake.HandshakeImpl1Client; +import org.java_websocket.handshake.HandshakeImpl1Server; +import org.java_websocket.handshake.Handshakedata; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.handshake.ServerHandshakeBuilder; +import org.java_websocket.util.Charsetfunctions; + +/** + * Base class for everything of a websocket specification which is not common such as the way the handshake is read or frames are transfered. + **/ +public abstract class Draft { + + public enum HandshakeState { + /** Handshake matched this Draft successfully */ + MATCHED, + /** Handshake is does not match this Draft */ + NOT_MATCHED + } + public enum CloseHandshakeType { + NONE, ONEWAY, TWOWAY + } + + public static int MAX_FAME_SIZE = 1000 * 1; + public static int INITIAL_FAMESIZE = 64; + + public static final byte[] FLASH_POLICY_REQUEST = Charsetfunctions.utf8Bytes( "<policy-file-request/>\0" ); + + /** In some cases the handshake will be parsed different depending on whether */ + protected Role role = null; + + protected Opcode continuousFrameType = null; + + public static ByteBuffer readLine( ByteBuffer buf ) { + ByteBuffer sbuf = ByteBuffer.allocate( buf.remaining() ); + byte prev = '0'; + byte cur = '0'; + while ( buf.hasRemaining() ) { + prev = cur; + cur = buf.get(); + sbuf.put( cur ); + if( prev == (byte) '\r' && cur == (byte) '\n' ) { + sbuf.limit( sbuf.position() - 2 ); + sbuf.position( 0 ); + return sbuf; + + } + } + // ensure that there wont be any bytes skipped + buf.position( buf.position() - sbuf.position() ); + return null; + } + + public static String readStringLine( ByteBuffer buf ) { + ByteBuffer b = readLine( buf ); + return b == null ? null : Charsetfunctions.stringAscii( b.array(), 0, b.limit() ); + } + + public static HandshakeBuilder translateHandshakeHttp( ByteBuffer buf, Role role ) throws InvalidHandshakeException , IncompleteHandshakeException { + HandshakeBuilder handshake; + + String line = readStringLine( buf ); + if( line == null ) + throw new IncompleteHandshakeException( buf.capacity() + 128 ); + + String[] firstLineTokens = line.split( " ", 3 );// eg. HTTP/1.1 101 Switching the Protocols + if( firstLineTokens.length != 3 ) { + throw new InvalidHandshakeException(); + } + + if( role == Role.CLIENT ) { + // translating/parsing the response from the SERVER + handshake = new HandshakeImpl1Server(); + ServerHandshakeBuilder serverhandshake = (ServerHandshakeBuilder) handshake; + serverhandshake.setHttpStatus( Short.parseShort( firstLineTokens[ 1 ] ) ); + serverhandshake.setHttpStatusMessage( firstLineTokens[ 2 ] ); + } else { + // translating/parsing the request from the CLIENT + ClientHandshakeBuilder clienthandshake = new HandshakeImpl1Client(); + clienthandshake.setResourceDescriptor( firstLineTokens[ 1 ] ); + handshake = clienthandshake; + } + + line = readStringLine( buf ); + while ( line != null && line.length() > 0 ) { + String[] pair = line.split( ":", 2 ); + if( pair.length != 2 ) + throw new InvalidHandshakeException( "not an http header" ); + handshake.put( pair[ 0 ], pair[ 1 ].replaceFirst( "^ +", "" ) ); + line = readStringLine( buf ); + } + if( line == null ) + throw new IncompleteHandshakeException(); + return handshake; + } + + public abstract HandshakeState acceptHandshakeAsClient( ClientHandshake request, ServerHandshake response ) throws InvalidHandshakeException; + + public abstract HandshakeState acceptHandshakeAsServer( ClientHandshake handshakedata ) throws InvalidHandshakeException; + + protected boolean basicAccept( Handshakedata handshakedata ) { + return handshakedata.getFieldValue( "Upgrade" ).equalsIgnoreCase( "websocket" ) && handshakedata.getFieldValue( "Connection" ).toLowerCase( Locale.ENGLISH ).contains( "upgrade" ); + } + + public abstract ByteBuffer createBinaryFrame( Framedata framedata ); // TODO Allow to send data on the base of an Iterator or InputStream + + public abstract List<Framedata> createFrames( ByteBuffer binary, boolean mask ); + + public abstract List<Framedata> createFrames( String text, boolean mask ); + + public List<Framedata> continuousFrame( Opcode op, ByteBuffer buffer, boolean fin ) { + if( op != Opcode.BINARY && op != Opcode.TEXT && op != Opcode.TEXT ) { + throw new IllegalArgumentException( "Only Opcode.BINARY or Opcode.TEXT are allowed" ); + } + + if( continuousFrameType != null ) { + continuousFrameType = Opcode.CONTINUOUS; + } else { + continuousFrameType = op; + } + + FrameBuilder bui = new FramedataImpl1( continuousFrameType ); + try { + bui.setPayload( buffer ); + } catch ( InvalidDataException e ) { + throw new RuntimeException( e ); // can only happen when one builds close frames(Opcode.Close) + } + bui.setFin( fin ); + if( fin ) { + continuousFrameType = null; + } else { + continuousFrameType = op; + } + return Collections.singletonList( (Framedata) bui ); + } + + public abstract void reset(); + + public List<ByteBuffer> createHandshake( Handshakedata handshakedata, Role ownrole ) { + return createHandshake( handshakedata, ownrole, true ); + } + + public List<ByteBuffer> createHandshake( Handshakedata handshakedata, Role ownrole, boolean withcontent ) { + StringBuilder bui = new StringBuilder( 100 ); + if( handshakedata instanceof ClientHandshake ) { + bui.append( "GET " ); + bui.append( ( (ClientHandshake) handshakedata ).getResourceDescriptor() ); + bui.append( " HTTP/1.1" ); + } else if( handshakedata instanceof ServerHandshake ) { + bui.append( "HTTP/1.1 101 " + ( (ServerHandshake) handshakedata ).getHttpStatusMessage() ); + } else { + throw new RuntimeException( "unknow role" ); + } + bui.append( "\r\n" ); + Iterator<String> it = handshakedata.iterateHttpFields(); + while ( it.hasNext() ) { + String fieldname = it.next(); + String fieldvalue = handshakedata.getFieldValue( fieldname ); + bui.append( fieldname ); + bui.append( ": " ); + bui.append( fieldvalue ); + bui.append( "\r\n" ); + } + bui.append( "\r\n" ); + byte[] httpheader = Charsetfunctions.asciiBytes( bui.toString() ); + + byte[] content = withcontent ? handshakedata.getContent() : null; + ByteBuffer bytebuffer = ByteBuffer.allocate( ( content == null ? 0 : content.length ) + httpheader.length ); + bytebuffer.put( httpheader ); + if( content != null ) + bytebuffer.put( content ); + bytebuffer.flip(); + return Collections.singletonList( bytebuffer ); + } + + public abstract ClientHandshakeBuilder postProcessHandshakeRequestAsClient( ClientHandshakeBuilder request ) throws InvalidHandshakeException; + + public abstract HandshakeBuilder postProcessHandshakeResponseAsServer( ClientHandshake request, ServerHandshakeBuilder response ) throws InvalidHandshakeException; + + public abstract List<Framedata> translateFrame( ByteBuffer buffer ) throws InvalidDataException; + + public abstract CloseHandshakeType getCloseHandshakeType(); + + /** + * Drafts must only be by one websocket at all. To prevent drafts to be used more than once the Websocket implementation should call this method in order to create a new usable version of a given draft instance.<br> + * The copy can be safely used in conjunction with a new websocket connection. + * */ + public abstract Draft copyInstance(); + + public Handshakedata translateHandshake( ByteBuffer buf ) throws InvalidHandshakeException { + return translateHandshakeHttp( buf, role ); + } + + public int checkAlloc( int bytecount ) throws LimitExedeedException , InvalidDataException { + if( bytecount < 0 ) + throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "Negative count" ); + return bytecount; + } + + public void setParseMode( Role role ) { + this.role = role; + } + + public Role getRole() { + return role; + } + +} diff --git a/cb-tools/java-source/src/main/java/org/java_websocket/drafts/Draft_10.java b/cb-tools/java-source/src/main/java/org/java_websocket/drafts/Draft_10.java new file mode 100644 index 00000000..305460a5 --- /dev/null +++ b/cb-tools/java-source/src/main/java/org/java_websocket/drafts/Draft_10.java @@ -0,0 +1,397 @@ +package org.java_websocket.drafts; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Random; + +import org.java_websocket.WebSocket.Role; +import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.exceptions.InvalidFrameException; +import org.java_websocket.exceptions.InvalidHandshakeException; +import org.java_websocket.exceptions.LimitExedeedException; +import org.java_websocket.exceptions.NotSendableException; +import org.java_websocket.framing.CloseFrameBuilder; +import org.java_websocket.framing.FrameBuilder; +import org.java_websocket.framing.Framedata; +import org.java_websocket.framing.Framedata.Opcode; +import org.java_websocket.framing.FramedataImpl1; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.ClientHandshakeBuilder; +import org.java_websocket.handshake.HandshakeBuilder; +import org.java_websocket.handshake.Handshakedata; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.handshake.ServerHandshakeBuilder; +import org.java_websocket.util.Base64; +import org.java_websocket.util.Charsetfunctions; + +public class Draft_10 extends Draft { + + private class IncompleteException extends Throwable { + + /** + * It's Serializable. + */ + private static final long serialVersionUID = 7330519489840500997L; + + private int preferedsize; + public IncompleteException( int preferedsize ) { + this.preferedsize = preferedsize; + } + public int getPreferedSize() { + return preferedsize; + } + } + + public static int readVersion( Handshakedata handshakedata ) { + String vers = handshakedata.getFieldValue( "Sec-WebSocket-Version" ); + if( vers.length() > 0 ) { + int v; + try { + v = new Integer( vers.trim() ); + return v; + } catch ( NumberFormatException e ) { + return -1; + } + } + return -1; + } + + private ByteBuffer incompleteframe; + private Framedata fragmentedframe = null; + + private final Random reuseableRandom = new Random(); + + @Override + public HandshakeState acceptHandshakeAsClient( ClientHandshake request, ServerHandshake response ) throws InvalidHandshakeException { + if( !request.hasFieldValue( "Sec-WebSocket-Key" ) || !response.hasFieldValue( "Sec-WebSocket-Accept" ) ) + return HandshakeState.NOT_MATCHED; + + String seckey_answere = response.getFieldValue( "Sec-WebSocket-Accept" ); + String seckey_challenge = request.getFieldValue( "Sec-WebSocket-Key" ); + seckey_challenge = generateFinalKey( seckey_challenge ); + + if( seckey_challenge.equals( seckey_answere ) ) + return HandshakeState.MATCHED; + return HandshakeState.NOT_MATCHED; + } + + @Override + public HandshakeState acceptHandshakeAsServer( ClientHandshake handshakedata ) throws InvalidHandshakeException { + // Sec-WebSocket-Origin is only required for browser clients + int v = readVersion( handshakedata ); + if( v == 7 || v == 8 )// g + return basicAccept( handshakedata ) ? HandshakeState.MATCHED : HandshakeState.NOT_MATCHED; + return HandshakeState.NOT_MATCHED; + } + + @Override + public ByteBuffer createBinaryFrame( Framedata framedata ) { + ByteBuffer mes = framedata.getPayloadData(); + boolean mask = role == Role.CLIENT; // framedata.getTransfereMasked(); + int sizebytes = mes.remaining() <= 125 ? 1 : mes.remaining() <= 65535 ? 2 : 8; + ByteBuffer buf = ByteBuffer.allocate( 1 + ( sizebytes > 1 ? sizebytes + 1 : sizebytes ) + ( mask ? 4 : 0 ) + mes.remaining() ); + byte optcode = fromOpcode( framedata.getOpcode() ); + byte one = (byte) ( framedata.isFin() ? -128 : 0 ); + one |= optcode; + buf.put( one ); + byte[] payloadlengthbytes = toByteArray( mes.remaining(), sizebytes ); + assert ( payloadlengthbytes.length == sizebytes ); + + if( sizebytes == 1 ) { + buf.put( (byte) ( (byte) payloadlengthbytes[ 0 ] | ( mask ? (byte) -128 : 0 ) ) ); + } else if( sizebytes == 2 ) { + buf.put( (byte) ( (byte) 126 | ( mask ? (byte) -128 : 0 ) ) ); + buf.put( payloadlengthbytes ); + } else if( sizebytes == 8 ) { + buf.put( (byte) ( (byte) 127 | ( mask ? (byte) -128 : 0 ) ) ); + buf.put( payloadlengthbytes ); + } else + throw new RuntimeException( "Size representation not supported/specified" ); + + if( mask ) { + ByteBuffer maskkey = ByteBuffer.allocate( 4 ); + maskkey.putInt( reuseableRandom.nextInt() ); + buf.put( maskkey.array() ); + for( int i = 0 ; mes.hasRemaining() ; i++ ) { + buf.put( (byte) ( mes.get() ^ maskkey.get( i % 4 ) ) ); + } + } else + buf.put( mes ); + // translateFrame ( buf.array () , buf.array ().length ); + assert ( buf.remaining() == 0 ) : buf.remaining(); + buf.flip(); + + return buf; + } + + @Override + public List<Framedata> createFrames( ByteBuffer binary, boolean mask ) { + FrameBuilder curframe = new FramedataImpl1(); + try { + curframe.setPayload( binary ); + } catch ( InvalidDataException e ) { + throw new NotSendableException( e ); + } + curframe.setFin( true ); + curframe.setOptcode( Opcode.BINARY ); + curframe.setTransferemasked( mask ); + return Collections.singletonList( (Framedata) curframe ); + } + + @Override + public List<Framedata> createFrames( String text, boolean mask ) { + FrameBuilder curframe = new FramedataImpl1(); + try { + curframe.setPayload( ByteBuffer.wrap( Charsetfunctions.utf8Bytes( text ) ) ); + } catch ( InvalidDataException e ) { + throw new NotSendableException( e ); + } + curframe.setFin( true ); + curframe.setOptcode( Opcode.TEXT ); + curframe.setTransferemasked( mask ); + return Collections.singletonList( (Framedata) curframe ); + } + + private byte fromOpcode( Opcode opcode ) { + if( opcode == Opcode.CONTINUOUS ) + return 0; + else if( opcode == Opcode.TEXT ) + return 1; + else if( opcode == Opcode.BINARY ) + return 2; + else if( opcode == Opcode.CLOSING ) + return 8; + else if( opcode == Opcode.PING ) + return 9; + else if( opcode == Opcode.PONG ) + return 10; + throw new RuntimeException( "Don't know how to handle " + opcode.toString() ); + } + + private String generateFinalKey( String in ) { + String seckey = in.trim(); + String acc = seckey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + MessageDigest sh1; + try { + sh1 = MessageDigest.getInstance( "SHA1" ); + } catch ( NoSuchAlgorithmException e ) { + throw new RuntimeException( e ); + } + return Base64.encodeBytes( sh1.digest( acc.getBytes() ) ); + } + + @Override + public ClientHandshakeBuilder postProcessHandshakeRequestAsClient( ClientHandshakeBuilder request ) { + request.put( "Upgrade", "websocket" ); + request.put( "Connection", "Upgrade" ); // to respond to a Connection keep alives + request.put( "Sec-WebSocket-Version", "8" ); + + byte[] random = new byte[ 16 ]; + reuseableRandom.nextBytes( random ); + request.put( "Sec-WebSocket-Key", Base64.encodeBytes( random ) ); + + return request; + } + + @Override + public HandshakeBuilder postProcessHandshakeResponseAsServer( ClientHandshake request, ServerHandshakeBuilder response ) throws InvalidHandshakeException { + response.put( "Upgrade", "websocket" ); + response.put( "Connection", request.getFieldValue( "Connection" ) ); // to respond to a Connection keep alives + response.setHttpStatusMessage( "Switching Protocols" ); + String seckey = request.getFieldValue( "Sec-WebSocket-Key" ); + if( seckey == null ) + throw new InvalidHandshakeException( "missing Sec-WebSocket-Key" ); + response.put( "Sec-WebSocket-Accept", generateFinalKey( seckey ) ); + return response; + } + + private byte[] toByteArray( long val, int bytecount ) { + byte[] buffer = new byte[ bytecount ]; + int highest = 8 * bytecount - 8; + for( int i = 0 ; i < bytecount ; i++ ) { + buffer[ i ] = (byte) ( val >>> ( highest - 8 * i ) ); + } + return buffer; + } + + private Opcode toOpcode( byte opcode ) throws InvalidFrameException { + switch ( opcode ) { + case 0: + return Opcode.CONTINUOUS; + case 1: + return Opcode.TEXT; + case 2: + return Opcode.BINARY; + // 3-7 are not yet defined + case 8: + return Opcode.CLOSING; + case 9: + return Opcode.PING; + case 10: + return Opcode.PONG; + // 11-15 are not yet defined + default : + throw new InvalidFrameException( "unknow optcode " + (short) opcode ); + } + } + + @Override + public List<Framedata> translateFrame( ByteBuffer buffer ) throws LimitExedeedException , InvalidDataException { + List<Framedata> frames = new LinkedList<Framedata>(); + Framedata cur; + + if( incompleteframe != null ) { + // complete an incomplete frame + while ( true ) { + try { + buffer.mark(); + int available_next_byte_count = buffer.remaining();// The number of bytes received + int expected_next_byte_count = incompleteframe.remaining();// The number of bytes to complete the incomplete frame + + if( expected_next_byte_count > available_next_byte_count ) { + // did not receive enough bytes to complete the frame + incompleteframe.put( buffer.array(), buffer.position(), available_next_byte_count ); + buffer.position( buffer.position() + available_next_byte_count ); + return Collections.emptyList(); + } + incompleteframe.put( buffer.array(), buffer.position(), expected_next_byte_count ); + buffer.position( buffer.position() + expected_next_byte_count ); + + cur = translateSingleFrame( (ByteBuffer) incompleteframe.duplicate().position( 0 ) ); + frames.add( cur ); + incompleteframe = null; + break; // go on with the normal frame receival + } catch ( IncompleteException e ) { + // extending as much as suggested + int oldsize = incompleteframe.limit(); + ByteBuffer extendedframe = ByteBuffer.allocate( checkAlloc( e.getPreferedSize() ) ); + assert ( extendedframe.limit() > incompleteframe.limit() ); + incompleteframe.rewind(); + extendedframe.put( incompleteframe ); + incompleteframe = extendedframe; + + return translateFrame( buffer ); + } + } + } + + while ( buffer.hasRemaining() ) {// Read as much as possible full frames + buffer.mark(); + try { + cur = translateSingleFrame( buffer ); + frames.add( cur ); + } catch ( IncompleteException e ) { + // remember the incomplete data + buffer.reset(); + int pref = e.getPreferedSize(); + incompleteframe = ByteBuffer.allocate( checkAlloc( pref ) ); + incompleteframe.put( buffer ); + break; + } + } + return frames; + } + + public Framedata translateSingleFrame( ByteBuffer buffer ) throws IncompleteException , InvalidDataException { + int maxpacketsize = buffer.remaining(); + int realpacketsize = 2; + if( maxpacketsize < realpacketsize ) + throw new IncompleteException( realpacketsize ); + byte b1 = buffer.get( /*0*/); + boolean FIN = b1 >> 8 != 0; + byte rsv = (byte) ( ( b1 & ~(byte) 128 ) >> 4 ); + if( rsv != 0 ) + throw new InvalidFrameException( "bad rsv " + rsv ); + byte b2 = buffer.get( /*1*/); + boolean MASK = ( b2 & -128 ) != 0; + int payloadlength = (byte) ( b2 & ~(byte) 128 ); + Opcode optcode = toOpcode( (byte) ( b1 & 15 ) ); + + if( !FIN ) { + if( optcode == Opcode.PING || optcode == Opcode.PONG || optcode == Opcode.CLOSING ) { + throw new InvalidFrameException( "control frames may no be fragmented" ); + } + } + + if( payloadlength >= 0 && payloadlength <= 125 ) { + } else { + if( optcode == Opcode.PING || optcode == Opcode.PONG || optcode == Opcode.CLOSING ) { + throw new InvalidFrameException( "more than 125 octets" ); + } + if( payloadlength == 126 ) { + realpacketsize += 2; // additional length bytes + if( maxpacketsize < realpacketsize ) + throw new IncompleteException( realpacketsize ); + byte[] sizebytes = new byte[ 3 ]; + sizebytes[ 1 ] = buffer.get( /*1 + 1*/); + sizebytes[ 2 ] = buffer.get( /*1 + 2*/); + payloadlength = new BigInteger( sizebytes ).intValue(); + } else { + realpacketsize += 8; // additional length bytes + if( maxpacketsize < realpacketsize ) + throw new IncompleteException( realpacketsize ); + byte[] bytes = new byte[ 8 ]; + for( int i = 0 ; i < 8 ; i++ ) { + bytes[ i ] = buffer.get( /*1 + i*/); + } + long length = new BigInteger( bytes ).longValue(); + if( length > Integer.MAX_VALUE ) { + throw new LimitExedeedException( "Payloadsize is to big..." ); + } else { + payloadlength = (int) length; + } + } + } + + // int maskskeystart = foff + realpacketsize; + realpacketsize += ( MASK ? 4 : 0 ); + // int payloadstart = foff + realpacketsize; + realpacketsize += payloadlength; + + if( maxpacketsize < realpacketsize ) + throw new IncompleteException( realpacketsize ); + + ByteBuffer payload = ByteBuffer.allocate( checkAlloc( payloadlength ) ); + if( MASK ) { + byte[] maskskey = new byte[ 4 ]; + buffer.get( maskskey ); + for( int i = 0 ; i < payloadlength ; i++ ) { + payload.put( (byte) ( (byte) buffer.get( /*payloadstart + i*/) ^ (byte) maskskey[ i % 4 ] ) ); + } + } else { + payload.put( buffer.array(), buffer.position(), payload.limit() ); + buffer.position( buffer.position() + payload.limit() ); + } + + FrameBuilder frame; + if( optcode == Opcode.CLOSING ) { + frame = new CloseFrameBuilder(); + } else { + frame = new FramedataImpl1(); + frame.setFin( FIN ); + frame.setOptcode( optcode ); + } + payload.flip(); + frame.setPayload( payload ); + return frame; + } + + @Override + public void reset() { + incompleteframe = null; + } + + @Override + public Draft copyInstance() { + return new Draft_10(); + } + + @Override + public CloseHandshakeType getCloseHandshakeType() { + return CloseHandshakeType.TWOWAY; + } +} diff --git a/cb-tools/java-source/src/main/java/org/java_websocket/drafts/Draft_17.java b/cb-tools/java-source/src/main/java/org/java_websocket/drafts/Draft_17.java new file mode 100644 index 00000000..5c4088f7 --- /dev/null +++ b/cb-tools/java-source/src/main/java/org/java_websocket/drafts/Draft_17.java @@ -0,0 +1,28 @@ +package org.java_websocket.drafts; + +import org.java_websocket.exceptions.InvalidHandshakeException; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.ClientHandshakeBuilder; + +public class Draft_17 extends Draft_10 { + @Override + public HandshakeState acceptHandshakeAsServer( ClientHandshake handshakedata ) throws InvalidHandshakeException { + int v = readVersion( handshakedata ); + if( v == 13 ) + return HandshakeState.MATCHED; + return HandshakeState.NOT_MATCHED; + } + + @Override + public ClientHandshakeBuilder postProcessHandshakeRequestAsClient( ClientHandshakeBuilder request ) { + super.postProcessHandshakeRequestAsClient( request ); + request.put( "Sec-WebSocket-Version", "13" );// overwriting the previous + return request; + } + + @Override + public Draft copyInstance() { + return new Draft_17(); + } + +} diff --git a/cb-tools/java-source/src/main/java/org/java_websocket/drafts/Draft_75.java b/cb-tools/java-source/src/main/java/org/java_websocket/drafts/Draft_75.java new file mode 100644 index 00000000..947a35ec --- /dev/null +++ b/cb-tools/java-source/src/main/java/org/java_websocket/drafts/Draft_75.java @@ -0,0 +1,206 @@ +package org.java_websocket.drafts; + +import java.nio.ByteBuffer; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Random; + +import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.exceptions.InvalidFrameException; +import org.java_websocket.exceptions.InvalidHandshakeException; +import org.java_websocket.exceptions.LimitExedeedException; +import org.java_websocket.exceptions.NotSendableException; +import org.java_websocket.framing.CloseFrame; +import org.java_websocket.framing.FrameBuilder; +import org.java_websocket.framing.Framedata; +import org.java_websocket.framing.Framedata.Opcode; +import org.java_websocket.framing.FramedataImpl1; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.ClientHandshakeBuilder; +import org.java_websocket.handshake.HandshakeBuilder; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.handshake.ServerHandshakeBuilder; +import org.java_websocket.util.Charsetfunctions; + +public class Draft_75 extends Draft { + + /** + * The byte representing CR, or Carriage Return, or \r + */ + public static final byte CR = (byte) 0x0D; + /** + * The byte representing LF, or Line Feed, or \n + */ + public static final byte LF = (byte) 0x0A; + /** + * The byte representing the beginning of a WebSocket text frame. + */ + public static final byte START_OF_FRAME = (byte) 0x00; + /** + * The byte representing the end of a WebSocket text frame. + */ + public static final byte END_OF_FRAME = (byte) 0xFF; + + /** Is only used to detect protocol violations */ + protected boolean readingState = false; + + protected List<Framedata> readyframes = new LinkedList<Framedata>(); + protected ByteBuffer currentFrame; + + private final Random reuseableRandom = new Random(); + + @Override + public HandshakeState acceptHandshakeAsClient( ClientHandshake request, ServerHandshake response ) { + return request.getFieldValue( "WebSocket-Origin" ).equals( response.getFieldValue( "Origin" ) ) && basicAccept( response ) ? HandshakeState.MATCHED : HandshakeState.NOT_MATCHED; + } + + @Override + public HandshakeState acceptHandshakeAsServer( ClientHandshake handshakedata ) { + if( handshakedata.hasFieldValue( "Origin" ) && basicAccept( handshakedata ) ) { + return HandshakeState.MATCHED; + } + return HandshakeState.NOT_MATCHED; + } + + @Override + public ByteBuffer createBinaryFrame( Framedata framedata ) { + if( framedata.getOpcode() != Opcode.TEXT ) { + throw new RuntimeException( "only text frames supported" ); + } + + ByteBuffer pay = framedata.getPayloadData(); + ByteBuffer b = ByteBuffer.allocate( pay.remaining() + 2 ); + b.put( START_OF_FRAME ); + pay.mark(); + b.put( pay ); + pay.reset(); + b.put( END_OF_FRAME ); + b.flip(); + return b; + } + + @Override + public List<Framedata> createFrames( ByteBuffer binary, boolean mask ) { + throw new RuntimeException( "not yet implemented" ); + } + + @Override + public List<Framedata> createFrames( String text, boolean mask ) { + FrameBuilder frame = new FramedataImpl1(); + try { + frame.setPayload( ByteBuffer.wrap( Charsetfunctions.utf8Bytes( text ) ) ); + } catch ( InvalidDataException e ) { + throw new NotSendableException( e ); + } + frame.setFin( true ); + frame.setOptcode( Opcode.TEXT ); + frame.setTransferemasked( mask ); + return Collections.singletonList( (Framedata) frame ); + } + + @Override + public ClientHandshakeBuilder postProcessHandshakeRequestAsClient( ClientHandshakeBuilder request ) throws InvalidHandshakeException { + request.put( "Upgrade", "WebSocket" ); + request.put( "Connection", "Upgrade" ); + if( !request.hasFieldValue( "Origin" ) ) { + request.put( "Origin", "random" + reuseableRandom.nextInt() ); + } + + return request; + } + + @Override + public HandshakeBuilder postProcessHandshakeResponseAsServer( ClientHandshake request, ServerHandshakeBuilder response ) throws InvalidHandshakeException { + response.setHttpStatusMessage( "Web Socket Protocol Handshake" ); + response.put( "Upgrade", "WebSocket" ); + response.put( "Connection", request.getFieldValue( "Connection" ) ); // to respond to a Connection keep alive + response.put( "WebSocket-Origin", request.getFieldValue( "Origin" ) ); + String location = "ws://" + request.getFieldValue( "Host" ) + request.getResourceDescriptor(); + response.put( "WebSocket-Location", location ); + // TODO handle Sec-WebSocket-Protocol and Set-Cookie + return response; + } + + protected List<Framedata> translateRegularFrame( ByteBuffer buffer ) throws InvalidDataException { + + while ( buffer.hasRemaining() ) { + byte newestByte = buffer.get(); + if( newestByte == START_OF_FRAME ) { // Beginning of Frame + if( readingState ) + throw new InvalidFrameException( "unexpected START_OF_FRAME" ); + readingState = true; + } else if( newestByte == END_OF_FRAME ) { // End of Frame + if( !readingState ) + throw new InvalidFrameException( "unexpected END_OF_FRAME" ); + // currentFrame will be null if END_OF_FRAME was send directly after + // START_OF_FRAME, thus we will send 'null' as the sent message. + if( this.currentFrame != null ) { + currentFrame.flip(); + FramedataImpl1 curframe = new FramedataImpl1(); + curframe.setPayload( currentFrame ); + curframe.setFin( true ); + curframe.setOptcode( Opcode.TEXT ); + readyframes.add( curframe ); + this.currentFrame = null; + buffer.mark(); + } + readingState = false; + } else if( readingState ) { // Regular frame data, add to current frame buffer //TODO This code is very expensive and slow + if( currentFrame == null ) { + currentFrame = createBuffer(); + } else if( !currentFrame.hasRemaining() ) { + currentFrame = increaseBuffer( currentFrame ); + } + currentFrame.put( newestByte ); + } else { + return null; + } + } + + // if no error occurred this block will be reached + /*if( readingState ) { + checkAlloc(currentFrame.position()+1); + }*/ + + List<Framedata> frames = readyframes; + readyframes = new LinkedList<Framedata>(); + return frames; + } + + @Override + public List<Framedata> translateFrame( ByteBuffer buffer ) throws InvalidDataException { + List<Framedata> frames = translateRegularFrame( buffer ); + if( frames == null ) { + throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR ); + } + return frames; + } + + @Override + public void reset() { + readingState = false; + this.currentFrame = null; + } + + @Override + public CloseHandshakeType getCloseHandshakeType() { + return CloseHandshakeType.NONE; + } + + public ByteBuffer createBuffer() { + return ByteBuffer.allocate( INITIAL_FAMESIZE ); + } + + public ByteBuffer increaseBuffer( ByteBuffer full ) throws LimitExedeedException , InvalidDataException { + full.flip(); + ByteBuffer newbuffer = ByteBuffer.allocate( checkAlloc( full.capacity() * 2 ) ); + newbuffer.put( full ); + return newbuffer; + } + + @Override + public Draft copyInstance() { + return new Draft_75(); + } +} diff --git a/cb-tools/java-source/src/main/java/org/java_websocket/drafts/Draft_76.java b/cb-tools/java-source/src/main/java/org/java_websocket/drafts/Draft_76.java new file mode 100644 index 00000000..26f23531 --- /dev/null +++ b/cb-tools/java-source/src/main/java/org/java_websocket/drafts/Draft_76.java @@ -0,0 +1,242 @@ +package org.java_websocket.drafts; + +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.Random; + +import org.java_websocket.WebSocket.Role; +import org.java_websocket.exceptions.IncompleteHandshakeException; +import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.exceptions.InvalidFrameException; +import org.java_websocket.exceptions.InvalidHandshakeException; +import org.java_websocket.framing.CloseFrame; +import org.java_websocket.framing.CloseFrameBuilder; +import org.java_websocket.framing.Framedata; +import org.java_websocket.framing.Framedata.Opcode; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.ClientHandshakeBuilder; +import org.java_websocket.handshake.HandshakeBuilder; +import org.java_websocket.handshake.Handshakedata; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.handshake.ServerHandshakeBuilder; + +public class Draft_76 extends Draft_75 { + private boolean failed = false; + private static final byte[] closehandshake = { -1, 0 }; + + private final Random reuseableRandom = new Random(); + + + public static byte[] createChallenge( String key1, String key2, byte[] key3 ) throws InvalidHandshakeException { + byte[] part1 = getPart( key1 ); + byte[] part2 = getPart( key2 ); + byte[] challenge = new byte[ 16 ]; + challenge[ 0 ] = part1[ 0 ]; + challenge[ 1 ] = part1[ 1 ]; + challenge[ 2 ] = part1[ 2 ]; + challenge[ 3 ] = part1[ 3 ]; + challenge[ 4 ] = part2[ 0 ]; + challenge[ 5 ] = part2[ 1 ]; + challenge[ 6 ] = part2[ 2 ]; + challenge[ 7 ] = part2[ 3 ]; + challenge[ 8 ] = key3[ 0 ]; + challenge[ 9 ] = key3[ 1 ]; + challenge[ 10 ] = key3[ 2 ]; + challenge[ 11 ] = key3[ 3 ]; + challenge[ 12 ] = key3[ 4 ]; + challenge[ 13 ] = key3[ 5 ]; + challenge[ 14 ] = key3[ 6 ]; + challenge[ 15 ] = key3[ 7 ]; + MessageDigest md5; + try { + md5 = MessageDigest.getInstance( "MD5" ); + } catch ( NoSuchAlgorithmException e ) { + throw new RuntimeException( e ); + } + return md5.digest( challenge ); + } + + private static String generateKey() { + Random r = new Random(); + long maxNumber = 4294967295L; + long spaces = r.nextInt( 12 ) + 1; + int max = new Long( maxNumber / spaces ).intValue(); + max = Math.abs( max ); + int number = r.nextInt( max ) + 1; + long product = number * spaces; + String key = Long.toString( product ); + // always insert atleast one random character + int numChars = r.nextInt( 12 ) + 1; + for( int i = 0 ; i < numChars ; i++ ) { + int position = r.nextInt( key.length() ); + position = Math.abs( position ); + char randChar = (char) ( r.nextInt( 95 ) + 33 ); + // exclude numbers here + if( randChar >= 48 && randChar <= 57 ) { + randChar -= 15; + } + key = new StringBuilder( key ).insert( position, randChar ).toString(); + } + for( int i = 0 ; i < spaces ; i++ ) { + int position = r.nextInt( key.length() - 1 ) + 1; + position = Math.abs( position ); + key = new StringBuilder( key ).insert( position, "\u0020" ).toString(); + } + return key; + } + + private static byte[] getPart( String key ) throws InvalidHandshakeException { + try { + long keyNumber = Long.parseLong( key.replaceAll( "[^0-9]", "" ) ); + long keySpace = key.split( "\u0020" ).length - 1; + if( keySpace == 0 ) { + throw new InvalidHandshakeException( "invalid Sec-WebSocket-Key (/key2/)" ); + } + long part = new Long( keyNumber / keySpace ); + return new byte[]{ (byte) ( part >> 24 ), (byte) ( ( part << 8 ) >> 24 ), (byte) ( ( part << 16 ) >> 24 ), (byte) ( ( part << 24 ) >> 24 ) }; + } catch ( NumberFormatException e ) { + throw new InvalidHandshakeException( "invalid Sec-WebSocket-Key (/key1/ or /key2/)" ); + } + } + + @Override + public HandshakeState acceptHandshakeAsClient( ClientHandshake request, ServerHandshake response ) { + if( failed ) { + return HandshakeState.NOT_MATCHED; + } + + try { + if( !response.getFieldValue( "Sec-WebSocket-Origin" ).equals( request.getFieldValue( "Origin" ) ) || !basicAccept( response ) ) { + return HandshakeState.NOT_MATCHED; + } + byte[] content = response.getContent(); + if( content == null || content.length == 0 ) { + throw new IncompleteHandshakeException(); + } + if( Arrays.equals( content, createChallenge( request.getFieldValue( "Sec-WebSocket-Key1" ), request.getFieldValue( "Sec-WebSocket-Key2" ), request.getContent() ) ) ) { + return HandshakeState.MATCHED; + } else { + return HandshakeState.NOT_MATCHED; + } + } catch ( InvalidHandshakeException e ) { + throw new RuntimeException( "bad handshakerequest", e ); + } + } + + @Override + public HandshakeState acceptHandshakeAsServer( ClientHandshake handshakedata ) { + if( handshakedata.getFieldValue( "Upgrade" ).equals( "WebSocket" ) && handshakedata.getFieldValue( "Connection" ).contains( "Upgrade" ) && handshakedata.getFieldValue( "Sec-WebSocket-Key1" ).length() > 0 && !handshakedata.getFieldValue( "Sec-WebSocket-Key2" ).isEmpty() && handshakedata.hasFieldValue( "Origin" ) ) + return HandshakeState.MATCHED; + return HandshakeState.NOT_MATCHED; + } + + @Override + public ClientHandshakeBuilder postProcessHandshakeRequestAsClient( ClientHandshakeBuilder request ) { + request.put( "Upgrade", "WebSocket" ); + request.put( "Connection", "Upgrade" ); + request.put( "Sec-WebSocket-Key1", generateKey() ); + request.put( "Sec-WebSocket-Key2", generateKey() ); + + if( !request.hasFieldValue( "Origin" ) ) { + request.put( "Origin", "random" + reuseableRandom.nextInt() ); + } + + byte[] key3 = new byte[ 8 ]; + reuseableRandom.nextBytes( key3 ); + request.setContent( key3 ); + return request; + + } + + @Override + public HandshakeBuilder postProcessHandshakeResponseAsServer( ClientHandshake request, ServerHandshakeBuilder response ) throws InvalidHandshakeException { + response.setHttpStatusMessage( "WebSocket Protocol Handshake" ); + response.put( "Upgrade", "WebSocket" ); + response.put( "Connection", request.getFieldValue( "Connection" ) ); // to respond to a Connection keep alive + response.put( "Sec-WebSocket-Origin", request.getFieldValue( "Origin" ) ); + String location = "ws://" + request.getFieldValue( "Host" ) + request.getResourceDescriptor(); + response.put( "Sec-WebSocket-Location", location ); + String key1 = request.getFieldValue( "Sec-WebSocket-Key1" ); + String key2 = request.getFieldValue( "Sec-WebSocket-Key2" ); + byte[] key3 = request.getContent(); + if( key1 == null || key2 == null || key3 == null || key3.length != 8 ) { + throw new InvalidHandshakeException( "Bad keys" ); + } + response.setContent( createChallenge( key1, key2, key3 ) ); + return response; + } + + @Override + public Handshakedata translateHandshake( ByteBuffer buf ) throws InvalidHandshakeException { + + HandshakeBuilder bui = translateHandshakeHttp( buf, role ); + // the first drafts are lacking a protocol number which makes them difficult to distinguish. Sec-WebSocket-Key1 is typical for draft76 + if( ( bui.hasFieldValue( "Sec-WebSocket-Key1" ) || role == Role.CLIENT ) && !bui.hasFieldValue( "Sec-WebSocket-Version" ) ) { + byte[] key3 = new byte[ role == Role.SERVER ? 8 : 16 ]; + try { + buf.get( key3 ); + } catch ( BufferUnderflowException e ) { + throw new IncompleteHandshakeException( buf.capacity() + 16 ); + } + bui.setContent( key3 ); + + } + return bui; + } + + @Override + public List<Framedata> translateFrame( ByteBuffer buffer ) throws InvalidDataException { + buffer.mark(); + List<Framedata> frames = super.translateRegularFrame( buffer ); + if( frames == null ) { + buffer.reset(); + frames = readyframes; + readingState = true; + if( currentFrame == null ) + currentFrame = ByteBuffer.allocate( 2 ); + else { + throw new InvalidFrameException(); + } + if( buffer.remaining() > currentFrame.remaining() ) { + throw new InvalidFrameException(); + } else { + currentFrame.put( buffer ); + } + if( !currentFrame.hasRemaining() ) { + if( Arrays.equals( currentFrame.array(), closehandshake ) ) { + frames.add( new CloseFrameBuilder( CloseFrame.NORMAL ) ); + return frames; + } + else{ + throw new InvalidFrameException(); + } + } else { + readyframes = new LinkedList<Framedata>(); + return frames; + } + } else { + return frames; + } + } + @Override + public ByteBuffer createBinaryFrame( Framedata framedata ) { + if( framedata.getOpcode() == Opcode.CLOSING ) + return ByteBuffer.wrap( closehandshake ); + return super.createBinaryFrame( framedata ); + } + + @Override + public CloseHandshakeType getCloseHandshakeType() { + return CloseHandshakeType.ONEWAY; + } + + @Override + public Draft copyInstance() { + return new Draft_76(); + } +} diff --git a/cb-tools/java-source/src/main/java/org/java_websocket/exceptions/IncompleteHandshakeException.java b/cb-tools/java-source/src/main/java/org/java_websocket/exceptions/IncompleteHandshakeException.java new file mode 100644 index 00000000..2fdb5eae --- /dev/null +++ b/cb-tools/java-source/src/main/java/org/java_websocket/exceptions/IncompleteHandshakeException.java @@ -0,0 +1,20 @@ +package org.java_websocket.exceptions; + +public class IncompleteHandshakeException extends RuntimeException { + + private static final long serialVersionUID = 7906596804233893092L; + private int newsize; + + public IncompleteHandshakeException( int newsize ) { + this.newsize = newsize; + } + + public IncompleteHandshakeException() { + this.newsize = 0; + } + + public int getPreferedSize() { + return newsize; + } + +} diff --git a/cb-tools/java-source/src/main/java/org/java_websocket/exceptions/InvalidDataException.java b/cb-tools/java-source/src/main/java/org/java_websocket/exceptions/InvalidDataException.java new file mode 100644 index 00000000..2ab9d328 --- /dev/null +++ b/cb-tools/java-source/src/main/java/org/java_websocket/exceptions/InvalidDataException.java @@ -0,0 +1,34 @@ +package org.java_websocket.exceptions; + +public class InvalidDataException extends Exception { + /** + * Serializable + */ + private static final long serialVersionUID = 3731842424390998726L; + + private int closecode; + + public InvalidDataException( int closecode ) { + this.closecode = closecode; + } + + public InvalidDataException( int closecode , String s ) { + super( s ); + this.closecode = closecode; + } + + public InvalidDataException( int closecode , Throwable t ) { + super( t ); + this.closecode = closecode; + } + + public InvalidDataException( int closecode , String s , Throwable t ) { + super( s, t ); + this.closecode = closecode; + } + + public int getCloseCode() { + return closecode; + } + +} diff --git a/cb-tools/java-source/src/main/java/org/java_websocket/exceptions/InvalidFrameException.java b/cb-tools/java-source/src/main/java/org/java_websocket/exceptions/InvalidFrameException.java new file mode 100644 index 00000000..c7fe4101 --- /dev/null +++ b/cb-tools/java-source/src/main/java/org/java_websocket/exceptions/InvalidFrameException.java @@ -0,0 +1,27 @@ +package org.java_websocket.exceptions; + +import org.java_websocket.framing.CloseFrame; + +public class InvalidFrameException extends InvalidDataException { + + /** + * Serializable + */ + private static final long serialVersionUID = -9016496369828887591L; + + public InvalidFrameException() { + super( CloseFrame.PROTOCOL_ERROR ); + } + + public InvalidFrameException( String arg0 ) { + super( CloseFrame.PROTOCOL_ERROR, arg0 ); + } + + public InvalidFrameException( Throwable arg0 ) { + super( CloseFrame.PROTOCOL_ERROR, arg0 ); + } + + public InvalidFrameException( String arg0 , Throwable arg1 ) { + super( CloseFrame.PROTOCOL_ERROR, arg0, arg1 ); + } +} diff --git a/cb-tools/java-source/src/main/java/org/java_websocket/exceptions/InvalidHandshakeException.java b/cb-tools/java-source/src/main/java/org/java_websocket/exceptions/InvalidHandshakeException.java new file mode 100644 index 00000000..4d0baec8 --- /dev/null +++ b/cb-tools/java-source/src/main/java/org/java_websocket/exceptions/InvalidHandshakeException.java @@ -0,0 +1,28 @@ +package org.java_websocket.exceptions; + +import org.java_websocket.framing.CloseFrame; + +public class InvalidHandshakeException extends InvalidDataException { + + /** + * Serializable + */ + private static final long serialVersionUID = -1426533877490484964L; + + public InvalidHandshakeException() { + super( CloseFrame.PROTOCOL_ERROR ); + } + + public InvalidHandshakeException( String arg0 , Throwable arg1 ) { + super( CloseFrame.PROTOCOL_ERROR, arg0, arg1 ); + } + + public InvalidHandshakeException( String arg0 ) { + super( CloseFrame.PROTOCOL_ERROR, arg0 ); + } + + public InvalidHandshakeException( Throwable arg0 ) { + super( CloseFrame.PROTOCOL_ERROR, arg0 ); + } + +} diff --git a/cb-tools/java-source/src/main/java/org/java_websocket/exceptions/LimitExedeedException.java b/cb-tools/java-source/src/main/java/org/java_websocket/exceptions/LimitExedeedException.java new file mode 100644 index 00000000..1ac7f8c5 --- /dev/null +++ b/cb-tools/java-source/src/main/java/org/java_websocket/exceptions/LimitExedeedException.java @@ -0,0 +1,20 @@ +package org.java_websocket.exceptions; + +import org.java_websocket.framing.CloseFrame; + +public class LimitExedeedException extends InvalidDataException { + + /** + * Serializable + */ + private static final long serialVersionUID = 6908339749836826785L; + + public LimitExedeedException() { + super( CloseFrame.TOOBIG ); + } + + public LimitExedeedException( String s ) { + super( CloseFrame.TOOBIG, s ); + } + +} diff --git a/cb-tools/java-source/src/main/java/org/java_websocket/exceptions/NotSendableException.java b/cb-tools/java-source/src/main/java/org/java_websocket/exceptions/NotSendableException.java new file mode 100644 index 00000000..2b2e2293 --- /dev/null +++ b/cb-tools/java-source/src/main/java/org/java_websocket/exceptions/NotSendableException.java @@ -0,0 +1,25 @@ +package org.java_websocket.exceptions; + +public class NotSendableException extends RuntimeException { + + /** + * Serializable + */ + private static final long serialVersionUID = -6468967874576651628L; + + public NotSendableException() { + } + + public NotSendableException( String message ) { + super( message ); + } + + public NotSendableException( Throwable cause ) { + super( cause ); + } + + public NotSendableException( String message , Throwable cause ) { + super( message, cause ); + } + +} diff --git a/cb-tools/java-source/src/main/java/org/java_websocket/exceptions/WebsocketNotConnectedException.java b/cb-tools/java-source/src/main/java/org/java_websocket/exceptions/WebsocketNotConnectedException.java new file mode 100644 index 00000000..45c5f4df --- /dev/null +++ b/cb-tools/java-source/src/main/java/org/java_websocket/exceptions/WebsocketNotConnectedException.java @@ -0,0 +1,5 @@ +package org.java_websocket.exceptions; + +public class WebsocketNotConnectedException extends RuntimeException { + +} diff --git a/cb-tools/java-source/src/main/java/org/java_websocket/framing/CloseFrame.java b/cb-tools/java-source/src/main/java/org/java_websocket/framing/CloseFrame.java new file mode 100644 index 00000000..f253b8dd --- /dev/null +++ b/cb-tools/java-source/src/main/java/org/java_websocket/framing/CloseFrame.java @@ -0,0 +1,98 @@ +package org.java_websocket.framing; + +import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.exceptions.InvalidFrameException; + +public interface CloseFrame extends Framedata { + /** + * indicates a normal closure, meaning whatever purpose the + * connection was established for has been fulfilled. + */ + public static final int NORMAL = 1000; + /** + * 1001 indicates that an endpoint is "going away", such as a server + * going down, or a browser having navigated away from a page. + */ + public static final int GOING_AWAY = 1001; + /** + * 1002 indicates that an endpoint is terminating the connection due + * to a protocol error. + */ + public static final int PROTOCOL_ERROR = 1002; + /** + * 1003 indicates that an endpoint is terminating the connection + * because it has received a type of data it cannot accept (e.g. an + * endpoint that understands only text data MAY send this if it + * receives a binary message). + */ + public static final int REFUSE = 1003; + /*1004: Reserved. The specific meaning might be defined in the future.*/ + /** + * 1005 is a reserved value and MUST NOT be set as a status code in a + * Close control frame by an endpoint. It is designated for use in + * applications expecting a status code to indicate that no status + * code was actually present. + */ + public static final int NOCODE = 1005; + /** + * 1006 is a reserved value and MUST NOT be set as a status code in a + * Close control frame by an endpoint. It is designated for use in + * applications expecting a status code to indicate that the + * connection was closed abnormally, e.g. without sending or + * receiving a Close control frame. + */ + public static final int ABNORMAL_CLOSE = 1006; + /** + * 1007 indicates that an endpoint is terminating the connection + * because it has received data within a message that was not + * consistent with the type of the message (e.g., non-UTF-8 [RFC3629] + * data within a text message). + */ + public static final int NO_UTF8 = 1007; + /** + * 1008 indicates that an endpoint is terminating the connection + * because it has received a message that violates its policy. This + * is a generic status code that can be returned when there is no + * other more suitable status code (e.g. 1003 or 1009), or if there + * is a need to hide specific details about the policy. + */ + public static final int POLICY_VALIDATION = 1008; + /** + * 1009 indicates that an endpoint is terminating the connection + * because it has received a message which is too big for it to + * process. + */ + public static final int TOOBIG = 1009; + /** + * 1010 indicates that an endpoint (client) is terminating the + * connection because it has expected the server to negotiate one or + * more extension, but the server didn't return them in the response + * message of the WebSocket handshake. The list of extensions which + * are needed SHOULD appear in the /reason/ part of the Close frame. + * Note that this status code is not used by the server, because it + * can fail the WebSocket handshake instead. + */ + public static final int EXTENSION = 1010; + /** + * 1011 indicates that a server is terminating the connection because + * it encountered an unexpected condition that prevented it from + * fulfilling the request. + **/ + public static final int UNEXPECTED_CONDITION = 1011; + /** + * 1015 is a reserved value and MUST NOT be set as a status code in a + * Close control frame by an endpoint. It is designated for use in + * applications expecting a status code to indicate that the + * connection was closed due to a failure to perform a TLS handshake + * (e.g., the server certificate can't be verified). + **/ + public static final int TLS_ERROR = 1015; + + /** The connection had never been established */ + public static final int NEVER_CONNECTED = -1; + public static final int BUGGYCLOSE = -2; + public static final int FLASHPOLICY = -3; + + public int getCloseCode() throws InvalidFrameException; + public String getMessage() throws InvalidDataException; +} diff --git a/cb-tools/java-source/src/main/java/org/java_websocket/framing/CloseFrameBuilder.java b/cb-tools/java-source/src/main/java/org/java_websocket/framing/CloseFrameBuilder.java new file mode 100644 index 00000000..fee1b540 --- /dev/null +++ b/cb-tools/java-source/src/main/java/org/java_websocket/framing/CloseFrameBuilder.java @@ -0,0 +1,123 @@ +package org.java_websocket.framing; + +import java.nio.ByteBuffer; + +import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.exceptions.InvalidFrameException; +import org.java_websocket.util.Charsetfunctions; + +public class CloseFrameBuilder extends FramedataImpl1 implements CloseFrame { + + static final ByteBuffer emptybytebuffer = ByteBuffer.allocate( 0 ); + + private int code; + private String reason; + + public CloseFrameBuilder() { + super( Opcode.CLOSING ); + setFin( true ); + } + + public CloseFrameBuilder( int code ) throws InvalidDataException { + super( Opcode.CLOSING ); + setFin( true ); + setCodeAndMessage( code, "" ); + } + + public CloseFrameBuilder( int code , String m ) throws InvalidDataException { + super( Opcode.CLOSING ); + setFin( true ); + setCodeAndMessage( code, m ); + } + + private void setCodeAndMessage( int code, String m ) throws InvalidDataException { + if( m == null ) { + m = ""; + } + // CloseFrame.TLS_ERROR is not allowed to be transfered over the wire + if( code == CloseFrame.TLS_ERROR ) { + code = CloseFrame.NOCODE; + m = ""; + } + if( code == CloseFrame.NOCODE ) { + if( 0 < m.length() ) { + throw new InvalidDataException( PROTOCOL_ERROR, "A close frame must have a closecode if it has a reason" ); + } + return;// empty payload + } + + byte[] by = Charsetfunctions.utf8Bytes( m ); + ByteBuffer buf = ByteBuffer.allocate( 4 ); + buf.putInt( code ); + buf.position( 2 ); + ByteBuffer pay = ByteBuffer.allocate( 2 + by.length ); + pay.put( buf ); + pay.put( by ); + pay.rewind(); + setPayload( pay ); + } + + private void initCloseCode() throws InvalidFrameException { + code = CloseFrame.NOCODE; + ByteBuffer payload = super.getPayloadData(); + payload.mark(); + if( payload.remaining() >= 2 ) { + ByteBuffer bb = ByteBuffer.allocate( 4 ); + bb.position( 2 ); + bb.putShort( payload.getShort() ); + bb.position( 0 ); + code = bb.getInt(); + + if( code == CloseFrame.ABNORMAL_CLOSE || code == CloseFrame.TLS_ERROR || code == CloseFrame.NOCODE || code > 4999 || code < 1000 || code == 1004 ) { + throw new InvalidFrameException( "closecode must not be sent over the wire: " + code ); + } + } + payload.reset(); + } + + @Override + public int getCloseCode() { + return code; + } + + private void initMessage() throws InvalidDataException { + if( code == CloseFrame.NOCODE ) { + reason = Charsetfunctions.stringUtf8( super.getPayloadData() ); + } else { + ByteBuffer b = super.getPayloadData(); + int mark = b.position();// because stringUtf8 also creates a mark + try { + b.position( b.position() + 2 ); + reason = Charsetfunctions.stringUtf8( b ); + } catch ( IllegalArgumentException e ) { + throw new InvalidFrameException( e ); + } finally { + b.position( mark ); + } + } + } + + @Override + public String getMessage() { + return reason; + } + + @Override + public String toString() { + return super.toString() + "code: " + code; + } + + @Override + public void setPayload( ByteBuffer payload ) throws InvalidDataException { + super.setPayload( payload ); + initCloseCode(); + initMessage(); + } + @Override + public ByteBuffer getPayloadData() { + if( code == NOCODE ) + return emptybytebuffer; + return super.getPayloadData(); + } + +} diff --git a/cb-tools/java-source/src/main/java/org/java_websocket/framing/FrameBuilder.java b/cb-tools/java-source/src/main/java/org/java_websocket/framing/FrameBuilder.java new file mode 100644 index 00000000..25a20de4 --- /dev/null +++ b/cb-tools/java-source/src/main/java/org/java_websocket/framing/FrameBuilder.java @@ -0,0 +1,17 @@ +package org.java_websocket.framing; + +import java.nio.ByteBuffer; + +import org.java_websocket.exceptions.InvalidDataException; + +public interface FrameBuilder extends Framedata { + + public abstract void setFin( boolean fin ); + + public abstract void setOptcode( Opcode optcode ); + + public abstract void setPayload( ByteBuffer payload ) throws InvalidDataException; + + public abstract void setTransferemasked( boolean transferemasked ); + +} \ No newline at end of file diff --git a/cb-tools/java-source/src/main/java/org/java_websocket/framing/Framedata.java b/cb-tools/java-source/src/main/java/org/java_websocket/framing/Framedata.java new file mode 100644 index 00000000..3dfa8c08 --- /dev/null +++ b/cb-tools/java-source/src/main/java/org/java_websocket/framing/Framedata.java @@ -0,0 +1,17 @@ +package org.java_websocket.framing; + +import java.nio.ByteBuffer; + +import org.java_websocket.exceptions.InvalidFrameException; + +public interface Framedata { + public enum Opcode { + CONTINUOUS, TEXT, BINARY, PING, PONG, CLOSING + // more to come + } + public boolean isFin(); + public boolean getTransfereMasked(); + public Opcode getOpcode(); + public ByteBuffer getPayloadData();// TODO the separation of the application data and the extension data is yet to be done + public abstract void append( Framedata nextframe ) throws InvalidFrameException; +} diff --git a/cb-tools/java-source/src/main/java/org/java_websocket/framing/FramedataImpl1.java b/cb-tools/java-source/src/main/java/org/java_websocket/framing/FramedataImpl1.java new file mode 100644 index 00000000..5fba075b --- /dev/null +++ b/cb-tools/java-source/src/main/java/org/java_websocket/framing/FramedataImpl1.java @@ -0,0 +1,110 @@ +package org.java_websocket.framing; + +import java.nio.ByteBuffer; +import java.util.Arrays; + +import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.exceptions.InvalidFrameException; +import org.java_websocket.util.Charsetfunctions; + +public class FramedataImpl1 implements FrameBuilder { + protected static byte[] emptyarray = {}; + protected boolean fin; + protected Opcode optcode; + private ByteBuffer unmaskedpayload; + protected boolean transferemasked; + + public FramedataImpl1() { + } + + public FramedataImpl1( Opcode op ) { + this.optcode = op; + unmaskedpayload = ByteBuffer.wrap( emptyarray ); + } + + /** + * Helper constructor which helps to create "echo" frames. + * The new object will use the same underlying payload data. + **/ + public FramedataImpl1( Framedata f ) { + fin = f.isFin(); + optcode = f.getOpcode(); + unmaskedpayload = f.getPayloadData(); + transferemasked = f.getTransfereMasked(); + } + + @Override + public boolean isFin() { + return fin; + } + + @Override + public Opcode getOpcode() { + return optcode; + } + + @Override + public boolean getTransfereMasked() { + return transferemasked; + } + + @Override + public ByteBuffer getPayloadData() { + return unmaskedpayload; + } + + @Override + public void setFin( boolean fin ) { + this.fin = fin; + } + + @Override + public void setOptcode( Opcode optcode ) { + this.optcode = optcode; + } + + @Override + public void setPayload( ByteBuffer payload ) throws InvalidDataException { + unmaskedpayload = payload; + } + + @Override + public void setTransferemasked( boolean transferemasked ) { + this.transferemasked = transferemasked; + } + + @Override + public void append( Framedata nextframe ) throws InvalidFrameException { + ByteBuffer b = nextframe.getPayloadData(); + if( unmaskedpayload == null ) { + unmaskedpayload = ByteBuffer.allocate( b.remaining() ); + b.mark(); + unmaskedpayload.put( b ); + b.reset(); + } else { + b.mark(); + unmaskedpayload.position( unmaskedpayload.limit() ); + unmaskedpayload.limit( unmaskedpayload.capacity() ); + + if( b.remaining() > unmaskedpayload.remaining() ) { + ByteBuffer tmp = ByteBuffer.allocate( b.remaining() + unmaskedpayload.capacity() ); + unmaskedpayload.flip(); + tmp.put( unmaskedpayload ); + tmp.put( b ); + unmaskedpayload = tmp; + + } else { + unmaskedpayload.put( b ); + } + unmaskedpayload.rewind(); + b.reset(); + } + fin = nextframe.isFin(); + } + + @Override + public String toString() { + return "Framedata{ optcode:" + getOpcode() + ", fin:" + isFin() + ", payloadlength:[pos:" + unmaskedpayload.position() + ", len:" + unmaskedpayload.remaining() + "], payload:" + Arrays.toString( Charsetfunctions.utf8Bytes( new String( unmaskedpayload.array() ) ) ) + "}"; + } + +} diff --git a/cb-tools/java-source/src/main/java/org/java_websocket/handshake/ClientHandshake.java b/cb-tools/java-source/src/main/java/org/java_websocket/handshake/ClientHandshake.java new file mode 100644 index 00000000..918d2218 --- /dev/null +++ b/cb-tools/java-source/src/main/java/org/java_websocket/handshake/ClientHandshake.java @@ -0,0 +1,6 @@ +package org.java_websocket.handshake; + +public interface ClientHandshake extends Handshakedata { + /**returns the HTTP Request-URI as defined by http://tools.ietf.org/html/rfc2616#section-5.1.2*/ + public String getResourceDescriptor(); +} diff --git a/cb-tools/java-source/src/main/java/org/java_websocket/handshake/ClientHandshakeBuilder.java b/cb-tools/java-source/src/main/java/org/java_websocket/handshake/ClientHandshakeBuilder.java new file mode 100644 index 00000000..88ac4f27 --- /dev/null +++ b/cb-tools/java-source/src/main/java/org/java_websocket/handshake/ClientHandshakeBuilder.java @@ -0,0 +1,5 @@ +package org.java_websocket.handshake; + +public interface ClientHandshakeBuilder extends HandshakeBuilder, ClientHandshake { + public void setResourceDescriptor( String resourceDescriptor ); +} diff --git a/cb-tools/java-source/src/main/java/org/java_websocket/handshake/HandshakeBuilder.java b/cb-tools/java-source/src/main/java/org/java_websocket/handshake/HandshakeBuilder.java new file mode 100644 index 00000000..8a6236ca --- /dev/null +++ b/cb-tools/java-source/src/main/java/org/java_websocket/handshake/HandshakeBuilder.java @@ -0,0 +1,6 @@ +package org.java_websocket.handshake; + +public interface HandshakeBuilder extends Handshakedata { + public abstract void setContent( byte[] content ); + public abstract void put( String name, String value ); +} diff --git a/cb-tools/java-source/src/main/java/org/java_websocket/handshake/HandshakeImpl1Client.java b/cb-tools/java-source/src/main/java/org/java_websocket/handshake/HandshakeImpl1Client.java new file mode 100644 index 00000000..15715e37 --- /dev/null +++ b/cb-tools/java-source/src/main/java/org/java_websocket/handshake/HandshakeImpl1Client.java @@ -0,0 +1,18 @@ +package org.java_websocket.handshake; + +public class HandshakeImpl1Client extends HandshakedataImpl1 implements ClientHandshakeBuilder { + private String resourceDescriptor = "*"; + + public HandshakeImpl1Client() { + } + + public void setResourceDescriptor( String resourceDescriptor ) throws IllegalArgumentException { + if(resourceDescriptor==null) + throw new IllegalArgumentException( "http resource descriptor must not be null" ); + this.resourceDescriptor = resourceDescriptor; + } + + public String getResourceDescriptor() { + return resourceDescriptor; + } +} diff --git a/cb-tools/java-source/src/main/java/org/java_websocket/handshake/HandshakeImpl1Server.java b/cb-tools/java-source/src/main/java/org/java_websocket/handshake/HandshakeImpl1Server.java new file mode 100644 index 00000000..7063b892 --- /dev/null +++ b/cb-tools/java-source/src/main/java/org/java_websocket/handshake/HandshakeImpl1Server.java @@ -0,0 +1,29 @@ +package org.java_websocket.handshake; + +public class HandshakeImpl1Server extends HandshakedataImpl1 implements ServerHandshakeBuilder { + private short httpstatus; + private String httpstatusmessage; + + public HandshakeImpl1Server() { + } + + @Override + public String getHttpStatusMessage() { + return httpstatusmessage; + } + + @Override + public short getHttpStatus() { + return httpstatus; + } + + public void setHttpStatusMessage( String message ) { + this.httpstatusmessage = message; + } + + public void setHttpStatus( short status ) { + httpstatus = status; + } + + +} diff --git a/cb-tools/java-source/src/main/java/org/java_websocket/handshake/Handshakedata.java b/cb-tools/java-source/src/main/java/org/java_websocket/handshake/Handshakedata.java new file mode 100644 index 00000000..577d6ce1 --- /dev/null +++ b/cb-tools/java-source/src/main/java/org/java_websocket/handshake/Handshakedata.java @@ -0,0 +1,10 @@ +package org.java_websocket.handshake; + +import java.util.Iterator; + +public interface Handshakedata { + public Iterator<String> iterateHttpFields(); + public String getFieldValue( String name ); + public boolean hasFieldValue( String name ); + public byte[] getContent(); +} diff --git a/cb-tools/java-source/src/main/java/org/java_websocket/handshake/HandshakedataImpl1.java b/cb-tools/java-source/src/main/java/org/java_websocket/handshake/HandshakedataImpl1.java new file mode 100644 index 00000000..d4d9555c --- /dev/null +++ b/cb-tools/java-source/src/main/java/org/java_websocket/handshake/HandshakedataImpl1.java @@ -0,0 +1,60 @@ +package org.java_websocket.handshake; + +import java.util.Collections; +import java.util.Iterator; +import java.util.TreeMap; + +public class HandshakedataImpl1 implements HandshakeBuilder { + private byte[] content; + private TreeMap<String,String> map; + + public HandshakedataImpl1() { + map = new TreeMap<String,String>( String.CASE_INSENSITIVE_ORDER ); + } + + /*public HandshakedataImpl1( Handshakedata h ) { + httpstatusmessage = h.getHttpStatusMessage(); + resourcedescriptor = h.getResourceDescriptor(); + content = h.getContent(); + map = new LinkedHashMap<String,String>(); + Iterator<String> it = h.iterateHttpFields(); + while ( it.hasNext() ) { + String key = (String) it.next(); + map.put( key, h.getFieldValue( key ) ); + } + }*/ + + @Override + public Iterator<String> iterateHttpFields() { + return Collections.unmodifiableSet( map.keySet() ).iterator();// Safety first + } + + @Override + public String getFieldValue( String name ) { + String s = map.get( name ); + if ( s == null ) { + return ""; + } + return s; + } + + @Override + public byte[] getContent() { + return content; + } + + @Override + public void setContent( byte[] content ) { + this.content = content; + } + + @Override + public void put( String name, String value ) { + map.put( name, value ); + } + + @Override + public boolean hasFieldValue( String name ) { + return map.containsKey( name ); + } +} diff --git a/cb-tools/java-source/src/main/java/org/java_websocket/handshake/ServerHandshake.java b/cb-tools/java-source/src/main/java/org/java_websocket/handshake/ServerHandshake.java new file mode 100644 index 00000000..880e9b2d --- /dev/null +++ b/cb-tools/java-source/src/main/java/org/java_websocket/handshake/ServerHandshake.java @@ -0,0 +1,6 @@ +package org.java_websocket.handshake; + +public interface ServerHandshake extends Handshakedata { + public short getHttpStatus(); + public String getHttpStatusMessage(); +} diff --git a/cb-tools/java-source/src/main/java/org/java_websocket/handshake/ServerHandshakeBuilder.java b/cb-tools/java-source/src/main/java/org/java_websocket/handshake/ServerHandshakeBuilder.java new file mode 100644 index 00000000..d518dfb1 --- /dev/null +++ b/cb-tools/java-source/src/main/java/org/java_websocket/handshake/ServerHandshakeBuilder.java @@ -0,0 +1,6 @@ +package org.java_websocket.handshake; + +public interface ServerHandshakeBuilder extends HandshakeBuilder, ServerHandshake { + public void setHttpStatus( short status ); + public void setHttpStatusMessage( String message ); +} diff --git a/cb-tools/java-source/src/main/java/org/java_websocket/server/DefaultSSLWebSocketServerFactory.java b/cb-tools/java-source/src/main/java/org/java_websocket/server/DefaultSSLWebSocketServerFactory.java new file mode 100644 index 00000000..b871260f --- /dev/null +++ b/cb-tools/java-source/src/main/java/org/java_websocket/server/DefaultSSLWebSocketServerFactory.java @@ -0,0 +1,51 @@ +package org.java_websocket.server; +import java.io.IOException; +import java.net.Socket; +import java.nio.channels.ByteChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.SocketChannel; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; + +import org.java_websocket.SSLSocketChannel2; +import org.java_websocket.WebSocketAdapter; +import org.java_websocket.WebSocketImpl; +import org.java_websocket.drafts.Draft; + + +public class DefaultSSLWebSocketServerFactory implements WebSocketServer.WebSocketServerFactory { + protected SSLContext sslcontext; + protected ExecutorService exec; + + public DefaultSSLWebSocketServerFactory( SSLContext sslContext ) { + this( sslContext, Executors.newSingleThreadScheduledExecutor() ); + } + + public DefaultSSLWebSocketServerFactory( SSLContext sslContext , ExecutorService exec ) { + if( sslContext == null || exec == null ) + throw new IllegalArgumentException(); + this.sslcontext = sslContext; + this.exec = exec; + } + + @Override + public ByteChannel wrapChannel( SocketChannel channel, SelectionKey key ) throws IOException { + SSLEngine e = sslcontext.createSSLEngine(); + e.setUseClientMode( false ); + return new SSLSocketChannel2( channel, e, exec, key ); + } + + @Override + public WebSocketImpl createWebSocket( WebSocketAdapter a, Draft d, Socket c ) { + return new WebSocketImpl( a, d ); + } + + @Override + public WebSocketImpl createWebSocket( WebSocketAdapter a, List<Draft> d, Socket s ) { + return new WebSocketImpl( a, d ); + } +} \ No newline at end of file diff --git a/cb-tools/java-source/src/main/java/org/java_websocket/server/DefaultWebSocketServerFactory.java b/cb-tools/java-source/src/main/java/org/java_websocket/server/DefaultWebSocketServerFactory.java new file mode 100644 index 00000000..3b89cdc2 --- /dev/null +++ b/cb-tools/java-source/src/main/java/org/java_websocket/server/DefaultWebSocketServerFactory.java @@ -0,0 +1,26 @@ +package org.java_websocket.server; + +import java.net.Socket; +import java.nio.channels.SelectionKey; +import java.nio.channels.SocketChannel; +import java.util.List; + +import org.java_websocket.WebSocketAdapter; +import org.java_websocket.WebSocketImpl; +import org.java_websocket.drafts.Draft; +import org.java_websocket.server.WebSocketServer.WebSocketServerFactory; + +public class DefaultWebSocketServerFactory implements WebSocketServerFactory { + @Override + public WebSocketImpl createWebSocket( WebSocketAdapter a, Draft d, Socket s ) { + return new WebSocketImpl( a, d ); + } + @Override + public WebSocketImpl createWebSocket( WebSocketAdapter a, List<Draft> d, Socket s ) { + return new WebSocketImpl( a, d ); + } + @Override + public SocketChannel wrapChannel( SocketChannel channel, SelectionKey key ) { + return (SocketChannel) channel; + } +} \ No newline at end of file diff --git a/cb-tools/java-source/src/main/java/org/java_websocket/server/WebSocketServer.java b/cb-tools/java-source/src/main/java/org/java_websocket/server/WebSocketServer.java new file mode 100644 index 00000000..4444ca61 --- /dev/null +++ b/cb-tools/java-source/src/main/java/org/java_websocket/server/WebSocketServer.java @@ -0,0 +1,743 @@ +package org.java_websocket.server; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.nio.channels.ByteChannel; +import java.nio.channels.CancelledKeyException; +import java.nio.channels.ClosedByInterruptException; +import java.nio.channels.SelectableChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import org.java_websocket.SocketChannelIOHelper; +import org.java_websocket.WebSocket; +import org.java_websocket.WebSocketAdapter; +import org.java_websocket.WebSocketFactory; +import org.java_websocket.WebSocketImpl; +import org.java_websocket.WrappedByteChannel; +import org.java_websocket.drafts.Draft; +import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.framing.CloseFrame; +import org.java_websocket.framing.Framedata; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.Handshakedata; +import org.java_websocket.handshake.ServerHandshakeBuilder; + +/** + * <tt>WebSocketServer</tt> is an abstract class that only takes care of the + * HTTP handshake portion of WebSockets. It's up to a subclass to add + * functionality/purpose to the server. + * + */ +public abstract class WebSocketServer extends WebSocketAdapter implements Runnable { + + public static int DECODERS = Runtime.getRuntime().availableProcessors(); + + /** + * Holds the list of active WebSocket connections. "Active" means WebSocket + * handshake is complete and socket can be written to, or read from. + */ + private final Collection<WebSocket> connections; + /** + * The port number that this WebSocket server should listen on. Default is + * WebSocket.DEFAULT_PORT. + */ + private final InetSocketAddress address; + /** + * The socket channel for this WebSocket server. + */ + private ServerSocketChannel server; + /** + * The 'Selector' used to get event keys from the underlying socket. + */ + private Selector selector; + /** + * The Draft of the WebSocket protocol the Server is adhering to. + */ + private List<Draft> drafts; + + private Thread selectorthread; + + private volatile AtomicBoolean isclosed = new AtomicBoolean( false ); + + private List<WebSocketWorker> decoders; + + private List<WebSocketImpl> iqueue; + private BlockingQueue<ByteBuffer> buffers; + private int queueinvokes = 0; + private AtomicInteger queuesize = new AtomicInteger( 0 ); + + private WebSocketServerFactory wsf = new DefaultWebSocketServerFactory(); + + /** + * Creates a WebSocketServer that will attempt to + * listen on port <var>WebSocket.DEFAULT_PORT</var>. + * + * @see #WebSocketServer(InetSocketAddress, int, List, Collection) more details here + */ + public WebSocketServer() throws UnknownHostException { + this( new InetSocketAddress( WebSocket.DEFAULT_PORT ), DECODERS, null ); + } + + /** + * Creates a WebSocketServer that will attempt to bind/listen on the given <var>address</var>. + * + * @see #WebSocketServer(InetSocketAddress, int, List, Collection) more details here + */ + public WebSocketServer( InetSocketAddress address ) { + this( address, DECODERS, null ); + } + + /** + * @see #WebSocketServer(InetSocketAddress, int, List, Collection) more details here + */ + public WebSocketServer( InetSocketAddress address , int decoders ) { + this( address, decoders, null ); + } + + /** + * @see #WebSocketServer(InetSocketAddress, int, List, Collection) more details here + */ + public WebSocketServer( InetSocketAddress address , List<Draft> drafts ) { + this( address, DECODERS, drafts ); + } + + /** + * @see #WebSocketServer(InetSocketAddress, int, List, Collection) more details here + */ + public WebSocketServer( InetSocketAddress address , int decodercount , List<Draft> drafts ) { + this( address, decodercount, drafts, new HashSet<WebSocket>() ); + } + + /** + * Creates a WebSocketServer that will attempt to bind/listen on the given <var>address</var>, + * and comply with <tt>Draft</tt> version <var>draft</var>. + * + * @param address + * The address (host:port) this server should listen on. + * @param decodercount + * The number of {@link WebSocketWorker}s that will be used to process the incoming network data. By default this will be <code>Runtime.getRuntime().availableProcessors()</code> + * @param drafts + * The versions of the WebSocket protocol that this server + * instance should comply to. Clients that use an other protocol version will be rejected. + * + * @param connectionscontainer + * Allows to specify a collection that will be used to store the websockets in. <br> + * If you plan to often iterate through the currently connected websockets you may want to use a collection that does not require synchronization like a {@link CopyOnWriteArraySet}. In that case make sure that you overload {@link #removeConnection(WebSocket)} and {@link #addConnection(WebSocket)}.<br> + * By default a {@link HashSet} will be used. + * + * @see #removeConnection(WebSocket) for more control over syncronized operation + * @see <a href="https://github.com/TooTallNate/Java-WebSocket/wiki/Drafts" > more about drafts + */ + public WebSocketServer( InetSocketAddress address , int decodercount , List<Draft> drafts , Collection<WebSocket> connectionscontainer ) { + if( address == null || decodercount < 1 || connectionscontainer == null ) { + throw new IllegalArgumentException( "address and connectionscontainer must not be null and you need at least 1 decoder" ); + } + + if( drafts == null ) + this.drafts = Collections.emptyList(); + else + this.drafts = drafts; + + this.address = address; + this.connections = connectionscontainer; + + iqueue = new LinkedList<WebSocketImpl>(); + + decoders = new ArrayList<WebSocketWorker>( decodercount ); + buffers = new LinkedBlockingQueue<ByteBuffer>(); + for( int i = 0 ; i < decodercount ; i++ ) { + WebSocketWorker ex = new WebSocketWorker(); + decoders.add( ex ); + ex.start(); + } + } + + /** + * Starts the server selectorthread that binds to the currently set port number and + * listeners for WebSocket connection requests. Creates a fixed thread pool with the size {@link WebSocketServer#DECODERS}<br> + * May only be called once. + * + * Alternatively you can call {@link WebSocketServer#run()} directly. + * + * @throws IllegalStateException + */ + public void start() { + if( selectorthread != null ) + throw new IllegalStateException( getClass().getName() + " can only be started once." ); + new Thread( this ).start();; + } + + /** + * Closes all connected clients sockets, then closes the underlying + * ServerSocketChannel, effectively killing the server socket selectorthread, + * freeing the port the server was bound to and stops all internal workerthreads. + * + * If this method is called before the server is started it will never start. + * + * @param timeout + * Specifies how many milliseconds the overall close handshaking may take altogether before the connections are closed without proper close handshaking.<br> + * + * @throws IOException + * When {@link ServerSocketChannel}.close throws an IOException + * @throws InterruptedException + */ + public void stop( int timeout ) throws InterruptedException { + if( !isclosed.compareAndSet( false, true ) ) { // this also makes sure that no further connections will be added to this.connections + return; + } + + List<WebSocket> socketsToClose = null; + + // copy the connections in a list (prevent callback deadlocks) + synchronized ( connections ) { + socketsToClose = new ArrayList<WebSocket>( connections ); + } + + for( WebSocket ws : socketsToClose ) { + ws.close( CloseFrame.GOING_AWAY ); + } + + synchronized ( this ) { + if( selectorthread != null ) { + if( Thread.currentThread() != selectorthread ) { + + } + if( selectorthread != Thread.currentThread() ) { + if( socketsToClose.size() > 0 ) + selectorthread.join( timeout );// isclosed will tell the selectorthread to go down after the last connection was closed + selectorthread.interrupt();// in case the selectorthread did not terminate in time we send the interrupt + selectorthread.join(); + } + } + } + } + public void stop() throws IOException , InterruptedException { + stop( 0 ); + } + + /** + * Returns a WebSocket[] of currently connected clients. + * Its iterators will be failfast and its not judicious + * to modify it. + * + * @return The currently connected clients. + */ + public Collection<WebSocket> connections() { + return this.connections; + } + + public InetSocketAddress getAddress() { + return this.address; + } + + /** + * Gets the port number that this server listens on. + * + * @return The port number. + */ + public int getPort() { + int port = getAddress().getPort(); + if( port == 0 && server != null ) { + port = server.socket().getLocalPort(); + } + return port; + } + + public List<Draft> getDraft() { + return Collections.unmodifiableList( drafts ); + } + + // Runnable IMPLEMENTATION ///////////////////////////////////////////////// + public void run() { + synchronized ( this ) { + if( selectorthread != null ) + throw new IllegalStateException( getClass().getName() + " can only be started once." ); + selectorthread = Thread.currentThread(); + if( isclosed.get() ) { + return; + } + } + selectorthread.setName( "WebsocketSelector" + selectorthread.getId() ); + try { + server = ServerSocketChannel.open(); + server.configureBlocking( false ); + ServerSocket socket = server.socket(); + socket.setReceiveBufferSize( WebSocketImpl.RCVBUF ); + socket.bind( address ); + selector = Selector.open(); + server.register( selector, server.validOps() ); + } catch ( IOException ex ) { + handleFatal( null, ex ); + return; + } + try { + while ( !selectorthread.isInterrupted() ) { + SelectionKey key = null; + WebSocketImpl conn = null; + try { + selector.select(); + Set<SelectionKey> keys = selector.selectedKeys(); + Iterator<SelectionKey> i = keys.iterator(); + + while ( i.hasNext() ) { + key = i.next(); + + if( !key.isValid() ) { + // Object o = key.attachment(); + continue; + } + + if( key.isAcceptable() ) { + if( !onConnect( key ) ) { + key.cancel(); + continue; + } + + SocketChannel channel = server.accept(); + channel.configureBlocking( false ); + WebSocketImpl w = wsf.createWebSocket( this, drafts, channel.socket() ); + w.key = channel.register( selector, SelectionKey.OP_READ, w ); + w.channel = wsf.wrapChannel( channel, w.key ); + i.remove(); + allocateBuffers( w ); + continue; + } + + if( key.isReadable() ) { + conn = (WebSocketImpl) key.attachment(); + ByteBuffer buf = takeBuffer(); + try { + if( SocketChannelIOHelper.read( buf, conn, conn.channel ) ) { + if( buf.hasRemaining() ) { + conn.inQueue.put( buf ); + queue( conn ); + i.remove(); + if( conn.channel instanceof WrappedByteChannel ) { + if( ( (WrappedByteChannel) conn.channel ).isNeedRead() ) { + iqueue.add( conn ); + } + } + } else + pushBuffer( buf ); + } else { + pushBuffer( buf ); + } + } catch ( IOException e ) { + pushBuffer( buf ); + throw e; + } + } + if( key.isWritable() ) { + conn = (WebSocketImpl) key.attachment(); + if( SocketChannelIOHelper.batch( conn, conn.channel ) ) { + if( key.isValid() ) + key.interestOps( SelectionKey.OP_READ ); + } + } + } + while ( !iqueue.isEmpty() ) { + conn = iqueue.remove( 0 ); + WrappedByteChannel c = ( (WrappedByteChannel) conn.channel ); + ByteBuffer buf = takeBuffer(); + try { + if( SocketChannelIOHelper.readMore( buf, conn, c ) ) + iqueue.add( conn ); + if( buf.hasRemaining() ) { + conn.inQueue.put( buf ); + queue( conn ); + } else { + pushBuffer( buf ); + } + } catch ( IOException e ) { + pushBuffer( buf ); + throw e; + } + + } + } catch ( CancelledKeyException e ) { + // an other thread may cancel the key + } catch ( ClosedByInterruptException e ) { + return; // do the same stuff as when InterruptedException is thrown + } catch ( IOException ex ) { + if( key != null ) + key.cancel(); + handleIOException( key, conn, ex ); + } catch ( InterruptedException e ) { + return;// FIXME controlled shutdown (e.g. take care of buffermanagement) + } + } + + } catch ( RuntimeException e ) { + // should hopefully never occur + handleFatal( null, e ); + } finally { + if( decoders != null ) { + for( WebSocketWorker w : decoders ) { + w.interrupt(); + } + } + if( selector != null ) { + try { + selector.close(); + } catch ( IOException e ) { + onError( null, e ); + } + } + if( server != null ) { + try { + server.close(); + } catch ( IOException e ) { + onError( null, e ); + } + } + } + } + protected void allocateBuffers( WebSocket c ) throws InterruptedException { + if( queuesize.get() >= 2 * decoders.size() + 1 ) { + return; + } + queuesize.incrementAndGet(); + buffers.put( createBuffer() ); + } + + protected void releaseBuffers( WebSocket c ) throws InterruptedException { + // queuesize.decrementAndGet(); + // takeBuffer(); + } + + public ByteBuffer createBuffer() { + return ByteBuffer.allocate( WebSocketImpl.RCVBUF ); + } + + private void queue( WebSocketImpl ws ) throws InterruptedException { + if( ws.workerThread == null ) { + ws.workerThread = decoders.get( queueinvokes % decoders.size() ); + queueinvokes++; + } + ws.workerThread.put( ws ); + } + + private ByteBuffer takeBuffer() throws InterruptedException { + return buffers.take(); + } + + private void pushBuffer( ByteBuffer buf ) throws InterruptedException { + if( buffers.size() > queuesize.intValue() ) + return; + buffers.put( buf ); + } + + private void handleIOException( SelectionKey key, WebSocket conn, IOException ex ) { + // onWebsocketError( conn, ex );// conn may be null here + if( conn != null ) { + conn.closeConnection( CloseFrame.ABNORMAL_CLOSE, ex.getMessage() ); + } else if( key != null ) { + SelectableChannel channel = key.channel(); + if( channel != null && channel.isOpen() ) { // this could be the case if the IOException ex is a SSLException + try { + channel.close(); + } catch ( IOException e ) { + // there is nothing that must be done here + } + if( WebSocketImpl.DEBUG ) + System.out.println( "Connection closed because of" + ex ); + } + } + } + + private void handleFatal( WebSocket conn, Exception e ) { + onError( conn, e ); + try { + stop(); + } catch ( IOException e1 ) { + onError( null, e1 ); + } catch ( InterruptedException e1 ) { + Thread.currentThread().interrupt(); + onError( null, e1 ); + } + } + + /** + * Gets the XML string that should be returned if a client requests a Flash + * security policy. + * + * The default implementation allows access from all remote domains, but + * only on the port that this WebSocketServer is listening on. + * + * This is specifically implemented for gitime's WebSocket client for Flash: + * http://github.com/gimite/web-socket-js + * + * @return An XML String that comforms to Flash's security policy. You MUST + * not include the null char at the end, it is appended automatically. + */ + protected String getFlashSecurityPolicy() { + return "<cross-domain-policy><allow-access-from domain=\"*\" to-ports=\"" + getPort() + "\" /></cross-domain-policy>"; + } + + @Override + public final void onWebsocketMessage( WebSocket conn, String message ) { + onMessage( conn, message ); + } + + @Override + @Deprecated + public/*final*/void onWebsocketMessageFragment( WebSocket conn, Framedata frame ) {// onFragment should be overloaded instead + onFragment( conn, frame ); + } + + @Override + public final void onWebsocketMessage( WebSocket conn, ByteBuffer blob ) { + onMessage( conn, blob ); + } + + @Override + public final void onWebsocketOpen( WebSocket conn, Handshakedata handshake ) { + if( addConnection( conn ) ) { + onOpen( conn, (ClientHandshake) handshake ); + } + } + + @Override + public final void onWebsocketClose( WebSocket conn, int code, String reason, boolean remote ) { + selector.wakeup(); + try { + if( removeConnection( conn ) ) { + onClose( conn, code, reason, remote ); + } + } finally { + try { + releaseBuffers( conn ); + } catch ( InterruptedException e ) { + Thread.currentThread().interrupt(); + } + } + + } + + /** + * This method performs remove operations on the connection and therefore also gives control over whether the operation shall be synchronized + * <p> + * {@link #WebSocketServer(InetSocketAddress, int, List, Collection)} allows to specify a collection which will be used to store current connections in.<br> + * Depending on the type on the connection, modifications of that collection may have to be synchronized. + **/ + protected boolean removeConnection( WebSocket ws ) { + boolean removed; + synchronized ( connections ) { + removed = this.connections.remove( ws ); + assert ( removed ); + } + if( isclosed.get() && connections.size() == 0 ) { + selectorthread.interrupt(); + } + return removed; + } + @Override + public ServerHandshakeBuilder onWebsocketHandshakeReceivedAsServer( WebSocket conn, Draft draft, ClientHandshake request ) throws InvalidDataException { + return super.onWebsocketHandshakeReceivedAsServer( conn, draft, request ); + } + + /** @see #removeConnection(WebSocket) */ + protected boolean addConnection( WebSocket ws ) { + if( !isclosed.get() ) { + synchronized ( connections ) { + boolean succ = this.connections.add( ws ); + assert ( succ ); + return succ; + } + } else { + // This case will happen when a new connection gets ready while the server is already stopping. + ws.close( CloseFrame.GOING_AWAY ); + return true;// for consistency sake we will make sure that both onOpen will be called + } + } + /** + * @param conn + * may be null if the error does not belong to a single connection + */ + @Override + public final void onWebsocketError( WebSocket conn, Exception ex ) { + onError( conn, ex ); + } + + @Override + public final void onWriteDemand( WebSocket w ) { + WebSocketImpl conn = (WebSocketImpl) w; + try { + conn.key.interestOps( SelectionKey.OP_READ | SelectionKey.OP_WRITE ); + } catch ( CancelledKeyException e ) { + // the thread which cancels key is responsible for possible cleanup + conn.outQueue.clear(); + } + selector.wakeup(); + } + + @Override + public void onWebsocketCloseInitiated( WebSocket conn, int code, String reason ) { + onCloseInitiated( conn, code, reason ); + } + + @Override + public void onWebsocketClosing( WebSocket conn, int code, String reason, boolean remote ) { + onClosing( conn, code, reason, remote ); + + } + + public void onCloseInitiated( WebSocket conn, int code, String reason ) { + } + + public void onClosing( WebSocket conn, int code, String reason, boolean remote ) { + + } + + public final void setWebSocketFactory( WebSocketServerFactory wsf ) { + this.wsf = wsf; + } + + public final WebSocketFactory getWebSocketFactory() { + return wsf; + } + + /** + * Returns whether a new connection shall be accepted or not.<br> + * Therefore method is well suited to implement some kind of connection limitation.<br> + * + * @see {@link #onOpen(WebSocket, ClientHandshake)}, {@link #onWebsocketHandshakeReceivedAsServer(WebSocket, Draft, ClientHandshake)} + **/ + protected boolean onConnect( SelectionKey key ) { + return true; + } + + private Socket getSocket( WebSocket conn ) { + WebSocketImpl impl = (WebSocketImpl) conn; + return ( (SocketChannel) impl.key.channel() ).socket(); + } + + @Override + public InetSocketAddress getLocalSocketAddress( WebSocket conn ) { + return (InetSocketAddress) getSocket( conn ).getLocalSocketAddress(); + } + + @Override + public InetSocketAddress getRemoteSocketAddress( WebSocket conn ) { + return (InetSocketAddress) getSocket( conn ).getRemoteSocketAddress(); + } + + /** Called after an opening handshake has been performed and the given websocket is ready to be written on. */ + public abstract void onOpen( WebSocket conn, ClientHandshake handshake ); + /** + * Called after the websocket connection has been closed. + * + * @param code + * The codes can be looked up here: {@link CloseFrame} + * @param reason + * Additional information string + * @param remote + * Returns whether or not the closing of the connection was initiated by the remote host. + **/ + public abstract void onClose( WebSocket conn, int code, String reason, boolean remote ); + /** + * Callback for string messages received from the remote host + * + * @see #onMessage(WebSocket, ByteBuffer) + **/ + public abstract void onMessage( WebSocket conn, String message ); + /** + * Called when errors occurs. If an error causes the websocket connection to fail {@link #onClose(WebSocket, int, String, boolean)} will be called additionally.<br> + * This method will be called primarily because of IO or protocol errors.<br> + * If the given exception is an RuntimeException that probably means that you encountered a bug.<br> + * + * @param con + * Can be null if there error does not belong to one specific websocket. For example if the servers port could not be bound. + **/ + public abstract void onError( WebSocket conn, Exception ex ); + /** + * Callback for binary messages received from the remote host + * + * @see #onMessage(WebSocket, String) + **/ + public void onMessage( WebSocket conn, ByteBuffer message ) { + } + + /** + * @see WebSocket#sendFragmentedFrame(org.java_websocket.framing.Framedata.Opcode, ByteBuffer, boolean) + */ + public void onFragment( WebSocket conn, Framedata fragment ) { + } + + public class WebSocketWorker extends Thread { + + private BlockingQueue<WebSocketImpl> iqueue; + + public WebSocketWorker() { + iqueue = new LinkedBlockingQueue<WebSocketImpl>(); + setName( "WebSocketWorker-" + getId() ); + setUncaughtExceptionHandler( new UncaughtExceptionHandler() { + @Override + public void uncaughtException( Thread t, Throwable e ) { + getDefaultUncaughtExceptionHandler().uncaughtException( t, e ); + } + } ); + } + + public void put( WebSocketImpl ws ) throws InterruptedException { + iqueue.put( ws ); + } + + @Override + public void run() { + WebSocketImpl ws = null; + try { + while ( true ) { + ByteBuffer buf = null; + ws = iqueue.take(); + buf = ws.inQueue.poll(); + assert ( buf != null ); + try { + ws.decode( buf ); + } finally { + pushBuffer( buf ); + } + } + } catch ( InterruptedException e ) { + } catch ( RuntimeException e ) { + handleFatal( ws, e ); + } + } + } + + public interface WebSocketServerFactory extends WebSocketFactory { + @Override + public WebSocketImpl createWebSocket( WebSocketAdapter a, Draft d, Socket s ); + + public WebSocketImpl createWebSocket( WebSocketAdapter a, List<Draft> drafts, Socket s ); + + /** + * Allows to wrap the Socketchannel( key.channel() ) to insert a protocol layer( like ssl or proxy authentication) beyond the ws layer. + * + * @param key + * a SelectionKey of an open SocketChannel. + * @return The channel on which the read and write operations will be performed.<br> + */ + public ByteChannel wrapChannel( SocketChannel channel, SelectionKey key ) throws IOException; + } +} diff --git a/cb-tools/java-source/src/main/java/org/java_websocket/util/Base64.java b/cb-tools/java-source/src/main/java/org/java_websocket/util/Base64.java new file mode 100644 index 00000000..38d06ae2 --- /dev/null +++ b/cb-tools/java-source/src/main/java/org/java_websocket/util/Base64.java @@ -0,0 +1,2065 @@ +package org.java_websocket.util; + +/** + * <p>Encodes and decodes to and from Base64 notation.</p> + * <p>Homepage: <a href="http://iharder.net/base64">http://iharder.net/base64</a>.</p> + * + * <p>Example:</p> + * + * <code>String encoded = Base64.encode( myByteArray );</code> + * <br /> + * <code>byte[] myByteArray = Base64.decode( encoded );</code> + * + * <p>The <tt>options</tt> parameter, which appears in a few places, is used to pass + * several pieces of information to the encoder. In the "higher level" methods such as + * encodeBytes( bytes, options ) the options parameter can be used to indicate such + * things as first gzipping the bytes before encoding them, not inserting linefeeds, + * and encoding using the URL-safe and Ordered dialects.</p> + * + * <p>Note, according to <a href="http://www.faqs.org/rfcs/rfc3548.html">RFC3548</a>, + * Section 2.1, implementations should not add line feeds unless explicitly told + * to do so. I've got Base64 set to this behavior now, although earlier versions + * broke lines by default.</p> + * + * <p>The constants defined in Base64 can be OR-ed together to combine options, so you + * might make a call like this:</p> + * + * <code>String encoded = Base64.encodeBytes( mybytes, Base64.GZIP | Base64.DO_BREAK_LINES );</code> + * <p>to compress the data before encoding it and then making the output have newline characters.</p> + * <p>Also...</p> + * <code>String encoded = Base64.encodeBytes( crazyString.getBytes() );</code> + * + * + * + * <p> + * Change Log: + * </p> + * <ul> + * <li>v2.3.7 - Fixed subtle bug when base 64 input stream contained the + * value 01111111, which is an invalid base 64 character but should not + * throw an ArrayIndexOutOfBoundsException either. Led to discovery of + * mishandling (or potential for better handling) of other bad input + * characters. You should now get an IOException if you try decoding + * something that has bad characters in it.</li> + * <li>v2.3.6 - Fixed bug when breaking lines and the final byte of the encoded + * string ended in the last column; the buffer was not properly shrunk and + * contained an extra (null) byte that made it into the string.</li> + * <li>v2.3.5 - Fixed bug in {@link #encodeFromFile} where estimated buffer size + * was wrong for files of size 31, 34, and 37 bytes.</li> + * <li>v2.3.4 - Fixed bug when working with gzipped streams whereby flushing + * the Base64.OutputStream closed the Base64 encoding (by padding with equals + * signs) too soon. Also added an option to suppress the automatic decoding + * of gzipped streams. Also added experimental support for specifying a + * class loader when using the + * {@link #decodeToObject(java.lang.String, int, java.lang.ClassLoader)} + * method.</li> + * <li>v2.3.3 - Changed default char encoding to US-ASCII which reduces the internal Java + * footprint with its CharEncoders and so forth. Fixed some javadocs that were + * inconsistent. Removed imports and specified things like java.io.IOException + * explicitly inline.</li> + * <li>v2.3.2 - Reduced memory footprint! Finally refined the "guessing" of how big the + * final encoded data will be so that the code doesn't have to create two output + * arrays: an oversized initial one and then a final, exact-sized one. Big win + * when using the {@link #encodeBytesToBytes(byte[])} family of methods (and not + * using the gzip options which uses a different mechanism with streams and stuff).</li> + * <li>v2.3.1 - Added {@link #encodeBytesToBytes(byte[], int, int, int)} and some + * similar helper methods to be more efficient with memory by not returning a + * String but just a byte array.</li> + * <li>v2.3 - <strong>This is not a drop-in replacement!</strong> This is two years of comments + * and bug fixes queued up and finally executed. Thanks to everyone who sent + * me stuff, and I'm sorry I wasn't able to distribute your fixes to everyone else. + * Much bad coding was cleaned up including throwing exceptions where necessary + * instead of returning null values or something similar. Here are some changes + * that may affect you: + * <ul> + * <li><em>Does not break lines, by default.</em> This is to keep in compliance with + * <a href="http://www.faqs.org/rfcs/rfc3548.html">RFC3548</a>.</li> + * <li><em>Throws exceptions instead of returning null values.</em> Because some operations + * (especially those that may permit the GZIP option) use IO streams, there + * is a possiblity of an java.io.IOException being thrown. After some discussion and + * thought, I've changed the behavior of the methods to throw java.io.IOExceptions + * rather than return null if ever there's an error. I think this is more + * appropriate, though it will require some changes to your code. Sorry, + * it should have been done this way to begin with.</li> + * <li><em>Removed all references to System.out, System.err, and the like.</em> + * Shame on me. All I can say is sorry they were ever there.</li> + * <li><em>Throws NullPointerExceptions and IllegalArgumentExceptions</em> as needed + * such as when passed arrays are null or offsets are invalid.</li> + * <li>Cleaned up as much javadoc as I could to avoid any javadoc warnings. + * This was especially annoying before for people who were thorough in their + * own projects and then had gobs of javadoc warnings on this file.</li> + * </ul> + * <li>v2.2.1 - Fixed bug using URL_SAFE and ORDERED encodings. Fixed bug + * when using very small files (~< 40 bytes).</li> + * <li>v2.2 - Added some helper methods for encoding/decoding directly from + * one file to the next. Also added a main() method to support command line + * encoding/decoding from one file to the next. Also added these Base64 dialects: + * <ol> + * <li>The default is RFC3548 format.</li> + * <li>Calling Base64.setFormat(Base64.BASE64_FORMAT.URLSAFE_FORMAT) generates + * URL and file name friendly format as described in Section 4 of RFC3548. + * http://www.faqs.org/rfcs/rfc3548.html</li> + * <li>Calling Base64.setFormat(Base64.BASE64_FORMAT.ORDERED_FORMAT) generates + * URL and file name friendly format that preserves lexical ordering as described + * in http://www.faqs.org/qa/rfcc-1940.html</li> + * </ol> + * Special thanks to Jim Kellerman at <a href="http://www.powerset.com/">http://www.powerset.com/</a> + * for contributing the new Base64 dialects. + * </li> + * + * <li>v2.1 - Cleaned up javadoc comments and unused variables and methods. Added + * some convenience methods for reading and writing to and from files.</li> + * <li>v2.0.2 - Now specifies UTF-8 encoding in places where the code fails on systems + * with other encodings (like EBCDIC).</li> + * <li>v2.0.1 - Fixed an error when decoding a single byte, that is, when the + * encoded data was a single byte.</li> + * <li>v2.0 - I got rid of methods that used booleans to set options. + * Now everything is more consolidated and cleaner. The code now detects + * when data that's being decoded is gzip-compressed and will decompress it + * automatically. Generally things are cleaner. You'll probably have to + * change some method calls that you were making to support the new + * options format (<tt>int</tt>s that you "OR" together).</li> + * <li>v1.5.1 - Fixed bug when decompressing and decoding to a + * byte[] using <tt>decode( String s, boolean gzipCompressed )</tt>. + * Added the ability to "suspend" encoding in the Output Stream so + * you can turn on and off the encoding if you need to embed base64 + * data in an otherwise "normal" stream (like an XML file).</li> + * <li>v1.5 - Output stream pases on flush() command but doesn't do anything itself. + * This helps when using GZIP streams. + * Added the ability to GZip-compress objects before encoding them.</li> + * <li>v1.4 - Added helper methods to read/write files.</li> + * <li>v1.3.6 - Fixed OutputStream.flush() so that 'position' is reset.</li> + * <li>v1.3.5 - Added flag to turn on and off line breaks. Fixed bug in input stream + * where last buffer being read, if not completely full, was not returned.</li> + * <li>v1.3.4 - Fixed when "improperly padded stream" error was thrown at the wrong time.</li> + * <li>v1.3.3 - Fixed I/O streams which were totally messed up.</li> + * </ul> + * + * <p> + * I am placing this code in the Public Domain. Do with it as you will. + * This software comes with no guarantees or warranties but with + * plenty of well-wishing instead! + * Please visit <a href="http://iharder.net/base64">http://iharder.net/base64</a> + * periodically to check for updates or to contribute improvements. + * </p> + * + * @author Robert Harder + * @author rob@iharder.net + * @version 2.3.7 + */ +public class Base64 +{ + +/* ******** P U B L I C F I E L D S ******** */ + + + /** No options specified. Value is zero. */ + public final static int NO_OPTIONS = 0; + + /** Specify encoding in first bit. Value is one. */ + public final static int ENCODE = 1; + + + /** Specify decoding in first bit. Value is zero. */ + public final static int DECODE = 0; + + + /** Specify that data should be gzip-compressed in second bit. Value is two. */ + public final static int GZIP = 2; + + /** Specify that gzipped data should <em>not</em> be automatically gunzipped. */ + public final static int DONT_GUNZIP = 4; + + + /** Do break lines when encoding. Value is 8. */ + public final static int DO_BREAK_LINES = 8; + + /** + * Encode using Base64-like encoding that is URL- and Filename-safe as described + * in Section 4 of RFC3548: + * <a href="http://www.faqs.org/rfcs/rfc3548.html">http://www.faqs.org/rfcs/rfc3548.html</a>. + * It is important to note that data encoded this way is <em>not</em> officially valid Base64, + * or at the very least should not be called Base64 without also specifying that is + * was encoded using the URL- and Filename-safe dialect. + */ + public final static int URL_SAFE = 16; + + + /** + * Encode using the special "ordered" dialect of Base64 described here: + * <a href="http://www.faqs.org/qa/rfcc-1940.html">http://www.faqs.org/qa/rfcc-1940.html</a>. + */ + public final static int ORDERED = 32; + + +/* ******** P R I V A T E F I E L D S ******** */ + + + /** Maximum line length (76) of Base64 output. */ + private final static int MAX_LINE_LENGTH = 76; + + + /** The equals sign (=) as a byte. */ + private final static byte EQUALS_SIGN = (byte)'='; + + + /** The new line character (\n) as a byte. */ + private final static byte NEW_LINE = (byte)'\n'; + + + /** Preferred encoding. */ + private final static String PREFERRED_ENCODING = "US-ASCII"; + + + private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding + private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding + + +/* ******** S T A N D A R D B A S E 6 4 A L P H A B E T ******** */ + + /** The 64 valid Base64 values. */ + /* Host platform me be something funny like EBCDIC, so we hardcode these values. */ + private final static byte[] _STANDARD_ALPHABET = { + (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', + (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', + (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', + (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', + (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', + (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', + (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', + (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', + (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', + (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'+', (byte)'/' + }; + + + /** + * Translates a Base64 value to either its 6-bit reconstruction value + * or a negative number indicating some other meaning. + **/ + private final static byte[] _STANDARD_DECODABET = { + -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 + -5,-5, // Whitespace: Tab and Linefeed + -9,-9, // Decimal 11 - 12 + -5, // Whitespace: Carriage Return + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 + -9,-9,-9,-9,-9, // Decimal 27 - 31 + -5, // Whitespace: Space + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 + 62, // Plus sign at decimal 43 + -9,-9,-9, // Decimal 44 - 46 + 63, // Slash at decimal 47 + 52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine + -9,-9,-9, // Decimal 58 - 60 + -1, // Equals sign at decimal 61 + -9,-9,-9, // Decimal 62 - 64 + 0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N' + 14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z' + -9,-9,-9,-9,-9,-9, // Decimal 91 - 96 + 26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm' + 39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z' + -9,-9,-9,-9,-9 // Decimal 123 - 127 + ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 + }; + + +/* ******** U R L S A F E B A S E 6 4 A L P H A B E T ******** */ + + /** + * Used in the URL- and Filename-safe dialect described in Section 4 of RFC3548: + * <a href="http://www.faqs.org/rfcs/rfc3548.html">http://www.faqs.org/rfcs/rfc3548.html</a>. + * Notice that the last two bytes become "hyphen" and "underscore" instead of "plus" and "slash." + */ + private final static byte[] _URL_SAFE_ALPHABET = { + (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', + (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', + (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', + (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', + (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', + (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', + (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', + (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', + (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', + (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'-', (byte)'_' + }; + + /** + * Used in decoding URL- and Filename-safe dialects of Base64. + */ + private final static byte[] _URL_SAFE_DECODABET = { + -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 + -5,-5, // Whitespace: Tab and Linefeed + -9,-9, // Decimal 11 - 12 + -5, // Whitespace: Carriage Return + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 + -9,-9,-9,-9,-9, // Decimal 27 - 31 + -5, // Whitespace: Space + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 + -9, // Plus sign at decimal 43 + -9, // Decimal 44 + 62, // Minus sign at decimal 45 + -9, // Decimal 46 + -9, // Slash at decimal 47 + 52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine + -9,-9,-9, // Decimal 58 - 60 + -1, // Equals sign at decimal 61 + -9,-9,-9, // Decimal 62 - 64 + 0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N' + 14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z' + -9,-9,-9,-9, // Decimal 91 - 94 + 63, // Underscore at decimal 95 + -9, // Decimal 96 + 26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm' + 39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z' + -9,-9,-9,-9,-9 // Decimal 123 - 127 + ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 + }; + + + +/* ******** O R D E R E D B A S E 6 4 A L P H A B E T ******** */ + + /** + * I don't get the point of this technique, but someone requested it, + * and it is described here: + * <a href="http://www.faqs.org/qa/rfcc-1940.html">http://www.faqs.org/qa/rfcc-1940.html</a>. + */ + private final static byte[] _ORDERED_ALPHABET = { + (byte)'-', + (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', + (byte)'5', (byte)'6', (byte)'7', (byte)'8', (byte)'9', + (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', + (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', + (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', + (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', + (byte)'_', + (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', + (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', + (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', + (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z' + }; + + /** + * Used in decoding the "ordered" dialect of Base64. + */ + private final static byte[] _ORDERED_DECODABET = { + -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 + -5,-5, // Whitespace: Tab and Linefeed + -9,-9, // Decimal 11 - 12 + -5, // Whitespace: Carriage Return + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 + -9,-9,-9,-9,-9, // Decimal 27 - 31 + -5, // Whitespace: Space + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 + -9, // Plus sign at decimal 43 + -9, // Decimal 44 + 0, // Minus sign at decimal 45 + -9, // Decimal 46 + -9, // Slash at decimal 47 + 1,2,3,4,5,6,7,8,9,10, // Numbers zero through nine + -9,-9,-9, // Decimal 58 - 60 + -1, // Equals sign at decimal 61 + -9,-9,-9, // Decimal 62 - 64 + 11,12,13,14,15,16,17,18,19,20,21,22,23, // Letters 'A' through 'M' + 24,25,26,27,28,29,30,31,32,33,34,35,36, // Letters 'N' through 'Z' + -9,-9,-9,-9, // Decimal 91 - 94 + 37, // Underscore at decimal 95 + -9, // Decimal 96 + 38,39,40,41,42,43,44,45,46,47,48,49,50, // Letters 'a' through 'm' + 51,52,53,54,55,56,57,58,59,60,61,62,63, // Letters 'n' through 'z' + -9,-9,-9,-9,-9 // Decimal 123 - 127 + ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 + }; + + +/* ******** D E T E R M I N E W H I C H A L H A B E T ******** */ + + + /** + * Returns one of the _SOMETHING_ALPHABET byte arrays depending on + * the options specified. + * It's possible, though silly, to specify ORDERED <b>and</b> URLSAFE + * in which case one of them will be picked, though there is + * no guarantee as to which one will be picked. + */ + private final static byte[] getAlphabet( int options ) { + if ((options & URL_SAFE) == URL_SAFE) { + return _URL_SAFE_ALPHABET; + } else if ((options & ORDERED) == ORDERED) { + return _ORDERED_ALPHABET; + } else { + return _STANDARD_ALPHABET; + } + } // end getAlphabet + + + /** + * Returns one of the _SOMETHING_DECODABET byte arrays depending on + * the options specified. + * It's possible, though silly, to specify ORDERED and URL_SAFE + * in which case one of them will be picked, though there is + * no guarantee as to which one will be picked. + */ + private final static byte[] getDecodabet( int options ) { + if( (options & URL_SAFE) == URL_SAFE) { + return _URL_SAFE_DECODABET; + } else if ((options & ORDERED) == ORDERED) { + return _ORDERED_DECODABET; + } else { + return _STANDARD_DECODABET; + } + } // end getAlphabet + + + + /** Defeats instantiation. */ + private Base64(){} + + + + +/* ******** E N C O D I N G M E T H O D S ******** */ + + + /** + * Encodes up to the first three bytes of array <var>threeBytes</var> + * and returns a four-byte array in Base64 notation. + * The actual number of significant bytes in your array is + * given by <var>numSigBytes</var>. + * The array <var>threeBytes</var> needs only be as big as + * <var>numSigBytes</var>. + * Code can reuse a byte array by passing a four-byte array as <var>b4</var>. + * + * @param b4 A reusable byte array to reduce array instantiation + * @param threeBytes the array to convert + * @param numSigBytes the number of significant bytes in your array + * @return four byte array in Base64 notation. + * @since 1.5.1 + */ + private static byte[] encode3to4( byte[] b4, byte[] threeBytes, int numSigBytes, int options ) { + encode3to4( threeBytes, 0, numSigBytes, b4, 0, options ); + return b4; + } // end encode3to4 + + + /** + * <p>Encodes up to three bytes of the array <var>source</var> + * and writes the resulting four Base64 bytes to <var>destination</var>. + * The source and destination arrays can be manipulated + * anywhere along their length by specifying + * <var>srcOffset</var> and <var>destOffset</var>. + * This method does not check to make sure your arrays + * are large enough to accomodate <var>srcOffset</var> + 3 for + * the <var>source</var> array or <var>destOffset</var> + 4 for + * the <var>destination</var> array. + * The actual number of significant bytes in your array is + * given by <var>numSigBytes</var>.</p> + * <p>This is the lowest level of the encoding methods with + * all possible parameters.</p> + * + * @param source the array to convert + * @param srcOffset the index where conversion begins + * @param numSigBytes the number of significant bytes in your array + * @param destination the array to hold the conversion + * @param destOffset the index where output will be put + * @return the <var>destination</var> array + * @since 1.3 + */ + private static byte[] encode3to4( + byte[] source, int srcOffset, int numSigBytes, + byte[] destination, int destOffset, int options ) { + + byte[] ALPHABET = getAlphabet( options ); + + // 1 2 3 + // 01234567890123456789012345678901 Bit position + // --------000000001111111122222222 Array position from threeBytes + // --------| || || || | Six bit groups to index ALPHABET + // >>18 >>12 >> 6 >> 0 Right shift necessary + // 0x3f 0x3f 0x3f Additional AND + + // Create buffer with zero-padding if there are only one or two + // significant bytes passed in the array. + // We have to shift left 24 in order to flush out the 1's that appear + // when Java treats a value as negative that is cast from a byte to an int. + int inBuff = ( numSigBytes > 0 ? ((source[ srcOffset ] << 24) >>> 8) : 0 ) + | ( numSigBytes > 1 ? ((source[ srcOffset + 1 ] << 24) >>> 16) : 0 ) + | ( numSigBytes > 2 ? ((source[ srcOffset + 2 ] << 24) >>> 24) : 0 ); + + switch( numSigBytes ) + { + case 3: + destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; + destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; + destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ]; + destination[ destOffset + 3 ] = ALPHABET[ (inBuff ) & 0x3f ]; + return destination; + + case 2: + destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; + destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; + destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ]; + destination[ destOffset + 3 ] = EQUALS_SIGN; + return destination; + + case 1: + destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; + destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; + destination[ destOffset + 2 ] = EQUALS_SIGN; + destination[ destOffset + 3 ] = EQUALS_SIGN; + return destination; + + default: + return destination; + } // end switch + } // end encode3to4 + + + + /** + * Performs Base64 encoding on the <code>raw</code> ByteBuffer, + * writing it to the <code>encoded</code> ByteBuffer. + * This is an experimental feature. Currently it does not + * pass along any options (such as {@link #DO_BREAK_LINES} + * or {@link #GZIP}. + * + * @param raw input buffer + * @param encoded output buffer + * @since 2.3 + */ + public static void encode( java.nio.ByteBuffer raw, java.nio.ByteBuffer encoded ){ + byte[] raw3 = new byte[3]; + byte[] enc4 = new byte[4]; + + while( raw.hasRemaining() ){ + int rem = Math.min(3,raw.remaining()); + raw.get(raw3,0,rem); + Base64.encode3to4(enc4, raw3, rem, Base64.NO_OPTIONS ); + encoded.put(enc4); + } // end input remaining + } + + + /** + * Performs Base64 encoding on the <code>raw</code> ByteBuffer, + * writing it to the <code>encoded</code> CharBuffer. + * This is an experimental feature. Currently it does not + * pass along any options (such as {@link #DO_BREAK_LINES} + * or {@link #GZIP}. + * + * @param raw input buffer + * @param encoded output buffer + * @since 2.3 + */ + public static void encode( java.nio.ByteBuffer raw, java.nio.CharBuffer encoded ){ + byte[] raw3 = new byte[3]; + byte[] enc4 = new byte[4]; + + while( raw.hasRemaining() ){ + int rem = Math.min(3,raw.remaining()); + raw.get(raw3,0,rem); + Base64.encode3to4(enc4, raw3, rem, Base64.NO_OPTIONS ); + for( int i = 0; i < 4; i++ ){ + encoded.put( (char)(enc4[i] & 0xFF) ); + } + } // end input remaining + } + + + + + /** + * Serializes an object and returns the Base64-encoded + * version of that serialized object. + * + * <p>As of v 2.3, if the object + * cannot be serialized or there is another error, + * the method will throw an java.io.IOException. <b>This is new to v2.3!</b> + * In earlier versions, it just returned a null value, but + * in retrospect that's a pretty poor way to handle it.</p> + * + * The object is not GZip-compressed before being encoded. + * + * @param serializableObject The object to encode + * @return The Base64-encoded object + * @throws java.io.IOException if there is an error + * @throws NullPointerException if serializedObject is null + * @since 1.4 + */ + public static String encodeObject( java.io.Serializable serializableObject ) + throws java.io.IOException { + return encodeObject( serializableObject, NO_OPTIONS ); + } // end encodeObject + + + + /** + * Serializes an object and returns the Base64-encoded + * version of that serialized object. + * + * <p>As of v 2.3, if the object + * cannot be serialized or there is another error, + * the method will throw an java.io.IOException. <b>This is new to v2.3!</b> + * In earlier versions, it just returned a null value, but + * in retrospect that's a pretty poor way to handle it.</p> + * + * The object is not GZip-compressed before being encoded. + * <p> + * Example options:<pre> + * GZIP: gzip-compresses object before encoding it. + * DO_BREAK_LINES: break lines at 76 characters + * </pre> + * <p> + * Example: <code>encodeObject( myObj, Base64.GZIP )</code> or + * <p> + * Example: <code>encodeObject( myObj, Base64.GZIP | Base64.DO_BREAK_LINES )</code> + * + * @param serializableObject The object to encode + * @param options Specified options + * @return The Base64-encoded object + * @see Base64#GZIP + * @see Base64#DO_BREAK_LINES + * @throws java.io.IOException if there is an error + * @since 2.0 + */ + public static String encodeObject( java.io.Serializable serializableObject, int options ) + throws java.io.IOException { + + if( serializableObject == null ){ + throw new NullPointerException( "Cannot serialize a null object." ); + } // end if: null + + // Streams + java.io.ByteArrayOutputStream baos = null; + java.io.OutputStream b64os = null; + java.util.zip.GZIPOutputStream gzos = null; + java.io.ObjectOutputStream oos = null; + + + try { + // ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream + baos = new java.io.ByteArrayOutputStream(); + b64os = new Base64.OutputStream( baos, ENCODE | options ); + if( (options & GZIP) != 0 ){ + // Gzip + gzos = new java.util.zip.GZIPOutputStream(b64os); + oos = new java.io.ObjectOutputStream( gzos ); + } else { + // Not gzipped + oos = new java.io.ObjectOutputStream( b64os ); + } + oos.writeObject( serializableObject ); + } // end try + catch( java.io.IOException e ) { + // Catch it and then throw it immediately so that + // the finally{} block is called for cleanup. + throw e; + } // end catch + finally { + try{ oos.close(); } catch( Exception e ){} + try{ gzos.close(); } catch( Exception e ){} + try{ b64os.close(); } catch( Exception e ){} + try{ baos.close(); } catch( Exception e ){} + } // end finally + + // Return value according to relevant encoding. + try { + return new String( baos.toByteArray(), PREFERRED_ENCODING ); + } // end try + catch (java.io.UnsupportedEncodingException uue){ + // Fall back to some Java default + return new String( baos.toByteArray() ); + } // end catch + + } // end encode + + + + /** + * Encodes a byte array into Base64 notation. + * Does not GZip-compress data. + * + * @param source The data to convert + * @return The data in Base64-encoded form + * @throws NullPointerException if source array is null + * @since 1.4 + */ + public static String encodeBytes( byte[] source ) { + // Since we're not going to have the GZIP encoding turned on, + // we're not going to have an java.io.IOException thrown, so + // we should not force the user to have to catch it. + String encoded = null; + try { + encoded = encodeBytes(source, 0, source.length, NO_OPTIONS); + } catch (java.io.IOException ex) { + assert false : ex.getMessage(); + } // end catch + assert encoded != null; + return encoded; + } // end encodeBytes + + + + /** + * Encodes a byte array into Base64 notation. + * <p> + * Example options:<pre> + * GZIP: gzip-compresses object before encoding it. + * DO_BREAK_LINES: break lines at 76 characters + * <i>Note: Technically, this makes your encoding non-compliant.</i> + * </pre> + * <p> + * Example: <code>encodeBytes( myData, Base64.GZIP )</code> or + * <p> + * Example: <code>encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES )</code> + * + * + * <p>As of v 2.3, if there is an error with the GZIP stream, + * the method will throw an java.io.IOException. <b>This is new to v2.3!</b> + * In earlier versions, it just returned a null value, but + * in retrospect that's a pretty poor way to handle it.</p> + * + * + * @param source The data to convert + * @param options Specified options + * @return The Base64-encoded data as a String + * @see Base64#GZIP + * @see Base64#DO_BREAK_LINES + * @throws java.io.IOException if there is an error + * @throws NullPointerException if source array is null + * @since 2.0 + */ + public static String encodeBytes( byte[] source, int options ) throws java.io.IOException { + return encodeBytes( source, 0, source.length, options ); + } // end encodeBytes + + + /** + * Encodes a byte array into Base64 notation. + * Does not GZip-compress data. + * + * <p>As of v 2.3, if there is an error, + * the method will throw an java.io.IOException. <b>This is new to v2.3!</b> + * In earlier versions, it just returned a null value, but + * in retrospect that's a pretty poor way to handle it.</p> + * + * + * @param source The data to convert + * @param off Offset in array where conversion should begin + * @param len Length of data to convert + * @return The Base64-encoded data as a String + * @throws NullPointerException if source array is null + * @throws IllegalArgumentException if source array, offset, or length are invalid + * @since 1.4 + */ + public static String encodeBytes( byte[] source, int off, int len ) { + // Since we're not going to have the GZIP encoding turned on, + // we're not going to have an java.io.IOException thrown, so + // we should not force the user to have to catch it. + String encoded = null; + try { + encoded = encodeBytes( source, off, len, NO_OPTIONS ); + } catch (java.io.IOException ex) { + assert false : ex.getMessage(); + } // end catch + assert encoded != null; + return encoded; + } // end encodeBytes + + + + /** + * Encodes a byte array into Base64 notation. + * <p> + * Example options:<pre> + * GZIP: gzip-compresses object before encoding it. + * DO_BREAK_LINES: break lines at 76 characters + * <i>Note: Technically, this makes your encoding non-compliant.</i> + * </pre> + * <p> + * Example: <code>encodeBytes( myData, Base64.GZIP )</code> or + * <p> + * Example: <code>encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES )</code> + * + * + * <p>As of v 2.3, if there is an error with the GZIP stream, + * the method will throw an java.io.IOException. <b>This is new to v2.3!</b> + * In earlier versions, it just returned a null value, but + * in retrospect that's a pretty poor way to handle it.</p> + * + * + * @param source The data to convert + * @param off Offset in array where conversion should begin + * @param len Length of data to convert + * @param options Specified options + * @return The Base64-encoded data as a String + * @see Base64#GZIP + * @see Base64#DO_BREAK_LINES + * @throws java.io.IOException if there is an error + * @throws NullPointerException if source array is null + * @throws IllegalArgumentException if source array, offset, or length are invalid + * @since 2.0 + */ + public static String encodeBytes( byte[] source, int off, int len, int options ) throws java.io.IOException { + byte[] encoded = encodeBytesToBytes( source, off, len, options ); + + // Return value according to relevant encoding. + try { + return new String( encoded, PREFERRED_ENCODING ); + } // end try + catch (java.io.UnsupportedEncodingException uue) { + return new String( encoded ); + } // end catch + + } // end encodeBytes + + + + + /** + * Similar to {@link #encodeBytes(byte[])} but returns + * a byte array instead of instantiating a String. This is more efficient + * if you're working with I/O streams and have large data sets to encode. + * + * + * @param source The data to convert + * @return The Base64-encoded data as a byte[] (of ASCII characters) + * @throws NullPointerException if source array is null + * @since 2.3.1 + */ + public static byte[] encodeBytesToBytes( byte[] source ) { + byte[] encoded = null; + try { + encoded = encodeBytesToBytes( source, 0, source.length, Base64.NO_OPTIONS ); + } catch( java.io.IOException ex ) { + assert false : "IOExceptions only come from GZipping, which is turned off: " + ex.getMessage(); + } + return encoded; + } + + + /** + * Similar to {@link #encodeBytes(byte[], int, int, int)} but returns + * a byte array instead of instantiating a String. This is more efficient + * if you're working with I/O streams and have large data sets to encode. + * + * + * @param source The data to convert + * @param off Offset in array where conversion should begin + * @param len Length of data to convert + * @param options Specified options + * @return The Base64-encoded data as a String + * @see Base64#GZIP + * @see Base64#DO_BREAK_LINES + * @throws java.io.IOException if there is an error + * @throws NullPointerException if source array is null + * @throws IllegalArgumentException if source array, offset, or length are invalid + * @since 2.3.1 + */ + public static byte[] encodeBytesToBytes( byte[] source, int off, int len, int options ) throws java.io.IOException { + + if( source == null ){ + throw new NullPointerException( "Cannot serialize a null array." ); + } // end if: null + + if( off < 0 ){ + throw new IllegalArgumentException( "Cannot have negative offset: " + off ); + } // end if: off < 0 + + if( len < 0 ){ + throw new IllegalArgumentException( "Cannot have length offset: " + len ); + } // end if: len < 0 + + if( off + len > source.length ){ + throw new IllegalArgumentException( + String.format( "Cannot have offset of %d and length of %d with array of length %d", off,len,source.length)); + } // end if: off < 0 + + + + // Compress? + if( (options & GZIP) != 0 ) { + java.io.ByteArrayOutputStream baos = null; + java.util.zip.GZIPOutputStream gzos = null; + Base64.OutputStream b64os = null; + + try { + // GZip -> Base64 -> ByteArray + baos = new java.io.ByteArrayOutputStream(); + b64os = new Base64.OutputStream( baos, ENCODE | options ); + gzos = new java.util.zip.GZIPOutputStream( b64os ); + + gzos.write( source, off, len ); + gzos.close(); + } // end try + catch( java.io.IOException e ) { + // Catch it and then throw it immediately so that + // the finally{} block is called for cleanup. + throw e; + } // end catch + finally { + try{ gzos.close(); } catch( Exception e ){} + try{ b64os.close(); } catch( Exception e ){} + try{ baos.close(); } catch( Exception e ){} + } // end finally + + return baos.toByteArray(); + } // end if: compress + + // Else, don't compress. Better not to use streams at all then. + else { + boolean breakLines = (options & DO_BREAK_LINES) != 0; + + //int len43 = len * 4 / 3; + //byte[] outBuff = new byte[ ( len43 ) // Main 4:3 + // + ( (len % 3) > 0 ? 4 : 0 ) // Account for padding + // + (breakLines ? ( len43 / MAX_LINE_LENGTH ) : 0) ]; // New lines + // Try to determine more precisely how big the array needs to be. + // If we get it right, we don't have to do an array copy, and + // we save a bunch of memory. + int encLen = ( len / 3 ) * 4 + ( len % 3 > 0 ? 4 : 0 ); // Bytes needed for actual encoding + if( breakLines ){ + encLen += encLen / MAX_LINE_LENGTH; // Plus extra newline characters + } + byte[] outBuff = new byte[ encLen ]; + + + int d = 0; + int e = 0; + int len2 = len - 2; + int lineLength = 0; + for( ; d < len2; d+=3, e+=4 ) { + encode3to4( source, d+off, 3, outBuff, e, options ); + + lineLength += 4; + if( breakLines && lineLength >= MAX_LINE_LENGTH ) + { + outBuff[e+4] = NEW_LINE; + e++; + lineLength = 0; + } // end if: end of line + } // en dfor: each piece of array + + if( d < len ) { + encode3to4( source, d+off, len - d, outBuff, e, options ); + e += 4; + } // end if: some padding needed + + + // Only resize array if we didn't guess it right. + if( e <= outBuff.length - 1 ){ + // If breaking lines and the last byte falls right at + // the line length (76 bytes per line), there will be + // one extra byte, and the array will need to be resized. + // Not too bad of an estimate on array size, I'd say. + byte[] finalOut = new byte[e]; + System.arraycopy(outBuff,0, finalOut,0,e); + //System.err.println("Having to resize array from " + outBuff.length + " to " + e ); + return finalOut; + } else { + //System.err.println("No need to resize array."); + return outBuff; + } + + } // end else: don't compress + + } // end encodeBytesToBytes + + + + + +/* ******** D E C O D I N G M E T H O D S ******** */ + + + /** + * Decodes four bytes from array <var>source</var> + * and writes the resulting bytes (up to three of them) + * to <var>destination</var>. + * The source and destination arrays can be manipulated + * anywhere along their length by specifying + * <var>srcOffset</var> and <var>destOffset</var>. + * This method does not check to make sure your arrays + * are large enough to accomodate <var>srcOffset</var> + 4 for + * the <var>source</var> array or <var>destOffset</var> + 3 for + * the <var>destination</var> array. + * This method returns the actual number of bytes that + * were converted from the Base64 encoding. + * <p>This is the lowest level of the decoding methods with + * all possible parameters.</p> + * + * + * @param source the array to convert + * @param srcOffset the index where conversion begins + * @param destination the array to hold the conversion + * @param destOffset the index where output will be put + * @param options alphabet type is pulled from this (standard, url-safe, ordered) + * @return the number of decoded bytes converted + * @throws NullPointerException if source or destination arrays are null + * @throws IllegalArgumentException if srcOffset or destOffset are invalid + * or there is not enough room in the array. + * @since 1.3 + */ + private static int decode4to3( + byte[] source, int srcOffset, + byte[] destination, int destOffset, int options ) { + + // Lots of error checking and exception throwing + if( source == null ){ + throw new NullPointerException( "Source array was null." ); + } // end if + if( destination == null ){ + throw new NullPointerException( "Destination array was null." ); + } // end if + if( srcOffset < 0 || srcOffset + 3 >= source.length ){ + throw new IllegalArgumentException( String.format( + "Source array with length %d cannot have offset of %d and still process four bytes.", source.length, srcOffset ) ); + } // end if + if( destOffset < 0 || destOffset +2 >= destination.length ){ + throw new IllegalArgumentException( String.format( + "Destination array with length %d cannot have offset of %d and still store three bytes.", destination.length, destOffset ) ); + } // end if + + + byte[] DECODABET = getDecodabet( options ); + + // Example: Dk== + if( source[ srcOffset + 2] == EQUALS_SIGN ) { + // Two ways to do the same thing. Don't know which way I like best. + //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) + // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 ); + int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) + | ( ( DECODABET[ source[ srcOffset + 1] ] & 0xFF ) << 12 ); + + destination[ destOffset ] = (byte)( outBuff >>> 16 ); + return 1; + } + + // Example: DkL= + else if( source[ srcOffset + 3 ] == EQUALS_SIGN ) { + // Two ways to do the same thing. Don't know which way I like best. + //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) + // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) + // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ); + int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) + | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 ) + | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6 ); + + destination[ destOffset ] = (byte)( outBuff >>> 16 ); + destination[ destOffset + 1 ] = (byte)( outBuff >>> 8 ); + return 2; + } + + // Example: DkLE + else { + // Two ways to do the same thing. Don't know which way I like best. + //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) + // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) + // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ) + // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 ); + int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) + | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 ) + | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6) + | ( ( DECODABET[ source[ srcOffset + 3 ] ] & 0xFF ) ); + + + destination[ destOffset ] = (byte)( outBuff >> 16 ); + destination[ destOffset + 1 ] = (byte)( outBuff >> 8 ); + destination[ destOffset + 2 ] = (byte)( outBuff ); + + return 3; + } + } // end decodeToBytes + + + + + + /** + * Low-level access to decoding ASCII characters in + * the form of a byte array. <strong>Ignores GUNZIP option, if + * it's set.</strong> This is not generally a recommended method, + * although it is used internally as part of the decoding process. + * Special case: if len = 0, an empty array is returned. Still, + * if you need more speed and reduced memory footprint (and aren't + * gzipping), consider this method. + * + * @param source The Base64 encoded data + * @return decoded data + * @since 2.3.1 + */ + public static byte[] decode( byte[] source ) + throws java.io.IOException { + byte[] decoded = null; +// try { + decoded = decode( source, 0, source.length, Base64.NO_OPTIONS ); +// } catch( java.io.IOException ex ) { +// assert false : "IOExceptions only come from GZipping, which is turned off: " + ex.getMessage(); +// } + return decoded; + } + + + + /** + * Low-level access to decoding ASCII characters in + * the form of a byte array. <strong>Ignores GUNZIP option, if + * it's set.</strong> This is not generally a recommended method, + * although it is used internally as part of the decoding process. + * Special case: if len = 0, an empty array is returned. Still, + * if you need more speed and reduced memory footprint (and aren't + * gzipping), consider this method. + * + * @param source The Base64 encoded data + * @param off The offset of where to begin decoding + * @param len The length of characters to decode + * @param options Can specify options such as alphabet type to use + * @return decoded data + * @throws java.io.IOException If bogus characters exist in source data + * @since 1.3 + */ + public static byte[] decode( byte[] source, int off, int len, int options ) + throws java.io.IOException { + + // Lots of error checking and exception throwing + if( source == null ){ + throw new NullPointerException( "Cannot decode null source array." ); + } // end if + if( off < 0 || off + len > source.length ){ + throw new IllegalArgumentException( String.format( + "Source array with length %d cannot have offset of %d and process %d bytes.", source.length, off, len ) ); + } // end if + + if( len == 0 ){ + return new byte[0]; + }else if( len < 4 ){ + throw new IllegalArgumentException( + "Base64-encoded string must have at least four characters, but length specified was " + len ); + } // end if + + byte[] DECODABET = getDecodabet( options ); + + int len34 = len * 3 / 4; // Estimate on array size + byte[] outBuff = new byte[ len34 ]; // Upper limit on size of output + int outBuffPosn = 0; // Keep track of where we're writing + + byte[] b4 = new byte[4]; // Four byte buffer from source, eliminating white space + int b4Posn = 0; // Keep track of four byte input buffer + int i = 0; // Source array counter + byte sbiDecode = 0; // Special value from DECODABET + + for( i = off; i < off+len; i++ ) { // Loop through source + + sbiDecode = DECODABET[ source[i]&0xFF ]; + + // White space, Equals sign, or legit Base64 character + // Note the values such as -5 and -9 in the + // DECODABETs at the top of the file. + if( sbiDecode >= WHITE_SPACE_ENC ) { + if( sbiDecode >= EQUALS_SIGN_ENC ) { + b4[ b4Posn++ ] = source[i]; // Save non-whitespace + if( b4Posn > 3 ) { // Time to decode? + outBuffPosn += decode4to3( b4, 0, outBuff, outBuffPosn, options ); + b4Posn = 0; + + // If that was the equals sign, break out of 'for' loop + if( source[i] == EQUALS_SIGN ) { + break; + } // end if: equals sign + } // end if: quartet built + } // end if: equals sign or better + } // end if: white space, equals sign or better + else { + // There's a bad input character in the Base64 stream. + throw new java.io.IOException( String.format( + "Bad Base64 input character decimal %d in array position %d", ((int)source[i])&0xFF, i ) ); + } // end else: + } // each input character + + byte[] out = new byte[ outBuffPosn ]; + System.arraycopy( outBuff, 0, out, 0, outBuffPosn ); + return out; + } // end decode + + + + + /** + * Decodes data from Base64 notation, automatically + * detecting gzip-compressed data and decompressing it. + * + * @param s the string to decode + * @return the decoded data + * @throws java.io.IOException If there is a problem + * @since 1.4 + */ + public static byte[] decode( String s ) throws java.io.IOException { + return decode( s, NO_OPTIONS ); + } + + + + /** + * Decodes data from Base64 notation, automatically + * detecting gzip-compressed data and decompressing it. + * + * @param s the string to decode + * @param options encode options such as URL_SAFE + * @return the decoded data + * @throws java.io.IOException if there is an error + * @throws NullPointerException if <tt>s</tt> is null + * @since 1.4 + */ + public static byte[] decode( String s, int options ) throws java.io.IOException { + + if( s == null ){ + throw new NullPointerException( "Input string was null." ); + } // end if + + byte[] bytes; + try { + bytes = s.getBytes( PREFERRED_ENCODING ); + } // end try + catch( java.io.UnsupportedEncodingException uee ) { + bytes = s.getBytes(); + } // end catch + //</change> + + // Decode + bytes = decode( bytes, 0, bytes.length, options ); + + // Check to see if it's gzip-compressed + // GZIP Magic Two-Byte Number: 0x8b1f (35615) + boolean dontGunzip = (options & DONT_GUNZIP) != 0; + if( (bytes != null) && (bytes.length >= 4) && (!dontGunzip) ) { + + int head = ((int)bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00); + if( java.util.zip.GZIPInputStream.GZIP_MAGIC == head ) { + java.io.ByteArrayInputStream bais = null; + java.util.zip.GZIPInputStream gzis = null; + java.io.ByteArrayOutputStream baos = null; + byte[] buffer = new byte[2048]; + int length = 0; + + try { + baos = new java.io.ByteArrayOutputStream(); + bais = new java.io.ByteArrayInputStream( bytes ); + gzis = new java.util.zip.GZIPInputStream( bais ); + + while( ( length = gzis.read( buffer ) ) >= 0 ) { + baos.write(buffer,0,length); + } // end while: reading input + + // No error? Get new bytes. + bytes = baos.toByteArray(); + + } // end try + catch( java.io.IOException e ) { + e.printStackTrace(); + // Just return originally-decoded bytes + } // end catch + finally { + try{ baos.close(); } catch( Exception e ){} + try{ gzis.close(); } catch( Exception e ){} + try{ bais.close(); } catch( Exception e ){} + } // end finally + + } // end if: gzipped + } // end if: bytes.length >= 2 + + return bytes; + } // end decode + + + + /** + * Attempts to decode Base64 data and deserialize a Java + * Object within. Returns <tt>null</tt> if there was an error. + * + * @param encodedObject The Base64 data to decode + * @return The decoded and deserialized object + * @throws NullPointerException if encodedObject is null + * @throws java.io.IOException if there is a general error + * @throws ClassNotFoundException if the decoded object is of a + * class that cannot be found by the JVM + * @since 1.5 + */ + public static Object decodeToObject( String encodedObject ) + throws java.io.IOException, java.lang.ClassNotFoundException { + return decodeToObject(encodedObject,NO_OPTIONS,null); + } + + + /** + * Attempts to decode Base64 data and deserialize a Java + * Object within. Returns <tt>null</tt> if there was an error. + * If <tt>loader</tt> is not null, it will be the class loader + * used when deserializing. + * + * @param encodedObject The Base64 data to decode + * @param options Various parameters related to decoding + * @param loader Optional class loader to use in deserializing classes. + * @return The decoded and deserialized object + * @throws NullPointerException if encodedObject is null + * @throws java.io.IOException if there is a general error + * @throws ClassNotFoundException if the decoded object is of a + * class that cannot be found by the JVM + * @since 2.3.4 + */ + public static Object decodeToObject( + String encodedObject, int options, final ClassLoader loader ) + throws java.io.IOException, java.lang.ClassNotFoundException { + + // Decode and gunzip if necessary + byte[] objBytes = decode( encodedObject, options ); + + java.io.ByteArrayInputStream bais = null; + java.io.ObjectInputStream ois = null; + Object obj = null; + + try { + bais = new java.io.ByteArrayInputStream( objBytes ); + + // If no custom class loader is provided, use Java's builtin OIS. + if( loader == null ){ + ois = new java.io.ObjectInputStream( bais ); + } // end if: no loader provided + + // Else make a customized object input stream that uses + // the provided class loader. + else { + ois = new java.io.ObjectInputStream(bais){ + @Override + public Class<?> resolveClass(java.io.ObjectStreamClass streamClass) + throws java.io.IOException, ClassNotFoundException { + Class<?> c = Class.forName(streamClass.getName(), false, loader); + if( c == null ){ + return super.resolveClass(streamClass); + } else { + return c; // Class loader knows of this class. + } // end else: not null + } // end resolveClass + }; // end ois + } // end else: no custom class loader + + obj = ois.readObject(); + } // end try + catch( java.io.IOException e ) { + throw e; // Catch and throw in order to execute finally{} + } // end catch + catch( java.lang.ClassNotFoundException e ) { + throw e; // Catch and throw in order to execute finally{} + } // end catch + finally { + try{ bais.close(); } catch( Exception e ){} + try{ ois.close(); } catch( Exception e ){} + } // end finally + + return obj; + } // end decodeObject + + + + /** + * Convenience method for encoding data to a file. + * + * <p>As of v 2.3, if there is a error, + * the method will throw an java.io.IOException. <b>This is new to v2.3!</b> + * In earlier versions, it just returned false, but + * in retrospect that's a pretty poor way to handle it.</p> + * + * @param dataToEncode byte array of data to encode in base64 form + * @param filename Filename for saving encoded data + * @throws java.io.IOException if there is an error + * @throws NullPointerException if dataToEncode is null + * @since 2.1 + */ + public static void encodeToFile( byte[] dataToEncode, String filename ) + throws java.io.IOException { + + if( dataToEncode == null ){ + throw new NullPointerException( "Data to encode was null." ); + } // end iff + + Base64.OutputStream bos = null; + try { + bos = new Base64.OutputStream( + new java.io.FileOutputStream( filename ), Base64.ENCODE ); + bos.write( dataToEncode ); + } // end try + catch( java.io.IOException e ) { + throw e; // Catch and throw to execute finally{} block + } // end catch: java.io.IOException + finally { + try{ bos.close(); } catch( Exception e ){} + } // end finally + + } // end encodeToFile + + + /** + * Convenience method for decoding data to a file. + * + * <p>As of v 2.3, if there is a error, + * the method will throw an java.io.IOException. <b>This is new to v2.3!</b> + * In earlier versions, it just returned false, but + * in retrospect that's a pretty poor way to handle it.</p> + * + * @param dataToDecode Base64-encoded data as a string + * @param filename Filename for saving decoded data + * @throws java.io.IOException if there is an error + * @since 2.1 + */ + public static void decodeToFile( String dataToDecode, String filename ) + throws java.io.IOException { + + Base64.OutputStream bos = null; + try{ + bos = new Base64.OutputStream( + new java.io.FileOutputStream( filename ), Base64.DECODE ); + bos.write( dataToDecode.getBytes( PREFERRED_ENCODING ) ); + } // end try + catch( java.io.IOException e ) { + throw e; // Catch and throw to execute finally{} block + } // end catch: java.io.IOException + finally { + try{ bos.close(); } catch( Exception e ){} + } // end finally + + } // end decodeToFile + + + + + /** + * Convenience method for reading a base64-encoded + * file and decoding it. + * + * <p>As of v 2.3, if there is a error, + * the method will throw an java.io.IOException. <b>This is new to v2.3!</b> + * In earlier versions, it just returned false, but + * in retrospect that's a pretty poor way to handle it.</p> + * + * @param filename Filename for reading encoded data + * @return decoded byte array + * @throws java.io.IOException if there is an error + * @since 2.1 + */ + public static byte[] decodeFromFile( String filename ) + throws java.io.IOException { + + byte[] decodedData = null; + Base64.InputStream bis = null; + try + { + // Set up some useful variables + java.io.File file = new java.io.File( filename ); + byte[] buffer = null; + int length = 0; + int numBytes = 0; + + // Check for size of file + if( file.length() > Integer.MAX_VALUE ) + { + throw new java.io.IOException( "File is too big for this convenience method (" + file.length() + " bytes)." ); + } // end if: file too big for int index + buffer = new byte[ (int)file.length() ]; + + // Open a stream + bis = new Base64.InputStream( + new java.io.BufferedInputStream( + new java.io.FileInputStream( file ) ), Base64.DECODE ); + + // Read until done + while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 ) { + length += numBytes; + } // end while + + // Save in a variable to return + decodedData = new byte[ length ]; + System.arraycopy( buffer, 0, decodedData, 0, length ); + + } // end try + catch( java.io.IOException e ) { + throw e; // Catch and release to execute finally{} + } // end catch: java.io.IOException + finally { + try{ bis.close(); } catch( Exception e) {} + } // end finally + + return decodedData; + } // end decodeFromFile + + + + /** + * Convenience method for reading a binary file + * and base64-encoding it. + * + * <p>As of v 2.3, if there is a error, + * the method will throw an java.io.IOException. <b>This is new to v2.3!</b> + * In earlier versions, it just returned false, but + * in retrospect that's a pretty poor way to handle it.</p> + * + * @param filename Filename for reading binary data + * @return base64-encoded string + * @throws java.io.IOException if there is an error + * @since 2.1 + */ + public static String encodeFromFile( String filename ) + throws java.io.IOException { + + String encodedData = null; + Base64.InputStream bis = null; + try + { + // Set up some useful variables + java.io.File file = new java.io.File( filename ); + byte[] buffer = new byte[ Math.max((int)(file.length() * 1.4+1),40) ]; // Need max() for math on small files (v2.2.1); Need +1 for a few corner cases (v2.3.5) + int length = 0; + int numBytes = 0; + + // Open a stream + bis = new Base64.InputStream( + new java.io.BufferedInputStream( + new java.io.FileInputStream( file ) ), Base64.ENCODE ); + + // Read until done + while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 ) { + length += numBytes; + } // end while + + // Save in a variable to return + encodedData = new String( buffer, 0, length, Base64.PREFERRED_ENCODING ); + + } // end try + catch( java.io.IOException e ) { + throw e; // Catch and release to execute finally{} + } // end catch: java.io.IOException + finally { + try{ bis.close(); } catch( Exception e) {} + } // end finally + + return encodedData; + } // end encodeFromFile + + /** + * Reads <tt>infile</tt> and encodes it to <tt>outfile</tt>. + * + * @param infile Input file + * @param outfile Output file + * @throws java.io.IOException if there is an error + * @since 2.2 + */ + public static void encodeFileToFile( String infile, String outfile ) + throws java.io.IOException { + + String encoded = Base64.encodeFromFile( infile ); + java.io.OutputStream out = null; + try{ + out = new java.io.BufferedOutputStream( + new java.io.FileOutputStream( outfile ) ); + out.write( encoded.getBytes("US-ASCII") ); // Strict, 7-bit output. + } // end try + catch( java.io.IOException e ) { + throw e; // Catch and release to execute finally{} + } // end catch + finally { + try { out.close(); } + catch( Exception ex ){} + } // end finally + } // end encodeFileToFile + + + /** + * Reads <tt>infile</tt> and decodes it to <tt>outfile</tt>. + * + * @param infile Input file + * @param outfile Output file + * @throws java.io.IOException if there is an error + * @since 2.2 + */ + public static void decodeFileToFile( String infile, String outfile ) + throws java.io.IOException { + + byte[] decoded = Base64.decodeFromFile( infile ); + java.io.OutputStream out = null; + try{ + out = new java.io.BufferedOutputStream( + new java.io.FileOutputStream( outfile ) ); + out.write( decoded ); + } // end try + catch( java.io.IOException e ) { + throw e; // Catch and release to execute finally{} + } // end catch + finally { + try { out.close(); } + catch( Exception ex ){} + } // end finally + } // end decodeFileToFile + + + /* ******** I N N E R C L A S S I N P U T S T R E A M ******** */ + + + + /** + * A {@link Base64.InputStream} will read data from another + * <tt>java.io.InputStream</tt>, given in the constructor, + * and encode/decode to/from Base64 notation on the fly. + * + * @see Base64 + * @since 1.3 + */ + public static class InputStream extends java.io.FilterInputStream { + + private boolean encode; // Encoding or decoding + private int position; // Current position in the buffer + private byte[] buffer; // Small buffer holding converted data + private int bufferLength; // Length of buffer (3 or 4) + private int numSigBytes; // Number of meaningful bytes in the buffer + private int lineLength; + private boolean breakLines; // Break lines at less than 80 characters + private int options; // Record options used to create the stream. + private byte[] decodabet; // Local copies to avoid extra method calls + + + /** + * Constructs a {@link Base64.InputStream} in DECODE mode. + * + * @param in the <tt>java.io.InputStream</tt> from which to read data. + * @since 1.3 + */ + public InputStream( java.io.InputStream in ) { + this( in, DECODE ); + } // end constructor + + + /** + * Constructs a {@link Base64.InputStream} in + * either ENCODE or DECODE mode. + * <p> + * Valid options:<pre> + * ENCODE or DECODE: Encode or Decode as data is read. + * DO_BREAK_LINES: break lines at 76 characters + * (only meaningful when encoding)</i> + * </pre> + * <p> + * Example: <code>new Base64.InputStream( in, Base64.DECODE )</code> + * + * + * @param in the <tt>java.io.InputStream</tt> from which to read data. + * @param options Specified options + * @see Base64#ENCODE + * @see Base64#DECODE + * @see Base64#DO_BREAK_LINES + * @since 2.0 + */ + public InputStream( java.io.InputStream in, int options ) { + + super( in ); + this.options = options; // Record for later + this.breakLines = (options & DO_BREAK_LINES) > 0; + this.encode = (options & ENCODE) > 0; + this.bufferLength = encode ? 4 : 3; + this.buffer = new byte[ bufferLength ]; + this.position = -1; + this.lineLength = 0; + this.decodabet = getDecodabet(options); + } // end constructor + + /** + * Reads enough of the input stream to convert + * to/from Base64 and returns the next byte. + * + * @return next byte + * @since 1.3 + */ + @Override + public int read() throws java.io.IOException { + + // Do we need to get data? + if( position < 0 ) { + if( encode ) { + byte[] b3 = new byte[3]; + int numBinaryBytes = 0; + for( int i = 0; i < 3; i++ ) { + int b = in.read(); + + // If end of stream, b is -1. + if( b >= 0 ) { + b3[i] = (byte)b; + numBinaryBytes++; + } else { + break; // out of for loop + } // end else: end of stream + + } // end for: each needed input byte + + if( numBinaryBytes > 0 ) { + encode3to4( b3, 0, numBinaryBytes, buffer, 0, options ); + position = 0; + numSigBytes = 4; + } // end if: got data + else { + return -1; // Must be end of stream + } // end else + } // end if: encoding + + // Else decoding + else { + byte[] b4 = new byte[4]; + int i = 0; + for( i = 0; i < 4; i++ ) { + // Read four "meaningful" bytes: + int b = 0; + do{ b = in.read(); } + while( b >= 0 && decodabet[ b & 0x7f ] <= WHITE_SPACE_ENC ); + + if( b < 0 ) { + break; // Reads a -1 if end of stream + } // end if: end of stream + + b4[i] = (byte)b; + } // end for: each needed input byte + + if( i == 4 ) { + numSigBytes = decode4to3( b4, 0, buffer, 0, options ); + position = 0; + } // end if: got four characters + else if( i == 0 ){ + return -1; + } // end else if: also padded correctly + else { + // Must have broken out from above. + throw new java.io.IOException( "Improperly padded Base64 input." ); + } // end + + } // end else: decode + } // end else: get data + + // Got data? + if( position >= 0 ) { + // End of relevant data? + if( /*!encode &&*/ position >= numSigBytes ){ + return -1; + } // end if: got data + + if( encode && breakLines && lineLength >= MAX_LINE_LENGTH ) { + lineLength = 0; + return '\n'; + } // end if + else { + lineLength++; // This isn't important when decoding + // but throwing an extra "if" seems + // just as wasteful. + + int b = buffer[ position++ ]; + + if( position >= bufferLength ) { + position = -1; + } // end if: end + + return b & 0xFF; // This is how you "cast" a byte that's + // intended to be unsigned. + } // end else + } // end if: position >= 0 + + // Else error + else { + throw new java.io.IOException( "Error in Base64 code reading stream." ); + } // end else + } // end read + + + /** + * Calls {@link #read()} repeatedly until the end of stream + * is reached or <var>len</var> bytes are read. + * Returns number of bytes read into array or -1 if + * end of stream is encountered. + * + * @param dest array to hold values + * @param off offset for array + * @param len max number of bytes to read into array + * @return bytes read into array or -1 if end of stream is encountered. + * @since 1.3 + */ + @Override + public int read( byte[] dest, int off, int len ) + throws java.io.IOException { + int i; + int b; + for( i = 0; i < len; i++ ) { + b = read(); + + if( b >= 0 ) { + dest[off + i] = (byte) b; + } + else if( i == 0 ) { + return -1; + } + else { + break; // Out of 'for' loop + } // Out of 'for' loop + } // end for: each byte read + return i; + } // end read + + } // end inner class InputStream + + + + + + + /* ******** I N N E R C L A S S O U T P U T S T R E A M ******** */ + + + + /** + * A {@link Base64.OutputStream} will write data to another + * <tt>java.io.OutputStream</tt>, given in the constructor, + * and encode/decode to/from Base64 notation on the fly. + * + * @see Base64 + * @since 1.3 + */ + public static class OutputStream extends java.io.FilterOutputStream { + + private boolean encode; + private int position; + private byte[] buffer; + private int bufferLength; + private int lineLength; + private boolean breakLines; + private byte[] b4; // Scratch used in a few places + private boolean suspendEncoding; + private int options; // Record for later + private byte[] decodabet; // Local copies to avoid extra method calls + + /** + * Constructs a {@link Base64.OutputStream} in ENCODE mode. + * + * @param out the <tt>java.io.OutputStream</tt> to which data will be written. + * @since 1.3 + */ + public OutputStream( java.io.OutputStream out ) { + this( out, ENCODE ); + } // end constructor + + + /** + * Constructs a {@link Base64.OutputStream} in + * either ENCODE or DECODE mode. + * <p> + * Valid options:<pre> + * ENCODE or DECODE: Encode or Decode as data is read. + * DO_BREAK_LINES: don't break lines at 76 characters + * (only meaningful when encoding)</i> + * </pre> + * <p> + * Example: <code>new Base64.OutputStream( out, Base64.ENCODE )</code> + * + * @param out the <tt>java.io.OutputStream</tt> to which data will be written. + * @param options Specified options. + * @see Base64#ENCODE + * @see Base64#DECODE + * @see Base64#DO_BREAK_LINES + * @since 1.3 + */ + public OutputStream( java.io.OutputStream out, int options ) { + super( out ); + this.breakLines = (options & DO_BREAK_LINES) != 0; + this.encode = (options & ENCODE) != 0; + this.bufferLength = encode ? 3 : 4; + this.buffer = new byte[ bufferLength ]; + this.position = 0; + this.lineLength = 0; + this.suspendEncoding = false; + this.b4 = new byte[4]; + this.options = options; + this.decodabet = getDecodabet(options); + } // end constructor + + + /** + * Writes the byte to the output stream after + * converting to/from Base64 notation. + * When encoding, bytes are buffered three + * at a time before the output stream actually + * gets a write() call. + * When decoding, bytes are buffered four + * at a time. + * + * @param theByte the byte to write + * @since 1.3 + */ + @Override + public void write(int theByte) + throws java.io.IOException { + // Encoding suspended? + if( suspendEncoding ) { + this.out.write( theByte ); + return; + } // end if: supsended + + // Encode? + if( encode ) { + buffer[ position++ ] = (byte)theByte; + if( position >= bufferLength ) { // Enough to encode. + + this.out.write( encode3to4( b4, buffer, bufferLength, options ) ); + + lineLength += 4; + if( breakLines && lineLength >= MAX_LINE_LENGTH ) { + this.out.write( NEW_LINE ); + lineLength = 0; + } // end if: end of line + + position = 0; + } // end if: enough to output + } // end if: encoding + + // Else, Decoding + else { + // Meaningful Base64 character? + if( decodabet[ theByte & 0x7f ] > WHITE_SPACE_ENC ) { + buffer[ position++ ] = (byte)theByte; + if( position >= bufferLength ) { // Enough to output. + + int len = Base64.decode4to3( buffer, 0, b4, 0, options ); + out.write( b4, 0, len ); + position = 0; + } // end if: enough to output + } // end if: meaningful base64 character + else if( decodabet[ theByte & 0x7f ] != WHITE_SPACE_ENC ) { + throw new java.io.IOException( "Invalid character in Base64 data." ); + } // end else: not white space either + } // end else: decoding + } // end write + + + + /** + * Calls {@link #write(int)} repeatedly until <var>len</var> + * bytes are written. + * + * @param theBytes array from which to read bytes + * @param off offset for array + * @param len max number of bytes to read into array + * @since 1.3 + */ + @Override + public void write( byte[] theBytes, int off, int len ) + throws java.io.IOException { + // Encoding suspended? + if( suspendEncoding ) { + this.out.write( theBytes, off, len ); + return; + } // end if: supsended + + for( int i = 0; i < len; i++ ) { + write( theBytes[ off + i ] ); + } // end for: each byte written + + } // end write + + + + /** + * Method added by PHIL. [Thanks, PHIL. -Rob] + * This pads the buffer without closing the stream. + * @throws java.io.IOException if there's an error. + */ + public void flushBase64() throws java.io.IOException { + if( position > 0 ) { + if( encode ) { + out.write( encode3to4( b4, buffer, position, options ) ); + position = 0; + } // end if: encoding + else { + throw new java.io.IOException( "Base64 input not properly padded." ); + } // end else: decoding + } // end if: buffer partially full + + } // end flush + + + /** + * Flushes and closes (I think, in the superclass) the stream. + * + * @since 1.3 + */ + @Override + public void close() throws java.io.IOException { + // 1. Ensure that pending characters are written + flushBase64(); + + // 2. Actually close the stream + // Base class both flushes and closes. + super.close(); + + buffer = null; + out = null; + } // end close + + + + /** + * Suspends encoding of the stream. + * May be helpful if you need to embed a piece of + * base64-encoded data in a stream. + * + * @throws java.io.IOException if there's an error flushing + * @since 1.5.1 + */ + public void suspendEncoding() throws java.io.IOException { + flushBase64(); + this.suspendEncoding = true; + } // end suspendEncoding + + + /** + * Resumes encoding of the stream. + * May be helpful if you need to embed a piece of + * base64-encoded data in a stream. + * + * @since 1.5.1 + */ + public void resumeEncoding() { + this.suspendEncoding = false; + } // end resumeEncoding + + + + } // end inner class OutputStream + + +} // end class Base64 diff --git a/cb-tools/java-source/src/main/java/org/java_websocket/util/Charsetfunctions.java b/cb-tools/java-source/src/main/java/org/java_websocket/util/Charsetfunctions.java new file mode 100644 index 00000000..bd8ad299 --- /dev/null +++ b/cb-tools/java-source/src/main/java/org/java_websocket/util/Charsetfunctions.java @@ -0,0 +1,90 @@ +package org.java_websocket.util; + +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CodingErrorAction; + +import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.framing.CloseFrame; + +public class Charsetfunctions { + + public static CodingErrorAction codingErrorAction = CodingErrorAction.REPORT; + + /* + * @return UTF-8 encoding in bytes + */ + public static byte[] utf8Bytes( String s ) { + try { + return s.getBytes( "UTF8" ); + } catch ( UnsupportedEncodingException e ) { + throw new RuntimeException( e ); + } + } + + /* + * @return ASCII encoding in bytes + */ + public static byte[] asciiBytes( String s ) { + try { + return s.getBytes( "ASCII" ); + } catch ( UnsupportedEncodingException e ) { + throw new RuntimeException( e ); + } + } + + public static String stringAscii( byte[] bytes ) { + return stringAscii( bytes, 0, bytes.length ); + } + + public static String stringAscii( byte[] bytes, int offset, int length ){ + try { + return new String( bytes, offset, length, "ASCII" ); + } catch ( UnsupportedEncodingException e ) { + throw new RuntimeException( e ); + } + } + + public static String stringUtf8( byte[] bytes ) throws InvalidDataException { + return stringUtf8( ByteBuffer.wrap( bytes ) ); + } + + /*public static String stringUtf8( byte[] bytes, int off, int length ) throws InvalidDataException { + CharsetDecoder decode = Charset.forName( "UTF8" ).newDecoder(); + decode.onMalformedInput( codingErrorAction ); + decode.onUnmappableCharacter( codingErrorAction ); + //decode.replaceWith( "X" ); + String s; + try { + s = decode.decode( ByteBuffer.wrap( bytes, off, length ) ).toString(); + } catch ( CharacterCodingException e ) { + throw new InvalidDataException( CloseFrame.NO_UTF8, e ); + } + return s; + }*/ + + public static String stringUtf8( ByteBuffer bytes ) throws InvalidDataException { + CharsetDecoder decode = Charset.forName( "UTF8" ).newDecoder(); + decode.onMalformedInput( codingErrorAction ); + decode.onUnmappableCharacter( codingErrorAction ); + // decode.replaceWith( "X" ); + String s; + try { + bytes.mark(); + s = decode.decode( bytes ).toString(); + bytes.reset(); + } catch ( CharacterCodingException e ) { + throw new InvalidDataException( CloseFrame.NO_UTF8, e ); + } + return s; + } + + public static void main( String[] args ) throws InvalidDataException { + stringUtf8( utf8Bytes( "\0" ) ); + stringAscii( asciiBytes( "\0" ) ); + } + +} diff --git a/cb-tools/java-source/src/test/java/AutobahnClientTest.java b/cb-tools/java-source/src/test/java/AutobahnClientTest.java new file mode 100644 index 00000000..a567fd1b --- /dev/null +++ b/cb-tools/java-source/src/test/java/AutobahnClientTest.java @@ -0,0 +1,163 @@ +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URI; +import java.nio.ByteBuffer; + +import org.java_websocket.WebSocket; +import org.java_websocket.WebSocketImpl; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.drafts.Draft; +import org.java_websocket.drafts.Draft_17; +import org.java_websocket.framing.FrameBuilder; +import org.java_websocket.framing.Framedata; +import org.java_websocket.handshake.ServerHandshake; + +public class AutobahnClientTest extends WebSocketClient { + + public AutobahnClientTest( Draft d , URI uri ) { + super( uri, d ); + } + /** + * @param args + */ + public static void main( String[] args ) { + System.out.println( "Testutility to profile/test this implementation using the Autobahn suit.\n" ); + System.out.println( "Type 'r <casenumber>' to run a testcase. Example: r 1" ); + System.out.println( "Type 'r <first casenumber> <last casenumber>' to run a testcase. Example: r 1 295" ); + System.out.println( "Type 'u' to update the test results." ); + System.out.println( "Type 'ex' to terminate the program." ); + System.out.println( "During sequences of cases the debugoutput will be turned of." ); + + System.out.println( "You can now enter in your commands:" ); + + try { + BufferedReader sysin = new BufferedReader( new InputStreamReader( System.in ) ); + + /*First of the thinks a programmer might want to change*/ + Draft d = new Draft_17(); + String clientname = "tootallnate/websocket"; + + String protocol = "ws"; + String host = "localhost"; + int port = 9001; + + String serverlocation = protocol + "://" + host + ":" + port; + String line = ""; + AutobahnClientTest e; + URI uri = null; + String perviousline = ""; + String nextline = null; + Integer start = null; + Integer end = null; + + while ( !line.contains( "ex" ) ) { + try { + if( nextline != null ) { + line = nextline; + nextline = null; + WebSocketImpl.DEBUG = false; + } else { + System.out.print( ">" ); + line = sysin.readLine(); + WebSocketImpl.DEBUG = true; + } + if( line.equals( "l" ) ) { + line = perviousline; + } + String[] spl = line.split( " " ); + if( line.startsWith( "r" ) ) { + if( spl.length == 3 ) { + start = new Integer( spl[ 1 ] ); + end = new Integer( spl[ 2 ] ); + } + if( start != null && end != null ) { + if( start > end ) { + start = null; + end = null; + } else { + nextline = "r " + start; + start++; + if( spl.length == 3 ) + continue; + } + } + uri = URI.create( serverlocation + "/runCase?case=" + spl[ 1 ] + "&agent=" + clientname ); + + } else if( line.startsWith( "u" ) ) { + WebSocketImpl.DEBUG = false; + uri = URI.create( serverlocation + "/updateReports?agent=" + clientname ); + } else if( line.startsWith( "d" ) ) { + try { + d = (Draft) Class.forName( "Draft_" + spl[ 1 ] ).getConstructor().newInstance(); + } catch ( Exception ex ) { + System.out.println( "Could not change draft" + ex ); + } + } + if( uri == null ) { + System.out.println( "Do not understand the input." ); + continue; + } + System.out.println( "//////////////////////Exec: " + uri.getQuery() ); + e = new AutobahnClientTest( d, uri ); + Thread t = new Thread( e ); + t.start(); + try { + t.join(); + + } catch ( InterruptedException e1 ) { + e1.printStackTrace(); + } finally { + e.close(); + } + } catch ( ArrayIndexOutOfBoundsException e1 ) { + System.out.println( "Bad Input r 1, u 1, d 10, ex" ); + } catch ( IllegalArgumentException e2 ) { + e2.printStackTrace(); + } + + } + } catch ( ArrayIndexOutOfBoundsException e ) { + System.out.println( "Missing server uri" ); + } catch ( IllegalArgumentException e ) { + e.printStackTrace(); + System.out.println( "URI should look like ws://localhost:8887 or wss://echo.websocket.org" ); + } catch ( IOException e ) { + e.printStackTrace(); // for System.in reader + } + System.exit( 0 ); + } + + @Override + public void onMessage( String message ) { + send( message ); + } + + @Override + public void onMessage( ByteBuffer blob ) { + getConnection().send( blob ); + } + + @Override + public void onError( Exception ex ) { + System.out.println( "Error: " ); + ex.printStackTrace(); + } + + @Override + public void onOpen( ServerHandshake handshake ) { + } + + @Override + public void onClose( int code, String reason, boolean remote ) { + System.out.println( "Closed: " + code + " " + reason ); + } + + @Override + public void onWebsocketMessageFragment( WebSocket conn, Framedata frame ) { + FrameBuilder builder = (FrameBuilder) frame; + builder.setTransferemasked( true ); + getConnection().sendFrame( frame ); + } + +} diff --git a/cb-tools/java-source/src/test/java/AutobahnServerTest.java b/cb-tools/java-source/src/test/java/AutobahnServerTest.java new file mode 100644 index 00000000..ed7d05c4 --- /dev/null +++ b/cb-tools/java-source/src/test/java/AutobahnServerTest.java @@ -0,0 +1,72 @@ +import java.net.InetSocketAddress; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.util.Collections; + +import org.java_websocket.WebSocket; +import org.java_websocket.WebSocketImpl; +import org.java_websocket.drafts.Draft; +import org.java_websocket.drafts.Draft_17; +import org.java_websocket.framing.FrameBuilder; +import org.java_websocket.framing.Framedata; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.server.WebSocketServer; + +public class AutobahnServerTest extends WebSocketServer { + private static int counter = 0; + + public AutobahnServerTest( int port , Draft d ) throws UnknownHostException { + super( new InetSocketAddress( port ), Collections.singletonList( d ) ); + } + + public AutobahnServerTest( InetSocketAddress address, Draft d ) { + super( address, Collections.singletonList( d ) ); + } + + @Override + public void onOpen( WebSocket conn, ClientHandshake handshake ) { + counter++; + System.out.println( "///////////Opened connection number" + counter ); + } + + @Override + public void onClose( WebSocket conn, int code, String reason, boolean remote ) { + System.out.println( "closed" ); + } + + @Override + public void onError( WebSocket conn, Exception ex ) { + System.out.println( "Error:" ); + ex.printStackTrace(); + } + + @Override + public void onMessage( WebSocket conn, String message ) { + conn.send( message ); + } + + @Override + public void onMessage( WebSocket conn, ByteBuffer blob ) { + conn.send( blob ); + } + + @Override + public void onWebsocketMessageFragment( WebSocket conn, Framedata frame ) { + FrameBuilder builder = (FrameBuilder) frame; + builder.setTransferemasked( false ); + conn.sendFrame( frame ); + } + + public static void main( String[] args ) throws UnknownHostException { + WebSocketImpl.DEBUG = false; + int port; + try { + port = new Integer( args[ 0 ] ); + } catch ( Exception e ) { + System.out.println( "No port specified. Defaulting to 9003" ); + port = 9003; + } + new AutobahnServerTest( port, new Draft_17() ).start(); + } + +} From 06c984656f009829f7f2aae1bfd59ab36067a9af Mon Sep 17 00:00:00 2001 From: jaib1 <j.bhagat@ucl.ac.uk> Date: Tue, 28 May 2019 19:40:15 +0100 Subject: [PATCH 377/393] started 'paths_config' and updated 'readme' --- .github/config.yml | 4 ++-- README.md | 50 ++++++++++++++++----------------------- docs/setup/paths_config.m | 18 ++++++++++++++ 3 files changed, 40 insertions(+), 32 deletions(-) create mode 100644 docs/setup/paths_config.m diff --git a/.github/config.yml b/.github/config.yml index 1c659e44..3cec0564 100644 --- a/.github/config.yml +++ b/.github/config.yml @@ -1,7 +1,7 @@ todo: autoAssign: true - keyword: ['FIXME','TODO','%TODO','%FIXME','@todo','@fixme'] - bodyKeyword: ['@body','BODY'] + keyword: ['TODO','FIXME','%TODO','%FIXME','@todo','@fixme','todo:','fixme:','@todo:','@fixme:'] + bodyKeyword: ['BODY','@body','body:','@body:'] blobLines: 5 caseSensitive: false label: true diff --git a/README.md b/README.md index b931f26d..d6453f9f 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,18 @@ ---------- # Rigbox -Rigbox is a a high-performance, open-source software toolbox for managing behavioral neuroscience experiments. Initially developed to probe mouse behavior for the [Steering Wheel Setup](https://www.ucl.ac.uk/cortexlab/tools/wheel), Rigbox is under active, test-driven development to encompass a variety of experimental paradigms across behavioral neuroscience. Rigbox simplifies hardware/software interfacing, synchronizes data streams from multiple sources, manages experimental data via communication with a remote database, and creates an environment where experimental parameters can be easily monitored and manipulated. Rigbox’s object-oriented paradigm facilitates a modular approach to designing experiments. Rigbox requires two machines, one for stimulus presentation ('the stimulus computer' or 'sc') and another for controlling and monitoring the experiment ('the master computer' or 'mc'). +Rigbox is a a high-performance, open-source software toolbox for managing behavioral neuroscience experiments. Initially developed to probe mouse behavior for the [Steering Wheel Setup](https://www.ucl.ac.uk/cortexlab/tools/wheel), Rigbox is under active, test-driven development to encompass a variety of experimental paradigms across behavioral neuroscience. Rigbox simplifies hardware/software interfacing, synchronizes data streams from multiple sources, manages experimental data via communication with a remote database, implements a viewing model for visual stimuli, and creates an environment where experimental parameters can be easily monitored and manipulated. Rigbox’s object-oriented paradigm facilitates a modular approach to designing experiments. Rigbox requires two machines, one for stimulus presentation ('the stimulus computer' or 'sc') and another for controlling and monitoring the experiment ('the master computer' or 'mc'). ## Getting Started -The following is a brief description of how to install Rigbox on your experimental rig. Additional detailed, step-by-step information can be found [here](https://www.ucl.ac.uk/cortexlab/tools/wheel). +The following is a brief description of how to install Rigbox on your experimental rig. Detailed, step-by-step information can be found in Rigbox's [ReadTheDocs](https://rigbox.readthedocs.io/en/latest/). Information specific to the steering wheel task can be found on the [CortexLab website](https://www.ucl.ac.uk/cortexlab/tools/wheel). -## Prerequisites +### Prerequisites Rigbox has the following software dependencies: * Windows Operating System (7 or later, 64-bit) * MATLAB (2017b or later) -* The following MathWorks MATLAB toolboxes: +* The following MathWorks MATLAB toolboxes (note, these can all be downloaded and installed directly within MATLAB via the "Add-Ons" button in the "Home" top toolstrip): * Data Acquisition Toolbox * Signal Processing Toolbox * Instrument Control Toolbox @@ -22,49 +22,36 @@ Rigbox has the following software dependencies: * [Psychophsics Toolbox](http://psychtoolbox.org/download.html) (v3 or later) * [NI-DAQmx support package](https://uk.mathworks.com/hardware-support/nidaqmx.html) -All required MathWorks MATLAB toolboxes can be downloaded and installed directly within MATLAB via the "Add-Ons" button in the "Home" top toolstrip. - Additionally, Rigbox works with a number of extra submodules (included): * [signals](https://github.com/cortex-lab/signals) (for designing bespoke experiments) * [alyx-matlab](https://github.com/cortex-lab/alyx-matlab) (for registering data to, and retrieving from, an Alyx database) * [npy-matlab](https://github.com/kwikteam/npy-matlab) (for saving data in binary NPY format) * [wheelAnalysis](https://github.com/cortex-lab/wheelAnalysis) (for analyzing data from the steering wheel task) -## Installation via git +### Installation via git 0. It is highly recommended to install Rigbox via git. If not already downloaded and installed, install [git](https://git-scm.com/download/win) (and the included minGW software environment and Git Bash MinTTY terminal emulator). After installing, launch the Git Bash terminal. -1. To install Rigbox, use the following commands in the Git Bash terminal to clone the repository from github to your local machine. (* *Note*: It is *not* recommended to clone directly into the MATLAB folder) +1. To install Rigbox, run the following commands in the Git Bash terminal to clone the repository from GitHub to your local machine. (* *Note*: It is *not* recommended to clone directly into the MATLAB folder) ``` cd ~ -git clone https://github.com/cortex-lab/Rigbox.git -``` -2. Pull the latest Rigbox-lite branch. This branch is currently the cleanest one, though in the future it will likely be merged with the master branch. -``` -cd Rigbox/ -git checkout rigbox-lite -``` -3. Clone the submodules: -``` -git submodule update --init +git clone --recurse-submodules https://github.com/cortex-lab/Rigbox ``` -4. Open MATLAB, make sure Rigbox and all subdirectories are in your path, run: +2. Open MATLAB, make sure Rigbox and all subdirectories are in your path, run: > addRigboxPaths - and restart MATLAB. - -5. Set the correct paths by following the instructions in the 'Rigbox\+dat\paths.m' file on both machines. -6. On the stimulus server, load 'Rigbox\Repositories\code\config\exampleRig\hardware.mat' and edit according to your specific hardware setup (link to detailed instructions above, under 'Getting started'). - -To keep the submodules up to date, run the following in the Git Bash terminal (within the Rigbox directory): +3. Set the correct paths on both machines by following the instructions in the '/docs/setup/paths_config' file. +4. On the stimulus server, set the hardware configuration by following the instructions in the '/docs/setup/hardware_config' file. +5. To keep the submodules up to date, run the following in the Git Bash terminal (within the Rigbox directory): ``` git pull --recurse-submodules ``` -## Running an experiment in MATLAB -On the stimulus server, run: +### Running an experiment in MATLAB + +On the stimulus computer, run: > srv.expServer -On the mc computer, run: +On the master computer, run: > mc This opens a GUI that will allow you to choose a subject, edit some of the experimental parameters and press 'Start' to begin the basic steering wheel task on the stimulus server. @@ -139,7 +126,10 @@ Additional information on the [alyx-matlab](https://github.com/cortex-lab/alyx-m ## Acknowledgements -<Add links to third party code> +* [GUI Layout Toolbox](https://uk.mathworks.com/matlabcentral/fileexchange/47982-gui-layout-toolbox) for code pertaining to Rigbox's UI +* [Psychophsics Toolbox](http://psychtoolbox.org) for code pertaining to visual stimulus presentation +* [NI-DAQmx](https://uk.mathworks.com/hardware-support/nidaqmx.html) for code pertaining to inerfacing with a NI-DAQ device +* [TooTallNate](https://github.com/TooTallNate/Java-WebSocket) for code pertaining to using Java Websockets ## Contributing @@ -147,4 +137,4 @@ Please read [CONTRIBUTING.md](https://github.com/cortex-lab/Rigbox/blob/dev/CONT ## Authors -The majority of the Rigbox code was written by [Chris Burgess](https://github.com/dendritic/) in 2013. It is now maintained and developed by Miles Wells (miles.wells@ucl.ac.uk), Jai Bhagat (j.bhagat@ucl.ac.uk) and a number of others at [CortexLab](https://www.ucl.ac.uk/cortexlab). +The majority of the Rigbox code was written by [Chris Burgess](https://github.com/dendritic/) in 2013. It is now maintained and developed by Miles Wells (miles.wells@ucl.ac.uk), Jai Bhagat (j.bhagat@ucl.ac.uk) and a number of others at [CortexLab](https://www.ucl.ac.uk/cortexlab). See also the full list of [contributors](https://github.com/cortex-lab/Rigbox/graphs/contributors). diff --git a/docs/setup/paths_config.m b/docs/setup/paths_config.m new file mode 100644 index 00000000..dc76aee6 --- /dev/null +++ b/docs/setup/paths_config.m @@ -0,0 +1,18 @@ +% The 'dat.paths' file is used for configuring important paths for the +% computers which Rigbox runs on and communicates with. These include paths +% to: +% +% 1) either the shared folder(s) OR the remote server(s) on which +% organization-wide configuration files, subject data and experiment data +% is stored, and a local directory for generating redundant copies of this +% data +% +% 2) optionally, paths to a remote database (e.g., if using Alyx), +% and a local redundant copy of that database +% +% 3) optionally, paths to any other directories for storing additional +% back-ups (e.g. for working analyses, tapes, etc...) +% +% 4) optionally, a path to a custom config file for the local computer. +% +% @todo: add instructions w/ code examples for setting up 'dat.paths' \ No newline at end of file From 279192af5c757fee6a09307b7f5e00bc1b5eeaf3 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Tue, 28 May 2019 18:55:17 +0300 Subject: [PATCH 378/393] AlyxPanel automatically actived when databaseURL field is defined in paths file --- +eui/MControl.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/+eui/MControl.m b/+eui/MControl.m index ff44471b..4f4ff1df 100644 --- a/+eui/MControl.m +++ b/+eui/MControl.m @@ -763,7 +763,8 @@ function buildUI(obj, parent) % Parent here is the MC window (figure) leftSideBox.Heights = [55 22]; % Create the Alyx panel - obj.AlyxPanel = eui.AlyxPanel(headerBox); + url = char(getOr(dat.paths, 'databaseURL', '')); + obj.AlyxPanel = eui.AlyxPanel(headerBox, isempty(url)); addlistener(obj.NewExpSubject, 'SelectionChanged', @(src, evt)obj.AlyxPanel.dispWaterReq(src, evt)); addlistener(obj.LogSubject, 'SelectionChanged', @(src, evt)obj.AlyxPanel.dispWaterReq(src, evt)); From 963f64ec93f780316c6e8643f445bfc4d3b4c842 Mon Sep 17 00:00:00 2001 From: jaib1 <j.bhagat@ucl.ac.uk> Date: Tue, 28 May 2019 20:57:42 +0100 Subject: [PATCH 379/393] updated submodules (merged dev -> master for alyx-matlab and signals) --- alyx-matlab | 2 +- signals | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/alyx-matlab b/alyx-matlab index 6496bb3e..1d9f0a88 160000 --- a/alyx-matlab +++ b/alyx-matlab @@ -1 +1 @@ -Subproject commit 6496bb3e71814ba55bf0a7d5cee5a6aa41b393ef +Subproject commit 1d9f0a88531a86981e4fa9205c41c67623c254a0 diff --git a/signals b/signals index a38950eb..9f4f5d06 160000 --- a/signals +++ b/signals @@ -1 +1 @@ -Subproject commit a38950eb55f48feac1df040b3f1b666572ec7b89 +Subproject commit 9f4f5d068805d8dc715e332449086e42fa396da2 From dfae06db44405ebc27e3138071e146184f25454a Mon Sep 17 00:00:00 2001 From: Jai Bhagat <j.bhagat@ucl.ac.uk> Date: Tue, 28 May 2019 21:58:38 +0100 Subject: [PATCH 380/393] Update README.md --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index d6453f9f..8e3a9c44 100644 --- a/README.md +++ b/README.md @@ -39,8 +39,8 @@ git clone --recurse-submodules https://github.com/cortex-lab/Rigbox 2. Open MATLAB, make sure Rigbox and all subdirectories are in your path, run: > addRigboxPaths and restart MATLAB. -3. Set the correct paths on both machines by following the instructions in the '/docs/setup/paths_config' file. -4. On the stimulus server, set the hardware configuration by following the instructions in the '/docs/setup/hardware_config' file. +3. Set the correct paths on both computers by following the instructions in the '/docs/setup/paths_config' file. +4. On the stimulus computer, set the hardware configuration by following the instructions in the '/docs/setup/hardware_config' file. 5. To keep the submodules up to date, run the following in the Git Bash terminal (within the Rigbox directory): ``` git pull --recurse-submodules @@ -54,7 +54,7 @@ On the stimulus computer, run: On the master computer, run: > mc -This opens a GUI that will allow you to choose a subject, edit some of the experimental parameters and press 'Start' to begin the basic steering wheel task on the stimulus server. +This opens a GUI that will allow you to choose a subject, edit some of the experimental parameters and press 'Start' to begin the basic steering wheel task on the stimulus computer. ## Code organization @@ -68,7 +68,7 @@ The "data" package contains code pertaining to the organization and logging of d The "user interface" package contains code pertaining to the Rigbox user interface. It contains code for constructing the mc GUI (MControl.m), and for plotting live experiment data or generating tables for viewing experiment parameters and subject logs. -This package is exclusively used by the mc computer. +This package is exclusively used by the master computer. ### +exp @@ -76,7 +76,7 @@ The "experiments" package is for the initialization and running of behavioural e The package also triggers auxiliary services (e.g. starting remote acquisition software), and loads parameters for presentation for each trail. The principle two base classes that control these experiments are 'Experiment' and its "signals package" counterpart, 'SignalsExp'. -This package is almost exclusively used by the stimulus server. +This package is almost exclusively used by the stimulus computer. ### +hw @@ -90,11 +90,11 @@ The "psychometrics" package contains simple functions for processing and plottin ### +srv -The "stim server" package contains the expServer function as well as classes that manage communications between rig computers. +The "stim server" package contains the 'expServer' function as well as classes that manage communications between rig computers. -The 'Service' base class allows the stimulus server to start and stop auxiliary acquisition systems at the beginning and end of experiments. +The 'Service' base class allows the stimulus computer to start and stop auxiliary acquisition systems at the beginning and end of experiments. -The 'StimulusControl' class is used by the mc computer to manage the stimulus server. +The 'StimulusControl' class is used by the master computer to manage the stimulus computer. * *Note*: Lower-level communication protocol code is found in the "cortexlab/+io" package. @@ -114,7 +114,7 @@ The 'StimulusControl' class is used by the mc computer to manage the stimulus se ### cortexlab -The "cortexlab" directory is intended for functions and classes that are rig or cortexlab specific, for instance code that allows compatibility with other stimulus presentation packages used by cortexlab (e.g. MPEP) +The "cortexlab" directory is intended for functions and classes that are rig or CortexLab specific, for example, code that allows compatibility with other stimulus presentation packages used by CortexLab (e.g. MPEP) ### tests From 02de05faea0925df5fff05689de56c10c6e1ef8e Mon Sep 17 00:00:00 2001 From: Jai Bhagat <j.bhagat@ucl.ac.uk> Date: Wed, 29 May 2019 13:46:48 +0100 Subject: [PATCH 381/393] Changed name to "LICENSE.md" --- LICENCE | 202 -------------------------------------------------------- 1 file changed, 202 deletions(-) delete mode 100644 LICENCE diff --git a/LICENCE b/LICENCE deleted file mode 100644 index e06d2081..00000000 --- a/LICENCE +++ /dev/null @@ -1,202 +0,0 @@ -Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - From eecf01ffd3e411ee91c025b0ee20f1ddfd41d66c Mon Sep 17 00:00:00 2001 From: jaib1 <j.bhagat@ucl.ac.uk> Date: Wed, 29 May 2019 17:06:27 +0100 Subject: [PATCH 382/393] re-initialized and re-built readTheDocs files --- docs/readthedocsFiles/Makefile | 4 ++-- .../_build/doctrees/environment.pickle | Bin 1569317 -> 0 bytes docs/readthedocsFiles/_build/html/objects.inv | Bin 248 -> 0 bytes .../_build/html/searchindex.js | 1 - .../build/doctrees/environment.pickle | Bin 0 -> 1569453 bytes .../doctrees/index.doctree} | Bin 4764 -> 4773 bytes .../{_build => build}/html/.buildinfo | 2 +- .../{_build => build}/html/.nojekyll | 0 .../html/_sources/index.rst.txt} | 2 +- .../html/_static/ajax-loader.gif | Bin .../html/_static/alabaster.css | 0 .../{_build => build}/html/_static/basic.css | 0 .../html/_static/comment-bright.png | Bin .../html/_static/comment-close.png | Bin .../html/_static/comment.png | Bin .../{_build => build}/html/_static/custom.css | 0 .../html/_static/doctools.js | 0 .../html/_static/documentation_options.js | 2 +- .../html/_static/down-pressed.png | Bin .../{_build => build}/html/_static/down.png | Bin .../{_build => build}/html/_static/file.png | Bin .../html/_static/jquery-3.2.1.js | 0 .../{_build => build}/html/_static/jquery.js | 0 .../{_build => build}/html/_static/minus.png | Bin .../{_build => build}/html/_static/plus.png | Bin .../html/_static/pygments.css | 0 .../html/_static/searchtools.js | 0 .../html/_static/underscore-1.3.1.js | 0 .../html/_static/underscore.js | 0 .../html/_static/up-pressed.png | Bin .../{_build => build}/html/_static/up.png | Bin .../html/_static/websupport.js | 0 .../{_build => build}/html/genindex.html | 6 +++--- .../html/root.html => build/html/index.html} | 4 ++-- docs/readthedocsFiles/build/html/objects.inv | Bin 0 -> 242 bytes .../{_build => build}/html/search.html | 6 +++--- .../build/html/searchindex.js | 1 + docs/readthedocsFiles/make.bat | 4 ++-- docs/readthedocsFiles/{ => source}/conf.py | 7 ++++--- .../root.rst.txt => source/index.rst} | 2 +- 40 files changed, 21 insertions(+), 20 deletions(-) delete mode 100644 docs/readthedocsFiles/_build/doctrees/environment.pickle delete mode 100644 docs/readthedocsFiles/_build/html/objects.inv delete mode 100644 docs/readthedocsFiles/_build/html/searchindex.js create mode 100644 docs/readthedocsFiles/build/doctrees/environment.pickle rename docs/readthedocsFiles/{_build/doctrees/root.doctree => build/doctrees/index.doctree} (84%) rename docs/readthedocsFiles/{_build => build}/html/.buildinfo (82%) rename docs/readthedocsFiles/{_build => build}/html/.nojekyll (100%) rename docs/readthedocsFiles/{root.rst => build/html/_sources/index.rst.txt} (88%) rename docs/readthedocsFiles/{_build => build}/html/_static/ajax-loader.gif (100%) rename docs/readthedocsFiles/{_build => build}/html/_static/alabaster.css (100%) rename docs/readthedocsFiles/{_build => build}/html/_static/basic.css (100%) rename docs/readthedocsFiles/{_build => build}/html/_static/comment-bright.png (100%) rename docs/readthedocsFiles/{_build => build}/html/_static/comment-close.png (100%) rename docs/readthedocsFiles/{_build => build}/html/_static/comment.png (100%) rename docs/readthedocsFiles/{_build => build}/html/_static/custom.css (100%) rename docs/readthedocsFiles/{_build => build}/html/_static/doctools.js (100%) rename docs/readthedocsFiles/{_build => build}/html/_static/documentation_options.js (92%) rename docs/readthedocsFiles/{_build => build}/html/_static/down-pressed.png (100%) rename docs/readthedocsFiles/{_build => build}/html/_static/down.png (100%) rename docs/readthedocsFiles/{_build => build}/html/_static/file.png (100%) rename docs/readthedocsFiles/{_build => build}/html/_static/jquery-3.2.1.js (100%) rename docs/readthedocsFiles/{_build => build}/html/_static/jquery.js (100%) rename docs/readthedocsFiles/{_build => build}/html/_static/minus.png (100%) rename docs/readthedocsFiles/{_build => build}/html/_static/plus.png (100%) rename docs/readthedocsFiles/{_build => build}/html/_static/pygments.css (100%) rename docs/readthedocsFiles/{_build => build}/html/_static/searchtools.js (100%) rename docs/readthedocsFiles/{_build => build}/html/_static/underscore-1.3.1.js (100%) rename docs/readthedocsFiles/{_build => build}/html/_static/underscore.js (100%) rename docs/readthedocsFiles/{_build => build}/html/_static/up-pressed.png (100%) rename docs/readthedocsFiles/{_build => build}/html/_static/up.png (100%) rename docs/readthedocsFiles/{_build => build}/html/_static/websupport.js (100%) rename docs/readthedocsFiles/{_build => build}/html/genindex.html (93%) rename docs/readthedocsFiles/{_build/html/root.html => build/html/index.html} (98%) create mode 100644 docs/readthedocsFiles/build/html/objects.inv rename docs/readthedocsFiles/{_build => build}/html/search.html (94%) create mode 100644 docs/readthedocsFiles/build/html/searchindex.js rename docs/readthedocsFiles/{ => source}/conf.py (98%) rename docs/readthedocsFiles/{_build/html/_sources/root.rst.txt => source/index.rst} (88%) diff --git a/docs/readthedocsFiles/Makefile b/docs/readthedocsFiles/Makefile index 008e178e..ad1b93c9 100644 --- a/docs/readthedocsFiles/Makefile +++ b/docs/readthedocsFiles/Makefile @@ -5,8 +5,8 @@ SPHINXOPTS = SPHINXBUILD = sphinx-build SPHINXPROJ = Rigbox -SOURCEDIR = . -BUILDDIR = _build +SOURCEDIR = source +BUILDDIR = build # Put it first so that "make" without argument is like "make help". help: diff --git a/docs/readthedocsFiles/_build/doctrees/environment.pickle b/docs/readthedocsFiles/_build/doctrees/environment.pickle deleted file mode 100644 index 5b4e7604e73011a2f052bc313234e53881c11422..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1569317 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�~r$?(G2`x0+rAwOdVg}r< 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�{;M=wX&ceF~yOcLx9 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	To}e95NwKnPnd#%2Ja+a)U)>$W}ZPGrGlv<4O- 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pKc^ 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�W=*_} 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 diff --git a/docs/readthedocsFiles/_build/html/objects.inv b/docs/readthedocsFiles/_build/html/objects.inv deleted file mode 100644 index 36158842b12aff9dd19820cebe424cc45d1fc455..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 248 zcmY#Z2rkIT%&Sny%qvUHE6FdaR47X=D$dN$Q!wIERtPA{&q_@$u~G=iOi#+M0E&b` zWUUl{?2wF9g`(8l#LT>u)FOraG=-9k%wmPK%$!sOAf23_TTql*T%4MsP+FXsm#$Ei zlbNK)RdLJP|Lo~A-kxg%H1s?-p7QkZIvaSwG{mF5>s9KMC(kr0nr6i8{HYlcA{O%M zkcvWA?dM>x;-H09U7tRCA9xk~X|m%g$C{2!AFj-K^5;%S>!h021!ul2`P1djYuqi` taLTJV__LRgx6$X%B0IZ+g}Wyi2k5qRIP!3|onTj4w!DauVOErfBLHvWX!ZaA diff --git a/docs/readthedocsFiles/_build/html/searchindex.js b/docs/readthedocsFiles/_build/html/searchindex.js deleted file mode 100644 index f05d463b..00000000 --- a/docs/readthedocsFiles/_build/html/searchindex.js +++ /dev/null @@ -1 +0,0 @@ -Search.setIndex({docnames:["root"],envversion:55,filenames:["root.rst"],objects:{},objnames:{},objtypes:{},terms:{index:0,modul:0,page:0,search:0},titles:["Welcome to Rigbox\u2019s documentation!"],titleterms:{document:0,indic:0,rigbox:0,tabl:0,welcom:0}}) \ No newline at end of file diff --git a/docs/readthedocsFiles/build/doctrees/environment.pickle b/docs/readthedocsFiles/build/doctrees/environment.pickle new file mode 100644 index 0000000000000000000000000000000000000000..c7259874b4a953ea100d88ea854c64b3187c864d GIT binary patch literal 1569453 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­6<}%Lbv>3PQzTQFD^Cr81-B2o=O{ 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�f<|!0dj0ncK20UBO2dn`=15@OQ<0oG| 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℞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Ƚ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Ƚ$rm7^RNI5u^3CR49l?+tFQ)Zu@3985u30DTd^HG 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Ƚ$rm7^RNI5u{e;x zFXEOMFT--I#44=8TCB$gY{F)2#Ww7~PVB}W?8AN>#33BPQ5?q!oWg0G#W`HSMO?-e zT*GzT#4X&xUEIe5Ji=o<#WTFXOT5M#yu*8Z#3y{gSA540{K9VpZ5Bu{{DBY%h0q9# zaEO42h>R$RhUkciScrqT_!IGw5Q&fkNs$~W@E20yZ=^vwq(??%!avA@Y{-tB$b~$} zi~J~nLMV))D25U!iBc$y|4<g?Q2~`u8C6jYHBb|^Q3v%<9}Uq6P0$q0(E_c|8g0=I z9ncY-&>3CP4L#5kz0n8#&>sUa2tzOw!!ZJ*FdAbq4ihjDlQ9L;FdZ{73v)0R^RWPn zuoz3R3@fk_tFZ>_upS$+30trg+pz<?up4`^4+n4%hj9eQa2zLb3TJQ@=Wzj-a2Z!| z4L5KTw{Zvea32rx2v6`7&+!7U@EULN4j=FlpYa9X@Et$#3xQW)tID8(YE>CL@ZLNG zLLxN6ARNLYA|fFQq9QtCAQoaHF5)3R5+D&0BPo(01yUjvQX>u0B0Vx76EY(UvLZWj zAQy5YFY=)P3ZgKIpcsmyB>qKdltEdPLj_bsWmG{mR7XwJLLJmaeKbHLG)7Z2LkqM- zYqUW-v`0q-pfkFl8@i(>dZ7>cqCW;;5C&r?hG7IoVl>8J9L8fJCSeMuVmfAE7G`5E z=3xOAVlkFr8J1%uR$&d+Vm&rs6E<TjwqXZ$VmJ0+ANJ!Q4&exn;y6y=6i(wT&fx+s z;xew_8m{9eZs88@;yxbW5gy|yp5X;v;x*pj9p2+3KH&?#;yZre7k(pXi$H?m4}?G{ zghp6|Lj*)bWJEzUL`O`-LL9`!pNNlyNQ5LvisVRvzmN)lBMs6aJu)H_{y`RGLw4ju zF62R8<VOJ%LSYm|F_b__ltO9zhq5S-3aEt2sETT+ftsj|I;e;GXoyB=f~IJW7HEam zXp45}fR5;d&ghD6=z*T-jXvmy{uqcs7=ob~ju9Az(HM(yn1G3xj47Cg>6nRGn1i{P zj|EtS#aN1ESb>#TjWt+@_1K6_*n+Ltjvd&A-PntLIDmsVj3YRP<2Z>^ID@k|j|;ej z%eabbxPhCvjXSu9`*?^)c!H;Rju&`^*LaI}_<)c2j4$|x@A!#d2+}f;pa_mX5E7vf z24N8%5fBNH5f#x812GXBaS#uGA^{R2F_It|k|QPlLTdbtv`B{x$cW7N2U(E~Igk^% zkq7yZ9|cheMNkyQQ3C&>6w2T~ltXz`L?u)~Ra8d})Ix34MLje?Lo`McG(&T=L@TsG zTeL?91fUbTpewqg2YR75`l25OU?2u#2!>%eMq(7kU@XRC0w!THreYdqU?yf`4(4G# z7Ge>WU@4Yk1y*4-)?yttU?VnT3$|f9c48OyU@!LL01n|Wj^Y?j;3Q7t49?*^F5(id z;3}@;25#Xt?&2OE;2|F437+9OUg8zr;4R+c13uw1zTz8x;3s|~NUK1CAvi)HBtjz$ z!XZ2&A`+q?DxxC>Vj(u-A|B!+0TLlGk|G&WASF^EHPRq0(jx;hAv3ZdE3zX8av?YJ zA|DE%APS=hilI14;$M_T8I(miR6s>kMio>;b<{*H)InX;M*}oMV>CrGv_MO=MjNz4 zdvrtqI-?7^p*wn_7y6(t`eOhFVK9bb7)D?uMq>=dVLT>c5~g4(reg+XVK(Ms9u{CB z7GnvPVL4V}71m%a)?))UVKcU38+KqPc4H6rVLuMy5RTv|j^hMQ;WW<T94_D@F5?QW z;W}>O7Vh9K?&AR-;W3`#8D8KeUgHhk;XOX$6TaXpzT*de;WvV|4kQ@<KnR3FXoN*L zL_kDDMifLtbi_m~#6evAiTFr}L`Z_9NRAZv3#sro(jXnuBO@~5A7nu`WJgZqLLTHr zeiT3<6h=`LLkW~bDU`;4D2wu_fJ&&0s;Gt<sEOLBgL<fshG>K)Xo}`&fmUdZwrGbA z=!j0}jIQX09_WeQ=!1UfkAWD3AsC9`7=ck3jj<Sq37Ck<n1X4Tj+vN+Ihc$2Sb#-X zjHOtH6<CSYSc7#~kB!)bE!c|f*nwTxjlI~112~AoID%t1j*~crGdPR$xPVKzjH|eY z8@P$vxPyDRkB4}KCwPkIc!5`VjkkD*5BP}B_=0cvj-U92AZ-E(is1MIArT5;5EkJP z0g(_HQ4tL>5EHQx2l4PH5+ETGBMFirIa1;;q{iP!i*(3<jL3|CkQLdG138f!d5{nJ zQ4obt1VvFCCGamwp$z^*Ih035R6-S0MRn9bE!0L`)I$R_L}N5TGc-p_v_c!SMSFBW z06L)yx}rOJpci_hFZy8s24XOVU>JsDBt~Hj#$r4sU=k){DyCruW@0wxU>@dUAr@f? zmSQ<pU=>zlE!JTJHexfjU>mk$Cw5^E_F_K{;1CYuD30L-PU1Aq;2h55A}-+yuHrgw z;1+J<F7Dw09^x^c;2ECdC0^kT-r_wz;1fRME56|ee&RQRv<)N}f+GY%A~eDv9Ks_a zA|VQ*B06Fq7GfhV;vqf~AQ2KHDUu-tQX&;nBMs6bJu)B@G9wGJB0F**7jh#n@}U3< zqA-e}7>c7L{zYk&L0ObT1yn?3R6#XVM@`g19n?jAG(aOXMpHCH3$#RQv_U(xM@Iyp zGrFJ~x}zt0p%40^KL%hB24g6OVFX5EG{#^Y#$zHTVG5>VI%Z%NW@9eqVF4CmF_vH% zmSZJWVGY(|JvLwyHe)NcVFz|%H}+s3_TwN9;Ruf6I8NXcPU9@j;Q}t=GOpknuHz<d z;STQNJ|5r^9^)yV;RRmeHQwMI-s2-a;S0XvJAU97ej{kRK!V{9gg_{SMp%SH1Vlt+ zL_st}M@+;*9K^++h>wIwgd|9c<Vb<PkP3ez4bmY!G9nZHK^9~~cH~4Z<UwBKM*$Q< zVH8C%lt4+8LTUVmvM7%VsD#R>ifX8Vny8IBsE7J!h(>6Frf7~9Xoc2ji+1RMj_8EW z=!$OWfu87%KIn)37>Gd_f}t3W5g3Kh7>jY3fQgulDVT=on2A}KgSnWG1z3c|Sc+v> zft6T|HCTuB*oaNog00w&9oU84*o%EQfP*-UBRGcRIEhm@gR?k~3%G>KxQc7Ift$FE zJGh7Yc!)=Mf~R<n7kGu&c#C)VfRFf$FZhP<_=#T#(ms%&2#!Ax5}^<VVG$k?5DAeH z710m_F%cVa5D$MM0TLoHk{}t9BPISqYW$6~NQVr_h|Ks0S&<DnkQ2F)2l<d61yKk^ zP!z>c0{@~E%HThgLwQt0B~(FGR7VZeLT%JVJv2Z=G)5CNLvyr5E3`pdv_}U7pcA^F zE4rfxdZ9P^q8|oeAO>RyhG95HVid+;EXHF3CSfwBVj5;(CT3#}=3zb-ViA^LDVAde zR$(>PVjVVMBQ|3TwqZMVVi)#cFZSaA4&gA4;uucgBu?WD&fz>R;u5alDz4)OZs9iW z;vOF0As*uip5ZxO;uYTDE#Bh;KH)RI;v0V8Cw?PHhd_cMI6@#KLL&^qAv_`?5~3g~ zq9X=kAvWS79^xYb5+N~?A{kO3B~k?v&^&dZUPY%7r9*mTL?--$EXaoJ$cbFYgS^O( z0w{#SD2iezfs!bN()bT$Q63dg36)V5)ldU9Q5$to5B1RyjnD*5(Ht$%3a!x=?a%=o z(FvW=72VJSJ<%I|&=37F5Q8uTLopm9FbbnF7UM7h6EPW6Fb&f&6SFV}b1@$aun3E> zG?0Mi%L27nD@3cX8f&o*8?X_Z0|{upB~TA+6YaoG?8YAK!+spZAsoR`9LEWq!fBkv zIb6U+T*eh#!*$%mE!@Ff+{Xhv!eczeGrYh{yv7^6!+U(hCw#$Ie8&&`!fyoa7)UVu zfe;9V&<Klgh=7QQj3|hP=!l6}h=aKJ6Y-HSkbvfi0yViwM9GjGDe)Il<8P!zI%Ggb zWX3<pifqV%oXCwl$cOwWh(aiWq9~3M_!p&62LGWP%A+DGp$e*^I%=R6YNIadp#d7A zF`A$mnxiFJp$*!iJvtx&ozMkc(H%X|3%$`7{V)InF&INI48t)Jqc8?zF&+~z36n7u z(=Y=wF&lF*5A(4Qi?9Ssu^cO~3ahae>#zYEu^C&i4coC3yRZj)u^$I;2#0YL$8Z8C zaT;fE4(D+Zmv9AFaUC~s3%79>_wWD@@fc6=4A1crukZ$M@g5)W37_#5-|z!J@f$$` z0ttrT2!W6YjW7s@@Q8>=h=Qnyju?oA*ocdGh>rwFgv3aSWJrOONQKl$gS1GG49JAc z$bziMjvUB^+{lZ3D1d?}j3Ow8;wXuKQ5t1X7UfU@6;T;gPz}{l6SYtWbx|J;&<Ksu z6wS~AEzufn&<^d<5dr9oF6f5t=!stFgTCmG0T_hA7>Z#Sfsq)EF&KyOn21T3f~lB} z8JLCHn2UK>fQ49$C0K^#Scz3wgSA+X4cLUu*otk~ft}cmJ=ll+IEX_yf}=Q&6F7y_ zIE!<*5J*7ti-9WHmqk}`4L5KTw{Zvea32rx2v6`7&+!7U@EULN4j=FlpYa9X@Et$# z3qd*s5){Gl2SOqg!XPZdBLX5JGNK|HVjw1BBM#!>Pb5G>Bt{Y>Lvp0VUr3F=krwHY z0U41Q{~#-}AqR3IH}W7K@}nRMp$LkiI7;AOltLN&hjJ*7il~GtsEX>Sfm*1Ix~PW+ zXo$vWf@WxrmS}}GXp8pffB<wt7j#8;^gu84Mql*901U)n48brA$4HFA7>vbuOu!^e z##Bth49vuA%)va&$3iT^5-i1XtiUR)##*ey25iJ;Y{52c$4>0R9_+<_9KazQ#!(!@ z37o`foWVJq$3<Mi6<o!2+`ui|#$DXQ13biIJi#+O$4k7z8@$DPe84As##em95B$V$ z1nC?|Fa$>kghXhBK{$j*L_|UqL`8JOKrF;YT*O0sBtRl0Mp7h03Zz6Tq(&N~MS5gF zCS*nyWJPx5KrZA)UgSf8KmwW<3{(j#EGmZL_!p({AIhQvDxwOiq6TWB4(g%-8lnlB zq6J!_4cejuI-)bWpgVe?H~OGI24FCTU^qr#G{#^&CSVdKV;ZJo7G`4}=3@~SV;PoX z6;@*%)?*VkV;i<(7j|PG_Tvx^;~0+P6i(wD&f^j;;~K8xCT`&l?&1L+;t8JO1zzGc z-rzkx;4{A9JAU9df^-QaIQ~E=ghn`oM<hf>G(<-%#6~>)iG)amq)3L8_zQm{4bme6 zGUFd)Lw4jsZsbFL6hdJXLvj3zQYeG}P#zUf8C6gnHBcLMP#+D@7){U|EzlZm&>kJo z37ydm-O&rZ(GUGG2!k;U!!Zh@F%IJ~36n7m(=iLPF&Fc&01L4gORyX(unMcO7VEG9 z8?gmju^l_G3%juw`*0A4a1_UI5~pwmXK@}Ea2Z!{9XD_rcW@sM@EA|<953)1Z}1); z@Cl#s4d3w#zY(l!Ai)t5p%50~5D}3O710nAu@D#WkN^ph1WAzsDUlj~BOTHs6EY(! zvLPpOAusZwAPS)<ilGEbqBP2&9Ll2-Dx(^zqZVqT9_phJ8lxGSqZL}C9oi!RozNBC z&=bAT7yU30gD@1sFcPCM7UM7xlQ0$2FcY&d7xS<Xi?9^SuoA1V7VEGPo3ItzuoJtm z7yED!hj0|fa1y6*7Uysgmv9x=a1*z17x(Z05Ag&~@d7XL25<2JAMpiW@dH2c8$r4S z5*&XZ6hb2$!XqLgAu6IFCSoBj;voSNA_<Zr1yUk4{zf{aM<!%OR%Am?<U(HLLje>- z5fnuUltgKiK{=F1B~(T=R7Wk;Mm^L=BQ!=cG)F76Mmw}e06L*7x}hg}p)dMjAO>M5 zhG8T|VJyaBA|_!freP*#VJ_xjAr@gNmSH7UVGY(|12$p{wqgf%Vh{G>01n~^j^YGP z;tbB>0xseTuHpu6;tuZO0UqKBp5g^w;tk&713uylzTyXdB1rc@g5nQ^Kxl+Pctk*C zL_u`KKy1XppNNk{NQ`7ij=zu!X^<8fkP-hN3$h~zaw8A&qW}t{2#TWwN})8$q8uus z5~`vaYN8hEq8=Ke5t^bITA~%&q8&OS0A0`(-O&TR(Fgr80E00E!!ZJ*F$Uu?0h2KW z(=h|HF$eRp0E@8%%drBhu?Fk00h_S}+pz<?u?PEc0Eck|$8iFuaR%pc0he(F*Kq^4 zaR>MD0FUtm&+!7U@doel0iW>&-|!v3@EgH;1QHw}5ei`u4iOOvQ4tL>5esn<4+)SE zNstuDkpiiZ8flRZ8IcKDkQF(Q6M2vq1yB%0P!uIl5~WcF<xn1#P#M)w9koy!^-v#; z&=}3o9IenA?a&?p=!CB5hMwq!zUYU67=)o1hLISBF&K*pn20HuiW!)RIhczDSct_~ zg5_9&)mVe|*nrL0g6-IW-PnWuIDo@Af@3(2Q#g%tIFC!XjBB`#TeyvTxQ|D8jAwX` zS9pzgc#lu`jBogkU-*q+Jp&1jkO+ma2#1J>gs6yyn23eAh=&A7h$Kjg6iA8G_#5ev z9+{9CS&<Dnkqdc|4+T*OMNteT@h{5YKa@uWR7Mq4M-9|Q9n?nyG)5CNM+>w@8?;9U zbV6rzLwEE-Z}dZd48mXx!*GnkXpF;nOu}SL!*tBTY|O)aEW%<e!*Z;`YOKS0Y{F)2 z!*=Y#ZtTN;9KvB7!*QI#X`ID5T*M_@#Wmc-E!@RDJj5eB#WTFbE4;y5e85M1!B_mi zPXy@|NKpKN5D1Mh2#*Mej3|hX7>JEH_!IGw2#Jvl$?+FbAq~<Z12W<tWI=Z1KyKtg zeiT4q6hU#6Kq-_)S(HOXR6<o$Lrv5|UDQKEG(uA}Lrb(mTeL$*1fUDLq6d1SH~OGI z24FCTU^qr#G{#^&CSWqAU^-@CHs)YH7GN=!U^!M`HP&Dq)?*VkV;i<(7j|PG_Tvx^ z;~0+P6i(wD&f^j;;~K8x7H;D%?%^RG;VGWs1zzF}-r@s3;tRgwJAU97ej{k_K!V{9 zgg_{SMp%SH1Vlt+L_st}M@+;*T*N~HBt#M<MGB-uYW$6KNRLd&jI79poXCZ|$cKU` zgrX>hlK2;8@E^*f0xF{ls-p&KqYmn$0UDzTnxh3;qYc`l13IBIx}iIIp*Q-WKL%ki zhG95HVKl~K942BCreYdqVix9N9u{H|mSP!JVine69X4VUwqhH0Vi)#cFAm@!4&w-p z;{;CQ49?>MF5?QW;|6Zy4({Ut9^(m~;{{&h4c_AeKI03%;|G2tNS{E0;}3*FXoN#} zL_%alLv+MKY{bK#NQgv8ieyNMzwkHGAU!f5GyXv~WJfOKMn2?6ArwY26vw|Ph5t|% z6;KgXP!%;$6LnA*4bTux&=f7u5^c~H9ncY-(FNVn1HI7){V@Q8F$BXg0;4eo<1qn~ zF$L2x1G6y)^RWPnu>{Mp0;{nG>#+fwu?5?)1G}*Y`*8q=aRkS40;h2X=Wzj-aRt|L z1GjMp_wfLa@dVHD0<ZA~@9_bj@de-U1HTcZZy>?(2SOn<!XZ2&Au^&NI$|L<;^9vu zKtd!zQY1$Tq(W+>MLJ|eCS*ZY<Umg3L0%L<K@>qzlt4+8Mj4btc~nAWR6}*tLT%JT zeKbO2G(&T=LTj``djy~px}qC;q8Iw29|mF&hGG~-Vid+=942BCreYdqVix9N9u{H| zmSP!JVine69X4VUwqhH0Vi)#e9}eOWj^Y?j;uOx}94_J#uHqVQ;uh}W9v<Qmp5hr^ z;uYTF9X{d{zTz8x;unJU3nUmqASA*dEFvHxq97__ASU7<F5)8r5+ezcBLz|+HPRv- zG9nYQAS-epC-NXK3ZNj0peRb9Bub+U%Ay=9q7tg28fu~z>Y^SRq7j;+8Cs$h+M*pg zA^@Gy1>MmDz0n8#F#v-x1j8``qcH~KF#(e?1=BGDvoQzru>gy)1k14ktFZ>_u>qU0 z1>3O$yRirRaR7&L1jlg#r*Q`7aRHZc1=n!{w{Zve@c@tU1kdpTuki-&@E)J=8Q<_7 zzwjHu`UesmArT5;5e^X%2~iOZF%b)K5f2HF5J`{}DUcGW@i)>TJu)FPvLYLDA{X)^ z9}1!nilP`w;$M`(e<+U%sEjJ8jvA<qI;f8ZXpAOkhURF6)@X<J2tX%vMK|<7PxL`w z48TAP!BC9ANQ}W)Ou$4;!Bot^Ow7StEWko6!BVWiO02<JY`{ir!B*_RPVB*69Kb;w z!BL#RNu0r1T);(K!ByPAP29m<JitRd!Bf1zOT58be85M1!B_miPXrkdNKpKN5D1Mh z2#*Mej3|hX7>JEH_!IGw2#Jvl$?+FbAq~<Z12W<tWI=Z1KyKtgeiT4q6hU#6Kq-_) zS(HOXR6<o$Lrv5|UDQKEG(uA}Lrb(m8?;3SbVO%#L3i{(Z}dTb48ULv!ElVgXpF&l zOu%GJ!F0^PY|Ozt%*P@u#xg9&Dy+sjtj8v7#x`unF6_oW?8hM-#xWenDV)YRoW~_x z#x-2WE!@UE+{Yt4#xp#}E4;=#yvHYe#y5P&FZ@QZfq?`^NQ6RIghNC`LR3UUOvFN5 z#6tokL=q%L3Zz78{Ec)-k4(sntjLC($c4Pfhk_`Cq9}%v_!njHAIhTwDx(UjqXufD z4(g);8lwrCqXk-{4cemv0?-Lv(G5M(3w_ZK12G6gF$^Oy3S%)26EO)>F%2^@3v)3K z3$X}Gu?#D*3Tv?r8?gynu?;)13wyB-2XP2TaSSJM3TJT+7jX$!a23~a1GjJ+_i!JN z@EFhV9Ix;i@9-X<@EPCm9l!7!K?emA3?UE_VGtG(5D`%j6)_MKaS#{rkpPL21j&&C zsgN3Jkq#M=30aU8Igk^1kQW6|5JgZFB~TKjQ3mBu9+glT)leO^P#g77AC1r$&CndJ z&>HQ~9s%ftuIPrI=!L%Mhk+P`p%{jd7=^JIhl!YkshEZtn29-<iv@uMHuYU-yaY?J z0xPiwYq0?vu?1VP13R$?dvO2<aRf(k0w-|>XK?`+aRpa#12=I8ckuuZ@dQut0x$6f zZ}9;i@daP;13wXDa3DeP2SOk;!XP{%ATpvLI$|I;;^0rjM<OIfG9<@eNQE>=iwww! ze~<;)kpsDr2l-I|g;4~>Q39n<8f8%q6;TOQQ4KXv6LnA*4bTux&=f7u5^c~H9ncY- z(FNVn1HI7){V@Q8F$BXg0;4eo<1qn~F$L2x1G6y)^RWPnu>{Mp0;{nG>#+fwu?5?) z1G}*Y`*8q=aRkS40;h2X=Wzj-aRt|L1GjMp_wfLa@dVHD0<ZA~@9_bj@de-U1HTbu zNFc%S2SOn<!XZ2&Au^&NI$|L<;^9vuL?R?bGNi;`_#0`E9vP4s{~#N(BNuWbAM&FR z3Zoc`<6o4*e<+IzsE8`4iW;biI;e{VXox0giWX>zHfW0u=!nkfg6`;n-spq=7=XbT zg5el}(HMjAn1IQcg6Wun*_ea*Sb)V?g5_9&)mVe|*nrL0g6-IW-PnWuIDo@Ag5x-W z(>R0kxPZ&Jf@`>rTeyvTxQ|D8jAwX`S9pzgc#lu`jBogkU-*q+LjwtpkO+ma2#1J> zgeZuL7>J2Dh>Q40fW%0G<Vb;3NR6~ehm6RCEXax+$ca42ivlQ!A}ERyD2dW2gK{X3 zN~nx#sE%5wje4k$Mre#?XpUBBjdo~{0CYlEbVE<{LSOX5Kn%iA48ur_!dQ&ML`=d| zOv6mf!d%S5LM*~kEW=8y!dk4uMr^`XY{O3M!d~pdK^($S9K%VR!daZdMO?yFT*FP= z!d=|MLp;J$Ji|-8!dtw<M|{Fpe8W%tLeOD>1VadfL>Poc1Vls>L`4k5L>$CLd?Y|( zBtdedKq{n0TBJipWI`5XMGoXd9^^#<6hsjeMG2HdX_P@Zlt(30Mm1DNE!0Lm)JG#U zMl&==E3`&Cv_}9sp)0zfCwieT`e7gjVJL=SBt~H@#$h5RVJfC!CT3wS=3yZgVJVhj zC01cA)?p(yVJo&_Cw5^k_TeB7;V6#bBu?Qh&fy|1;VQ1-CT`&_?%^RG;VGWsC0^kz z-r*xY;VZu32Yw>R@IZp%4}?Hygh6;jKx9Ngbi_bx#KE74k3>j}WJr#`kP2y#78#Hc z{~!ypBL{LL5Ave`3Zn>$qXbH!G|HkJDxwmqq8e(V7V4rN8ln-Jq8VDE722X5IwAmF z&=oz<6MfJZ127OnFcc#&5@RqH6EG1|Fcs4=1G6y)^RWPnu>{Mp0;{nG>#+fwu?5?) z1G}*Y`*8q=aRkS40;h2X=WrgEa2eNd9k*~B_i!JN@EFhV9Ix;i@9-WU@d;n?4L|V< zK}Q4<3?UE_VGtG(5D`%j6)_MKaS#{rkpPL21j&&CsgN3Jkq#M=30aU8Igk^1kQW6| z5JgZFB~TKjQ3hpE4i!)lRZtbxQ3JJ58}(2hjnEj)&>XGM8tu>?0qBIT=!Tx?g}&&A zff$6L7>1D;g|QfiiI{|`n1-2{g}IoAg;<28Sca8Yg|%3Rjo5@O*oqz4i9Ohh12~Ao zID+FifzvpH^SFS^xPt4rfm^tZySRr3c!(!>iWhi^H+YK=_=qp~iXZriAR_|_ia!tn zp%Dh*5do1A1<?@$u@MJ<B0dr!F_IxU{z59GL0V)$M*M>;$c`Myh1|%8{3wLND2C$r z7p3qY%Ax`)q6(^_25O=X>Y@P}q6wO!1zMsF+M)wGqBFXnJ9?lu`k){BV-N;o7=~jM zMq?btV-hA~8m40wW@8@aV-Xf(8J1%eR%0F3V-q%G8@6K?c4Hs*;}8zx7>?r<PU9TT z;}S088m{9OZsQ*A;}IU?8J^=6UgI6!;}bsP8@}Tgek0hZK!PJALLn@|AtE9nDxx7~ zAOX!|1?nfyIHGv?6A6$IiID`!kQ^!T7gFPIq(wSpKt^Q7Kgfz~$bp>5jXcPQ{3wV* zD1xFWjuQA6rBDX{p&ZJiA}XN@s-ik-pcZPQF6yBH8lo|ppc$H@C0d~k+M+!=AOM}v z1zph{J<toi(HH$N00S`?Lof`(F%qLN24gWE6EF#rF%{D=12Zujb1)C{u@H-}1WU0T zE3gWyu@>vF0UNOyTd)n=u@k$n2Yay}2XF|7aTLdJ0w-}AXK)VZaS@kr1y^w$H*gEL zaToXS01xpPPw))S@e;4_25<2mAMgpE@fF|j13&Q_K}H7>48aisArTs35DwuH5s?rD zQ4t+65DT#p7x54u36Kbhkrc_00x6LSsgVY0kscY437L@vS&<z%kPEqy7x_>C1yLA9 zPz=RU691w!%AhRDp#mzRGOC~&s-q@qp$_VzJ{q7A8lx$ip#@r^HQJyZ+M^=^&>3CO z4c*Zbz0fC+faZMzRk-?#24WC~U?_%T1V&*r#$p^MU?L`C3Z`K?W?~lRU@qok0Ty8~ zmSP!JU?o;#4c1{jHewUDU@Nv`2X<jM_F^9n;2;j;2#(=6PT~~K;4IGL0xsb)uHqVQ z1QO8vW}qhRw&*VI;Q=1vF`nQVp5rB6;SJv6JwD(QKI1FC;Rk-=H-d}_Bp8At1VSP- z!XO;NBO)Rp3Zf!9Vjvb`BQD}0J`x}i5+f;+Aq7$*6;dM&(jq-FAQLhp3$h|Rav&FS zBQNry01Bcoil7*Zqa^-CX_P@(ltTqnL}gS#HB?7U)IuH9MSV0tBQ!=+G(!utL~FD` zJG4hf1fVmzpc}fQCwid|`l3GuU=RjlD28DKMq)I^U>wF{A|_!9reZo~U>0U$F6LnY z7Gg1$U>TNUC01b#)?z(2U=ucDE4E<=c49a7U?2A5AP(UOj^a2@;1o{dEY9HqF5)t- z;2N&uCT`&l?&3Zk;1M3<DW2g4Ug9<0;2qxMBR=5^zT!K6;1_-)=-5Dl;SYpBD1=5> zghK>GL}WxkG(<;C#6ldz#h-|egh+%WNQ&f0fxnOne<KajAw4o86aGOKWJ7l3L@wk( zUgSps6hdJXMKP2>Nt8lq{D-nAj|!-S%BYHJsDYZOjXJ1@`e=woXo99_juvQz)@X}% z=zxysgwE)SZs>uY=#4(;hyECdK^TIe7>*Gbh0z#`ahQOKn2afyhUu7zS(t;ln2!Zm zgvD5jWmtigSdBGUhxOQqP1u61*p408h27YTeK>%FIE*7WhT}MiQ#gaOIFAdsgv+>! zYq)`%xQ#owhx>SlM|gs#c#ao%h1YnCcldyh_#8+;^Dlwg2EU1Z;unIB3nUmqASA*d zEFvHxq97__ASU7<F5)8r5+ezcBLz|+HPRv-G9nYQAS-epC-NXK3ZNj0peRb9Bub+U z%Aq_ep)#tWI%=Ud>Y+Xwp)s1FIa;AL+MzuH&<S194L#8debEmCF$hC33?nfLV=)dB zF$q&K4Kpzdb1@GKu?S1C3@fn;Yq1U+u?btT4Lh+5d$A7(aR^6o3@334XK@Y}aS2y( z4L5NMcX1C7@d!`x3@`BtZ}AQv@d;n?4L|V<LB|IY3?UE_VGtG(5D`%j6)_MKaS#{r zkpPL21j&&CsgN3Jkq#M=30aU8Igk^1kQW6|5JgZFB~TKjQ3mBu9+glT)leO^P#g77 zAC1r$&CndJ&>HQ~9s%ftuIPrI=!L%Mhk+P`p%{jd7=^JIhl!YkshEbDn1#8RhlN;# zrC5fQScSD%hmF{Tt=NX0*oD2=hl4nTqd10>IEAw~hl{v`tGI@nxP`m8hlhBCr+9{! zc!jrkhmZJ#ulR<a_=TVo0tto?2#GKViwKB_D2R#}h>19ei}*-@#7KhVNP$#HjkHLI zjL3v6$ch}ui9E=Q0w{<gD2fs&iP9*8awv~VsElf;j#{XVdZ>>^XpClPj#g-mc4&_P zbV65jLr?TVU-ZL348l+h!$^$6Sd7C&Ou|%5!%WP=T+G8lEW%PO!%D2eTCBrHY{FJ- z!%pnNUhKm`9Kul?!%3XNS)9W~T*6gc!%f`6UEIS%Ji=2v!%MuvTfD<Ze8N|J!%zG| z(20QrLkNUK7=%RxL_`!sMGVA59K=O@BtT*$L2{%(Dx^kQq(eqzLKb924&+20<V67# zL=hB436w->ltDR^M<rB7HB?6})J8qjM<X;wGc-plv_?C$M*upZE4ra4dZ91+VIT%! zD28DqMqw<*VIn4BDyCs3W??SoVIdY_DVAX+R$(pHVIwwSE4E=Lc405};UEs-D30MI zPT?%h;UX^KDz4!sZs9KO;UOO3DW2gaUg0g?;UhlbE56|;ej(_jK!PCzLLv;pA_5{J z3ZfzgVj>RWB0drzF_IuTQXmylBQ4S)BQhZivLXj^A`kMS01BcAilPKcqBP2&9Ll2- zDx(^zqZVqT9_phJ8lxGSqZL}C9oi!RozNBC&=bAT7yU30gD@1sFcPCM7UM7xlQ0$2 zFcY&d7xS<Xi?9^SuoA1V7VEGPo3ItzuoJtm7yED!hj0|fa1y6*7Uysgmv9x=a1*z1 z7x(ZGkMI=F@Di`^7Vq#8pYRpm@DslfbaEiU5CS0)24N8a5fKGZ5d$$12XPS}36K~` zkQ^zH3aOD6>5vhbkOf(h138fgc~JlbQ3OR%0wqxzWl#>~Q3;h%4b@Q#wNVfC(Fl#v z49(FBt<etc5r9tUif-tMUg(Q{7>Gd_ieVUuQ5cJHn21T3ifNdMS(uA?ScpYfie*@d zRalF4*oaNoif!15UD%6#IEX_yieos5Q#gxrxQI)*ifg!uTeypRc!)=Mif4F<S9pte z_=r#Vif{OdUkExSkYEUbkO+gYh=7QQf~bgrn23Y8h>rwFj3h{o6i9{CNQ-pHh)l?W ztjK|!$b-BnfPyH3q9}oqD2*~Ghw`X|%BY6wsD;|7hx%xQ#%PA-Xoc2jhxQ0SCv-(O z^h7W8ML!J0APmJYjKnC6#W+mFBuvFL%)~6r#XKy;A}qx+ti&p;#X4-nCTztv?8GkY z#XcOwAsodqoWv=d#W`HWC0xZd+{7*1#XUU4BRs`3yu>TK#XEe&Cw#>>{KPK=of=3m zgg{7yL0CjUL_|SU#6V2ML0rT~0whKfBu5IQLTaQ%I%Gs9WI<NsKu+XAUKBt<6hTpx zKuMHF8I(hLR6=D`Lv_?bZPW`Spn3g39Y!@2HAWLOLvyr5E3`pdv_}U7pcA^FE4rfx zdZ9P^q8|oeAO>RyhG95HVid+;EXHF3CSfwBVj5;(CT3#}=3zb-ViA^LDVAdeR$(>P zVjVVMBQ|3TwqZMVVi)#cFZSaA4&gA4;uucgBu?WD&fz>R;u5alDz4)OZs9iW;vOF0 zAs*uip5ZxO;uYTDE#Bh;KH)RI;v0V8Cw>PK5HKQMv(_COweQfjX|vW{>a?!cs!_u_ z&02SA)VgDv_FX6RoG>C%o!0*^v!oz1IXgZxJukl~bxQw~9=?+Nl>GR_oSb+Nqj*Z} dlpewOvdq--<ouM>_}u)I(wx*{pytvfJpf3+cSHaH literal 0 HcmV?d00001 diff --git a/docs/readthedocsFiles/_build/doctrees/root.doctree b/docs/readthedocsFiles/build/doctrees/index.doctree similarity index 84% rename from docs/readthedocsFiles/_build/doctrees/root.doctree rename to docs/readthedocsFiles/build/doctrees/index.doctree index d8006b2050acade832c6ee45566b9565ce97f730..1a2803413613e76646287dd1371c0a2820cc941f 100644 GIT binary patch delta 133 zcmbQEx>S{=fo1Bfi7bnR!c$Wed=o1bj4Txl&8-Yftc)h^IE_`bVlpq&baN|btC-N@ z)S}`Tuf)ulpv?57{EC>A{N&=8qSVBckc?CpAlof7C$%`HIKQ+gIW;CTFD12N^JOL% NMmAOmTZW~94*-ZHEt3EM delta 124 zcmZ3gI!BeIfo1B%i7bnR+=?<4d=o1bj7$^^jjasKtqdpbIE_`bVlpq&bbTXdtC-N@ z)S}`Tuf)ulpv?57{EC>A{N&=8qSVBcl8jUk+buIE6)2FOU$Xf&lM5p&3z!MCln(&x C2`VT6 diff --git a/docs/readthedocsFiles/_build/html/.buildinfo b/docs/readthedocsFiles/build/html/.buildinfo similarity index 82% rename from docs/readthedocsFiles/_build/html/.buildinfo rename to docs/readthedocsFiles/build/html/.buildinfo index eae1f52b..9da8011e 100644 --- a/docs/readthedocsFiles/_build/html/.buildinfo +++ b/docs/readthedocsFiles/build/html/.buildinfo @@ -1,4 +1,4 @@ # Sphinx build info version 1 # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: 20c7f121df0d240593df77635760ec12 +config: adfda6a0c469d3cdb652aa0ef11c5293 tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/readthedocsFiles/_build/html/.nojekyll b/docs/readthedocsFiles/build/html/.nojekyll similarity index 100% rename from docs/readthedocsFiles/_build/html/.nojekyll rename to docs/readthedocsFiles/build/html/.nojekyll diff --git a/docs/readthedocsFiles/root.rst b/docs/readthedocsFiles/build/html/_sources/index.rst.txt similarity index 88% rename from docs/readthedocsFiles/root.rst rename to docs/readthedocsFiles/build/html/_sources/index.rst.txt index 22c68b15..40462dbc 100644 --- a/docs/readthedocsFiles/root.rst +++ b/docs/readthedocsFiles/build/html/_sources/index.rst.txt @@ -1,5 +1,5 @@ .. Rigbox documentation master file, created by - sphinx-quickstart on Fri May 24 13:07:11 2019. + sphinx-quickstart on Wed May 29 17:04:21 2019. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. diff --git a/docs/readthedocsFiles/_build/html/_static/ajax-loader.gif b/docs/readthedocsFiles/build/html/_static/ajax-loader.gif similarity index 100% rename from docs/readthedocsFiles/_build/html/_static/ajax-loader.gif rename to docs/readthedocsFiles/build/html/_static/ajax-loader.gif diff --git a/docs/readthedocsFiles/_build/html/_static/alabaster.css b/docs/readthedocsFiles/build/html/_static/alabaster.css similarity index 100% rename from docs/readthedocsFiles/_build/html/_static/alabaster.css rename to docs/readthedocsFiles/build/html/_static/alabaster.css diff --git a/docs/readthedocsFiles/_build/html/_static/basic.css b/docs/readthedocsFiles/build/html/_static/basic.css similarity index 100% rename from docs/readthedocsFiles/_build/html/_static/basic.css rename to docs/readthedocsFiles/build/html/_static/basic.css diff --git a/docs/readthedocsFiles/_build/html/_static/comment-bright.png b/docs/readthedocsFiles/build/html/_static/comment-bright.png similarity index 100% rename from docs/readthedocsFiles/_build/html/_static/comment-bright.png rename to docs/readthedocsFiles/build/html/_static/comment-bright.png diff --git a/docs/readthedocsFiles/_build/html/_static/comment-close.png b/docs/readthedocsFiles/build/html/_static/comment-close.png similarity index 100% rename from docs/readthedocsFiles/_build/html/_static/comment-close.png rename to docs/readthedocsFiles/build/html/_static/comment-close.png diff --git a/docs/readthedocsFiles/_build/html/_static/comment.png b/docs/readthedocsFiles/build/html/_static/comment.png similarity index 100% rename from docs/readthedocsFiles/_build/html/_static/comment.png rename to docs/readthedocsFiles/build/html/_static/comment.png diff --git a/docs/readthedocsFiles/_build/html/_static/custom.css b/docs/readthedocsFiles/build/html/_static/custom.css similarity index 100% rename from docs/readthedocsFiles/_build/html/_static/custom.css rename to docs/readthedocsFiles/build/html/_static/custom.css diff --git a/docs/readthedocsFiles/_build/html/_static/doctools.js b/docs/readthedocsFiles/build/html/_static/doctools.js similarity index 100% rename from docs/readthedocsFiles/_build/html/_static/doctools.js rename to docs/readthedocsFiles/build/html/_static/doctools.js diff --git a/docs/readthedocsFiles/_build/html/_static/documentation_options.js b/docs/readthedocsFiles/build/html/_static/documentation_options.js similarity index 92% rename from docs/readthedocsFiles/_build/html/_static/documentation_options.js rename to docs/readthedocsFiles/build/html/_static/documentation_options.js index 5cb7e4e6..bed3ec74 100644 --- a/docs/readthedocsFiles/_build/html/_static/documentation_options.js +++ b/docs/readthedocsFiles/build/html/_static/documentation_options.js @@ -1,6 +1,6 @@ var DOCUMENTATION_OPTIONS = { URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), - VERSION: '2.1', + VERSION: '2.2', LANGUAGE: 'None', COLLAPSE_INDEX: false, FILE_SUFFIX: '.html', diff --git a/docs/readthedocsFiles/_build/html/_static/down-pressed.png b/docs/readthedocsFiles/build/html/_static/down-pressed.png similarity index 100% rename from docs/readthedocsFiles/_build/html/_static/down-pressed.png rename to docs/readthedocsFiles/build/html/_static/down-pressed.png diff --git a/docs/readthedocsFiles/_build/html/_static/down.png b/docs/readthedocsFiles/build/html/_static/down.png similarity index 100% rename from docs/readthedocsFiles/_build/html/_static/down.png rename to docs/readthedocsFiles/build/html/_static/down.png diff --git a/docs/readthedocsFiles/_build/html/_static/file.png b/docs/readthedocsFiles/build/html/_static/file.png similarity index 100% rename from docs/readthedocsFiles/_build/html/_static/file.png rename to docs/readthedocsFiles/build/html/_static/file.png diff --git a/docs/readthedocsFiles/_build/html/_static/jquery-3.2.1.js b/docs/readthedocsFiles/build/html/_static/jquery-3.2.1.js similarity index 100% rename from docs/readthedocsFiles/_build/html/_static/jquery-3.2.1.js rename to docs/readthedocsFiles/build/html/_static/jquery-3.2.1.js diff --git a/docs/readthedocsFiles/_build/html/_static/jquery.js b/docs/readthedocsFiles/build/html/_static/jquery.js similarity index 100% rename from docs/readthedocsFiles/_build/html/_static/jquery.js rename to docs/readthedocsFiles/build/html/_static/jquery.js diff --git a/docs/readthedocsFiles/_build/html/_static/minus.png b/docs/readthedocsFiles/build/html/_static/minus.png similarity index 100% rename from docs/readthedocsFiles/_build/html/_static/minus.png rename to docs/readthedocsFiles/build/html/_static/minus.png diff --git a/docs/readthedocsFiles/_build/html/_static/plus.png b/docs/readthedocsFiles/build/html/_static/plus.png similarity index 100% rename from docs/readthedocsFiles/_build/html/_static/plus.png rename to docs/readthedocsFiles/build/html/_static/plus.png diff --git a/docs/readthedocsFiles/_build/html/_static/pygments.css b/docs/readthedocsFiles/build/html/_static/pygments.css similarity index 100% rename from docs/readthedocsFiles/_build/html/_static/pygments.css rename to docs/readthedocsFiles/build/html/_static/pygments.css diff --git a/docs/readthedocsFiles/_build/html/_static/searchtools.js b/docs/readthedocsFiles/build/html/_static/searchtools.js similarity index 100% rename from docs/readthedocsFiles/_build/html/_static/searchtools.js rename to docs/readthedocsFiles/build/html/_static/searchtools.js diff --git a/docs/readthedocsFiles/_build/html/_static/underscore-1.3.1.js b/docs/readthedocsFiles/build/html/_static/underscore-1.3.1.js similarity index 100% rename from docs/readthedocsFiles/_build/html/_static/underscore-1.3.1.js rename to docs/readthedocsFiles/build/html/_static/underscore-1.3.1.js diff --git a/docs/readthedocsFiles/_build/html/_static/underscore.js b/docs/readthedocsFiles/build/html/_static/underscore.js similarity index 100% rename from docs/readthedocsFiles/_build/html/_static/underscore.js rename to docs/readthedocsFiles/build/html/_static/underscore.js diff --git a/docs/readthedocsFiles/_build/html/_static/up-pressed.png b/docs/readthedocsFiles/build/html/_static/up-pressed.png similarity index 100% rename from docs/readthedocsFiles/_build/html/_static/up-pressed.png rename to docs/readthedocsFiles/build/html/_static/up-pressed.png diff --git a/docs/readthedocsFiles/_build/html/_static/up.png b/docs/readthedocsFiles/build/html/_static/up.png similarity index 100% rename from docs/readthedocsFiles/_build/html/_static/up.png rename to docs/readthedocsFiles/build/html/_static/up.png diff --git a/docs/readthedocsFiles/_build/html/_static/websupport.js b/docs/readthedocsFiles/build/html/_static/websupport.js similarity index 100% rename from docs/readthedocsFiles/_build/html/_static/websupport.js rename to docs/readthedocsFiles/build/html/_static/websupport.js diff --git a/docs/readthedocsFiles/_build/html/genindex.html b/docs/readthedocsFiles/build/html/genindex.html similarity index 93% rename from docs/readthedocsFiles/_build/html/genindex.html rename to docs/readthedocsFiles/build/html/genindex.html index d1d3289a..b47a0f71 100644 --- a/docs/readthedocsFiles/_build/html/genindex.html +++ b/docs/readthedocsFiles/build/html/genindex.html @@ -7,7 +7,7 @@ <head> <meta http-equiv="X-UA-Compatible" content="IE=Edge" /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> - <title>Index — Rigbox 2.1 documentation</title> + <title>Index — Rigbox 2.2 documentation</title> <link rel="stylesheet" href="_static/alabaster.css" type="text/css" /> <link rel="stylesheet" href="_static/pygments.css" type="text/css" /> <script type="text/javascript" id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script> @@ -46,7 +46,7 @@ <h1 id="index">Index</h1> </div> <div class="sphinxsidebar" role="navigation" aria-label="main navigation"> <div class="sphinxsidebarwrapper"> -<h1 class="logo"><a href="root.html">Rigbox</a></h1> +<h1 class="logo"><a href="index.html">Rigbox</a></h1> @@ -60,7 +60,7 @@ <h3>Navigation</h3> <div class="relations"> <h3>Related Topics</h3> <ul> - <li><a href="root.html">Documentation overview</a><ul> + <li><a href="index.html">Documentation overview</a><ul> </ul></li> </ul> </div> diff --git a/docs/readthedocsFiles/_build/html/root.html b/docs/readthedocsFiles/build/html/index.html similarity index 98% rename from docs/readthedocsFiles/_build/html/root.html rename to docs/readthedocsFiles/build/html/index.html index 0bf8e2bd..44dd99b2 100644 --- a/docs/readthedocsFiles/_build/html/root.html +++ b/docs/readthedocsFiles/build/html/index.html @@ -6,7 +6,7 @@ <head> <meta http-equiv="X-UA-Compatible" content="IE=Edge" /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> - <title>Welcome to Rigbox’s documentation! — Rigbox 2.1 documentation</title> + <title>Welcome to Rigbox’s documentation! — Rigbox 2.2 documentation</title> <link rel="stylesheet" href="_static/alabaster.css" type="text/css" /> <link rel="stylesheet" href="_static/pygments.css" type="text/css" /> <script type="text/javascript" id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script> @@ -94,7 +94,7 @@ <h3>Quick search</h3> & <a href="https://github.com/bitprophet/alabaster">Alabaster 0.7.11</a> | - <a href="_sources/root.rst.txt" + <a href="_sources/index.rst.txt" rel="nofollow">Page source</a> </div> diff --git a/docs/readthedocsFiles/build/html/objects.inv b/docs/readthedocsFiles/build/html/objects.inv new file mode 100644 index 0000000000000000000000000000000000000000..f1eb9e10e3d7e31d49d2fee3c8916da49b22662d GIT binary patch literal 242 zcmY#Z2rkIT%&Sny%qvUHE6FdaR47X=D$dN$Q!wIERtPA{&q_@$u~G=iOi#+M0E&b` zWUUl{?2wF9g`(8l#LT>u)FOraG=-9k%wmPK%$!sOAf23_TTql*T%4MsP+FXsm#$Ei zlbNK)RdLJP|Lo~A-kxg%H1s?-p7QkZIvaSwG{mF5>s9KMC(kr0nr6gsq-y>=so?6N zbtt%a(&yq}QLj&;O-7d|f6|sSJ);xGuuA)^?^$h6os(zI2R}J=*8gBqsN+&SAEu;f n`E_2Iep**9J-Tva(G#8!Spmb2M+qG@4oOv7;tUaQJYE6-kt<@P literal 0 HcmV?d00001 diff --git a/docs/readthedocsFiles/_build/html/search.html b/docs/readthedocsFiles/build/html/search.html similarity index 94% rename from docs/readthedocsFiles/_build/html/search.html rename to docs/readthedocsFiles/build/html/search.html index 7c832514..95118f0c 100644 --- a/docs/readthedocsFiles/_build/html/search.html +++ b/docs/readthedocsFiles/build/html/search.html @@ -6,7 +6,7 @@ <head> <meta http-equiv="X-UA-Compatible" content="IE=Edge" /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> - <title>Search — Rigbox 2.1 documentation</title> + <title>Search — Rigbox 2.2 documentation</title> <link rel="stylesheet" href="_static/alabaster.css" type="text/css" /> <link rel="stylesheet" href="_static/pygments.css" type="text/css" /> <script type="text/javascript" id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script> @@ -69,7 +69,7 @@ <h1 id="search-documentation">Search</h1> </div> <div class="sphinxsidebar" role="navigation" aria-label="main navigation"> <div class="sphinxsidebarwrapper"> -<h1 class="logo"><a href="root.html">Rigbox</a></h1> +<h1 class="logo"><a href="index.html">Rigbox</a></h1> @@ -83,7 +83,7 @@ <h3>Navigation</h3> <div class="relations"> <h3>Related Topics</h3> <ul> - <li><a href="root.html">Documentation overview</a><ul> + <li><a href="index.html">Documentation overview</a><ul> </ul></li> </ul> </div> diff --git a/docs/readthedocsFiles/build/html/searchindex.js b/docs/readthedocsFiles/build/html/searchindex.js new file mode 100644 index 00000000..9437111d --- /dev/null +++ b/docs/readthedocsFiles/build/html/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({docnames:["index"],envversion:55,filenames:["index.rst"],objects:{},objnames:{},objtypes:{},terms:{index:0,modul:0,page:0,search:0},titles:["Welcome to Rigbox\u2019s documentation!"],titleterms:{document:0,indic:0,rigbox:0,tabl:0,welcom:0}}) \ No newline at end of file diff --git a/docs/readthedocsFiles/make.bat b/docs/readthedocsFiles/make.bat index 9ccf5f91..230c1bef 100644 --- a/docs/readthedocsFiles/make.bat +++ b/docs/readthedocsFiles/make.bat @@ -7,8 +7,8 @@ REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) -set SOURCEDIR=. -set BUILDDIR=_build +set SOURCEDIR=source +set BUILDDIR=build set SPHINXPROJ=Rigbox if "%1" == "" goto help diff --git a/docs/readthedocsFiles/conf.py b/docs/readthedocsFiles/source/conf.py similarity index 98% rename from docs/readthedocsFiles/conf.py rename to docs/readthedocsFiles/source/conf.py index 4d304561..0ba36f9c 100644 --- a/docs/readthedocsFiles/conf.py +++ b/docs/readthedocsFiles/source/conf.py @@ -26,7 +26,7 @@ # The short X.Y version version = '' # The full version, including alpha/beta/rc tags -release = '2.1' +release = '2.2' # -- General configuration --------------------------------------------------- @@ -46,6 +46,7 @@ 'sphinx.ext.coverage', 'sphinx.ext.imgmath', 'sphinx.ext.ifconfig', + 'sphinx.ext.viewcode', 'sphinx.ext.githubpages', ] @@ -59,7 +60,7 @@ source_suffix = '.rst' # The master toctree document. -master_doc = 'root' +master_doc = 'index' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -71,7 +72,7 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path . -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +exclude_patterns = [] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' diff --git a/docs/readthedocsFiles/_build/html/_sources/root.rst.txt b/docs/readthedocsFiles/source/index.rst similarity index 88% rename from docs/readthedocsFiles/_build/html/_sources/root.rst.txt rename to docs/readthedocsFiles/source/index.rst index 22c68b15..40462dbc 100644 --- a/docs/readthedocsFiles/_build/html/_sources/root.rst.txt +++ b/docs/readthedocsFiles/source/index.rst @@ -1,5 +1,5 @@ .. Rigbox documentation master file, created by - sphinx-quickstart on Fri May 24 13:07:11 2019. + sphinx-quickstart on Wed May 29 17:04:21 2019. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. From 63bfdd05a9e6ee73d40172395adac074b8639e6e Mon Sep 17 00:00:00 2001 From: jaib1 <j.bhagat@ucl.ac.uk> Date: Thu, 30 May 2019 10:59:44 +0100 Subject: [PATCH 383/393] hotfix for activating AlyxPanel in MC --- +eui/MControl.m | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/+eui/MControl.m b/+eui/MControl.m index 4f4ff1df..bb76593f 100644 --- a/+eui/MControl.m +++ b/+eui/MControl.m @@ -763,8 +763,12 @@ function buildUI(obj, parent) % Parent here is the MC window (figure) leftSideBox.Heights = [55 22]; % Create the Alyx panel + + % check to see if there is a remote database url defined in + % `dat.paths` url = char(getOr(dat.paths, 'databaseURL', '')); - obj.AlyxPanel = eui.AlyxPanel(headerBox, isempty(url)); + % if so, activate AlyxPanel, if not, disable AlyxPanel + obj.AlyxPanel = eui.AlyxPanel(headerBox, ~isempty(url)); addlistener(obj.NewExpSubject, 'SelectionChanged', @(src, evt)obj.AlyxPanel.dispWaterReq(src, evt)); addlistener(obj.LogSubject, 'SelectionChanged', @(src, evt)obj.AlyxPanel.dispWaterReq(src, evt)); From cfe2e40f3c682412c94f5157b136aa4bc59b5235 Mon Sep 17 00:00:00 2001 From: jaib1 <j.bhagat@ucl.ac.uk> Date: Thu, 30 May 2019 15:05:06 +0100 Subject: [PATCH 384/393] now requires 2017b or later --- addRigboxPaths.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addRigboxPaths.m b/addRigboxPaths.m index b705367e..9fb6a928 100644 --- a/addRigboxPaths.m +++ b/addRigboxPaths.m @@ -23,8 +23,8 @@ function addRigboxPaths(savePaths) 'Click <a href="matlab:web(''%s'',''-browser'')">here</a> to install.'],... 'https://www.microsoft.com/en-us/download/details.aspx?id=48145') -% Check MATLAB 2016b is running -assert(~verLessThan('matlab', '8.4'), 'Requires MATLAB 2016b or later') +% Check MATLAB 2017b is running +assert(~verLessThan('matlab', '9.3'), 'Requires MATLAB 2017b or later') % Check essential toolboxes are installed (common to both master and % stimulus computers) From f43146f9f61a41cddd05118b78f8bd1045ba30b1 Mon Sep 17 00:00:00 2001 From: jaib1 <j.bhagat@ucl.ac.uk> Date: Mon, 3 Jun 2019 13:31:10 +0100 Subject: [PATCH 385/393] hotfix to SignalsExp for ending experiments --- +exp/SignalsExp.m | 1 - 1 file changed, 1 deletion(-) diff --git a/+exp/SignalsExp.m b/+exp/SignalsExp.m index 347be45d..d6883ff7 100644 --- a/+exp/SignalsExp.m +++ b/+exp/SignalsExp.m @@ -193,7 +193,6 @@ obj.Events.expStart.map(true).into(advanceTrial) %expStart signals advance obj.Events.endTrial.into(advanceTrial) %endTrial signals advance advanceTrial.map(true).keepWhen(hasNext).into(obj.Events.newTrial) %newTrial if more - lastTrialOver.into(obj.Events.expStop) %newTrial if more obj.Events.expStop.onValue(@(~)quit(obj))]; % initialise the parameter signals globalPars.post(rmfield(globalStruct, 'defFunction')); From 92d5ec6be33c54835d817eb495a565bc809fb864 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 19 Jun 2019 14:45:01 +0300 Subject: [PATCH 386/393] Added script for testing entire repo --- tests/runall.m | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 tests/runall.m diff --git a/tests/runall.m b/tests/runall.m new file mode 100644 index 00000000..d95cc4d3 --- /dev/null +++ b/tests/runall.m @@ -0,0 +1,26 @@ +%% Script for running all Rigbox tests +% To be called for code checks and the like +% TODO May become a function +% TODO May add flags for levels of testing +% TODO Method setup in dat_test may become global fixture +% TODO Delete sinusoidLayer_test from this folder +% TODO Deal with directory changes +main_tests = testsuite; + +%% Gather signals tests +root = getOr(dat.paths,'rigbox'); +signals_tests = testsuite(fullfile(root, 'signals', 'tests')); + +%% Gather alyx-matlab tests +alyx_tests = testsuite(fullfile(root, 'alyx-matlab', 'tests')); + +%% Filter & run +% the suite is automatically sorted based on shared fixtures. However, if +% you add, remove, or reorder elements after initial suite creation, call +% the sortByFixtures method to sort the suite. +all_tests = [main_tests signals_tests alyx_tests]; +results = run(all_tests); + +%% Diagnostics +failed = {all_tests([results.Failed]).Name}'; +% Load benchmarks and compare for performance tests? \ No newline at end of file From c2d3ab522256a8ef86ee8faec3df8856818ab6b8 Mon Sep 17 00:00:00 2001 From: k1o0 <k1o0@3tk.co> Date: Wed, 26 Jun 2019 15:21:40 +0300 Subject: [PATCH 387/393] setValuesTest (#162) squash and rebase onto dev * Started setVals test for param editor * Finished ParamEditor tests; changed Timeline flags to switches --- +hw/TLOutput.m | 13 +++-- +hw/Timeline.m | 85 +++++++++++++++++++++++--------- tests/ParamEditor_test.m | 47 +++++++++++++++--- tests/fixtures/util/MockDialog.m | 17 +++---- 4 files changed, 119 insertions(+), 43 deletions(-) diff --git a/+hw/TLOutput.m b/+hw/TLOutput.m index 0d7dff65..0109fdfc 100644 --- a/+hw/TLOutput.m +++ b/+hw/TLOutput.m @@ -28,13 +28,18 @@ % 2018-01 NS created properties - Name % The name of the timeline output, for easy identification - Enable = true % Will not do anything with it unless this is true - Verbose = false % Flag to output status updates. Initialization message outputs regardless of verbose. + % The name of the timeline output, for easy identification + Name + % Will not do anything with it unless this is true + Enable matlab.lang.OnOffSwitchState = 'on' + % Flag to output status updates. Initialization message outputs + % regardless of verbose. + Verbose matlab.lang.OnOffSwitchState = 'off' end properties (Transient, Hidden, Access = protected) - Session % Holds an NI DAQ session object + % Holds an NI DAQ session object + Session end methods (Abstract) diff --git a/+hw/Timeline.m b/+hw/Timeline.m index c97756c2..6639d52a 100644 --- a/+hw/Timeline.m +++ b/+hw/Timeline.m @@ -67,44 +67,81 @@ % 2017-10 MW updated properties - DaqVendor = 'ni' % 'ni' is using National Instruments USB-6211 DAQ - DaqIds = 'Dev1' % Device ID can be found with daq.getDevices() - DaqSampleRate = 1000 % rate at which daq aquires data in Hz, see Rate - DaqSamplesPerNotify % determines the number of data samples to be processed each time, see Timeline.process(), constructor and NotifyWhenDataAvailableExceeds - Outputs = hw.TLOutputChrono % array of output classes, defining any signals you desire to be sent from the daq. See Also HW.TLOUTPUT, HW.TLOUTPUTCLOCK + % 'ni' is using National Instruments USB-6211 DAQ + DaqVendor = 'ni' + % Device ID can be found with daq.getDevices() + DaqIds = 'Dev1' + % rate at which daq aquires data in Hz, see Rate + DaqSampleRate = 1000 + % determines the number of data samples to be processed each time, + % see Timeline.process(), constructor and + % NotifyWhenDataAvailableExceeds + DaqSamplesPerNotify + % array of output classes, defining any signals you desire to be + % sent from the daq. See Also HW.TLOUTPUT, HW.TLOUTPUTCLOCK + Outputs = hw.TLOutputChrono + % All configured inputs. Inputs = struct('name', 'chrono',... - 'arrayColumn', -1,... % -1 is default indicating unused, this is update when the channels are added during tl.start() + 'arrayColumn', -1,... -1 is default indicating unused, this is update when the channels are added during tl.start() 'daqChannelID', 'ai0',... 'measurement', 'Voltage',... 'terminalConfig', 'SingleEnded',... 'axesScale', 1) % multiplicative vertical scaling for when live plotting the input - UseInputs = {'chrono'} % array of inputs to record while tl is running - StopDelay = 2 % currently pauses for at least 2 secs as 'hack' before stopping main DAQ session to allow - MaxExpectedDuration = 2*60*60 % expected experiment time so data structure is initialised to sensible size (in secs) - AquiredDataType = 'double' % default data type for the acquired data array (i.e. Data.rawDAQData) - UseTimeline = false % used by expServer. If true, timeline is started by default (otherwise can be toggled with the t key) - LivePlot = false % if true the data are plotted as the data are aquired - FigureScale = [0 0 1 1]; % figure position in normalized units, default is full screen - WriteBufferToDisk = false % if true the data buffer is written to disk as they're aquired NB: in the future this will happen by default + % array of inputs to record while tl is running + UseInputs = {'chrono'} + % currently pauses for at least 2 secs as 'hack' before stopping + % main DAQ session to allow + StopDelay = 2 + % expected experiment time so data structure is initialised to + % sensible size (in secs) + MaxExpectedDuration = 2*60*60 + % default data type for the acquired data array (i.e. + % Data.rawDAQData) + AquiredDataType = 'double' + % If true, timeline is started by default (otherwise can be toggled + % with the t key in expServer) + UseTimeline matlab.lang.OnOffSwitchState = 'off' + % if true the data are plotted as the data are aquired + LivePlot matlab.lang.OnOffSwitchState = 'off' + % figure position in normalized units, default is full screen + FigureScale = [0 0 1 1] + % if true the data buffer is written to disk as they're aquired NB: + % in the future this will happen by default + WriteBufferToDisk matlab.lang.OnOffSwitchState = 'off' end properties (Dependent) - SamplingInterval % defined as 1/DaqSampleRate - IsRunning = false % flag is set to true when the first chrono pulse is aquired and set to false when tl is stopped (and everything saved), see tl.process and tl.stop + % Sampling interval defined as 1/DaqSampleRate + SamplingInterval + % Switch set to true when the first chrono pulse is aquired and + % set to false when tl is stopped (and everything saved), see + % tl.process and tl.stop + IsRunning matlab.lang.OnOffSwitchState = 'off' end properties (Transient, Access = protected) - Listener % holds the listener for 'DataAvailable', see DataAvailable and Timeline.process() - LastTimestamp % the last timestamp returned from the daq during the DataAvailable event. Used to check sampling continuity, see tl.process() - Ref % the expRef string. See tl.start() - AlyxInstance % a struct contraining the Alyx token, user and url for ile registration. See tl.start() - Data % A structure containing timeline data - Axes % A figure handle for plotting the aquired data as it's processed - DataFID % The data file ID for writing aquired data directly to disk + % holds the listener for 'DataAvailable', see DataAvailable and + % Timeline.process() + Listener + % the last timestamp returned from the daq during the DataAvailable + % event. Used to check sampling continuity, see tl.process() + LastTimestamp + % the expRef string. See tl.start() + Ref + % a struct contraining the Alyx token, user and url for ile + % registration. See tl.start() + AlyxInstance + % A structure containing timeline data + Data + % A figure handle for plotting the aquired data as it's processed + Axes + % The data file ID for writing aquired data directly to disk + DataFID end properties (Transient, SetAccess = protected, GetAccess = {?hw.Timeline, ?hw.TLOutput}) - Sessions = containers.Map % map of daq sessions and their channels, created at tl.start() + % Map of daq sessions and their channels, created at tl.start() + Sessions = containers.Map end methods diff --git a/tests/ParamEditor_test.m b/tests/ParamEditor_test.m index 25f94598..9856780d 100644 --- a/tests/ParamEditor_test.m +++ b/tests/ParamEditor_test.m @@ -1,5 +1,6 @@ -classdef (SharedTestFixtures={matlab.unittest.fixtures.PathFixture(... -[fileparts(mfilename('fullpath')) '\fixtures'])})... % add 'fixtures' folder as test fixture +classdef (SharedTestFixtures={ % add 'fixtures' folder as test fixture + matlab.unittest.fixtures.PathFixture('fixtures'),... + matlab.unittest.fixtures.PathFixture(['fixtures' filesep 'util'])})... ParamEditor_test < matlab.unittest.TestCase properties @@ -259,11 +260,45 @@ function test_deleteCondition(testCase) [PE.Parameters.numTrialConditions, numel(PE.Parameters.TrialSpecificNames)]) end - function test_setValues(testCase) - % TODO Add test for the set values button. For now let's fail this + function test_setSelectedValues(testCase) + % (1:10:100) % Sets selected rows to [1 11 21 31 41 51 61 71 81 91] + % @(~)randi(100) % Assigned random integer to each selected row + % @(a)a*50 % Multiplies each condition value by 50 + % false % Sets all selected rows to false testCase.assertTrue(~testCase.Changed, 'Changed flag incorrect') -% PE = testCase.ParamEditor; - testCase.assertTrue(false, 'Test not implemented') + mock = MockDialog.instance('char'); + mock.InTest = true; + mock.UseDefaults = false; + mock.Dialogs('Set values') = fun.CellSeq.create({'(1:10:100)', '@(~)randi(100)', '@(a)a*50'}); + + % Select some cells to set + event.Indices = [(1:5)' ones(5,1)*4]; + selection_fn = testCase.Table.CellSelectionCallback; + selection_fn([],event) + + % Retrieve function handle for set condition + callback_fn = pick(findobj(testCase.Figure,... + 'String', 'Set values'), 'Callback'); + callback_fn() + + % Verify Changed event triggered + testCase.verifyTrue(testCase.Changed, ... + 'Failed to notify listeners of parameter change') + + P = testCase.ParamEditor.Parameters; + % Verify values changed + expected = (1:10:100); + testCase.verifyEqual(P.Struct.numRepeats(1:5), expected(1:5), ... + 'Failed to modify parameters') + + callback_fn() + values = P.Struct.numRepeats(1:5); + testCase.verifyTrue(all(values < 100), ... + 'Failed to modify parameters') + + callback_fn() + testCase.verifyEqual(P.Struct.numRepeats(1:5)/50, values, ... + 'Failed to modify parameters') end function test_sortByColumn(testCase) diff --git a/tests/fixtures/util/MockDialog.m b/tests/fixtures/util/MockDialog.m index 52d7773e..6b1e57ae 100644 --- a/tests/fixtures/util/MockDialog.m +++ b/tests/fixtures/util/MockDialog.m @@ -117,7 +117,7 @@ function reset(obj) end case 'inputdlg' % Find key - if ~strcmp(obj.Dialogs.KeyType, 'char') + if ~strcmp(obj.Dialogs.KeyType, 'char') && ~obj.UseDefaults key = obj.fromCount; elseif isempty(varargin) key = 'Input'; @@ -141,6 +141,10 @@ function reset(obj) if isa(answer, 'fun.CellSeq') answer = answer.first; obj.Dialogs(key) = obj.Dialogs(key).rest; + elseif isa(answer, 'fun.EmptySeq') + warning('MockDialog:NewCall:EmptySeq', ... + 'End of input sequence, using default input instead') + answer = def; end % inputdlg always returns a cell @@ -152,14 +156,9 @@ function reset(obj) methods (Access = private) - function key = fromCount() - if strcmp(obj.Dialogs.KeyType, 'char') - key = []; - return - elseif ~obj.UseDefaults - assert(obj.Dialogs.Count > 0, 'MockDialog:newCall:NoValuesSet', ... - 'No values saved in Dialogs property') - end + function key = fromCount(obj) + assert(obj.Dialogs.Count > 0, 'MockDialog:newCall:NoValuesSet', ... + 'No values saved in Dialogs property') key = obj.NumCalls; if key > obj.Dialogs.Count key = key - obj.Dialogs.Count*floor(key/uint32(obj.Dialogs.Count)); From f27cc6f226ed9046fbd4c91373a3fca907a438a1 Mon Sep 17 00:00:00 2001 From: Jai Bhagat <j.bhagat@ucl.ac.uk> Date: Wed, 26 Jun 2019 14:55:08 +0100 Subject: [PATCH 388/393] add Nick's changes from #164 --- +hw/+ptb/Window.m | 3 ++- +srv/expServer.m | 12 +++++++----- cortexlab/+git/changes.m | 6 ------ 3 files changed, 9 insertions(+), 12 deletions(-) delete mode 100644 cortexlab/+git/changes.m diff --git a/+hw/+ptb/Window.m b/+hw/+ptb/Window.m index 8cab6568..590ce030 100644 --- a/+hw/+ptb/Window.m +++ b/+hw/+ptb/Window.m @@ -479,6 +479,7 @@ function applyCalibration(obj, cal) tt = (1:ns)/acqRate; figure; plot(tt,clock); + ylabel('clock signal'); title('Clock'); upCrossings = find(diff( clock > 1 ) == 1); dnCrossings = find(diff( clock > 1 ) == -1); @@ -490,7 +491,7 @@ function applyCalibration(obj, cal) end plot( tt, light ); hold on xlabel('Time (s)'); - ylabel('Signal (Volts)'); + ylabel('Photodiode Signal (Volts)'); set(gca,'ylim',[0 1.1*max(light)]); title(sprintf('In this plot. digital has been delayed by %2.2f ms', delay)); diff --git a/+srv/expServer.m b/+srv/expServer.m index 954c6aee..1d76cb9d 100644 --- a/+srv/expServer.m +++ b/+srv/expServer.m @@ -359,11 +359,13 @@ function whiteScreen() function calibrateGamma() stimWindow = rig.stimWindow; DaqDev = rig.daqController.DaqIds; - lightIn = 'ai0'; % defaults from hw.psy.Window - clockIn = 'ai1'; - clockOut = 'port1/line0 (PFI4)'; - log(['Please connect photodiode to %s, clockIn to %s and clockOut to %s.\r'... - 'Press any key to contiue\n'],lightIn,clockIn,clockOut); + lightIn = 'ai1'; % defaults from hw.psy.Window + clockIn = 'ai0'; + clockOut = 'port1/line0'; + clockOutHint = 'PFI4'; + log(['Please connect photodiode to %s, clockIn to %s and clockOut to '... + '%s (%s).\r Press any key to contiue\n'],... + lightIn,clockIn,clockOut, clockOutHint); pause; % wait for keypress stimWindow.Calibration = stimWindow.calibration(DaqDev); % calibration pause(1); diff --git a/cortexlab/+git/changes.m b/cortexlab/+git/changes.m deleted file mode 100644 index d9e9f013..00000000 --- a/cortexlab/+git/changes.m +++ /dev/null @@ -1,6 +0,0 @@ -disp('Updating queued Alyx posts...') -posts = dirPlus(getOr(dat.paths, 'localAlyxQueue', 'C:/localAlyxQueue')); -posts = posts(endsWith(posts, 'put')); -newPosts = cellfun(@(str)[str(1:end-3) 'patch'], posts, 'uni', 0); -status = cellfun(@movefile, posts, newPosts); -assert(all(status), 'Unable to rename queued Alyx files, please do this manually') \ No newline at end of file From 2a473d2643b8a574b849a858062b68f914b759df Mon Sep 17 00:00:00 2001 From: Jai Bhagat <j.bhagat@ucl.ac.uk> Date: Mon, 1 Jul 2019 14:23:26 +0100 Subject: [PATCH 389/393] updated doc for 'Window' --- +hw/+ptb/Window.m | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/+hw/+ptb/Window.m b/+hw/+ptb/Window.m index 590ce030..1108aeb7 100644 --- a/+hw/+ptb/Window.m +++ b/+hw/+ptb/Window.m @@ -385,6 +385,28 @@ function fillRect(obj, colour, rect) end function [nx, ny] = drawText(obj, text, x, y, colour, vSpacing, wrapAt) + % Calls PTB's `DrawFormattedText` to display text to the PTB Window + % + % Inputs: + % `text`: the text to be displaye + % `x`: a number, in pixels, of the x-position of the left border of + % the text + % `y`: a number, in pixels, of the y-position of the top border of + % the text, in pixels + % `colour`: an [r g b] or [r g b a] numeric vector for the color of + % the text + % `vSpacing`: a number for the spacing between lines of text + % `wrapAt`: a number for the max number of characters in a line of text + % + % Outputs: + % `nx`: + % `ny`: + % + % Examples: + % + % + % + % See also: DrawFormattedText if nargin < 7 wrapAt = []; end From 4abd3946a15a13f1602d04df151a12f8df7cc910 Mon Sep 17 00:00:00 2001 From: Jai Bhagat <j.bhagat@ucl.ac.uk> Date: Fri, 5 Jul 2019 16:48:30 +0100 Subject: [PATCH 390/393] changes to daq channels for gamma cal --- +srv/expServer.m | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/+srv/expServer.m b/+srv/expServer.m index 1d76cb9d..23470693 100644 --- a/+srv/expServer.m +++ b/+srv/expServer.m @@ -14,17 +14,18 @@ function expServer(useTimelineOverride, bgColour) % harware file is used. % % Key bindings: -% t - Toggle Timeline on and off. The default state is defined in the +% 't' - Toggle Timeline on and off. The default state is defined in the % hardware file but may be overridden as the first input argument. -% w - Toggle reward on and off. This switches the output of the first +% 'w' - Toggle reward on and off. This switches the output of the first % DAQ output channel between its 'high' and 'low' state. The % specific DAQ channel and its default state are set in the hardware % file. -% space - Deliver default reward, specified by the DefaultCommand +% 'space' - Deliver default reward, specified by the DefaultCommand % property in the hardware file. -% m - Perform water calibration. -% b - Toggle the background colour between the default and white. -% g - Perform gamma correction +% 'm' - Perform water calibration. +% 'g' - Perform gamma correction +% 'b' - Toggle the background colour between the default and white. +% 'q' - Quit expServer. % % % See also MC, io.WSJCommunicator, hw.devices, srv.prepareExp, hw.Timeline @@ -359,8 +360,8 @@ function whiteScreen() function calibrateGamma() stimWindow = rig.stimWindow; DaqDev = rig.daqController.DaqIds; - lightIn = 'ai1'; % defaults from hw.psy.Window - clockIn = 'ai0'; + lightIn = 'ai0'; % defaults from hw.ptb.Window + clockIn = 'ai1'; clockOut = 'port1/line0'; clockOutHint = 'PFI4'; log(['Please connect photodiode to %s, clockIn to %s and clockOut to '... @@ -371,7 +372,7 @@ function calibrateGamma() pause(1); saveGamma(stimWindow.Calibration); stimWindow.applyCalibration(stimWindow.Calibration); - clear('lightIn','clockIn','clockOut','cal'); + clear('lightIn','clockIn','clockOut','clockOutHint'); log('Gamma calibration complete'); end From 090af94ec05d9fa43e2038ad24b59e37a3b7947f Mon Sep 17 00:00:00 2001 From: Jai Bhagat <j.bhagat@ucl.ac.uk> Date: Fri, 5 Jul 2019 17:13:19 +0100 Subject: [PATCH 391/393] quit exp server after gamma cal --- +srv/expServer.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/+srv/expServer.m b/+srv/expServer.m index 23470693..a0452642 100644 --- a/+srv/expServer.m +++ b/+srv/expServer.m @@ -373,7 +373,8 @@ function calibrateGamma() saveGamma(stimWindow.Calibration); stimWindow.applyCalibration(stimWindow.Calibration); clear('lightIn','clockIn','clockOut','clockOutHint'); - log('Gamma calibration complete'); + log('Gamma calibration complete. Quitting expServer'); + running = false; end function saveGamma(cal) From 94183e831fbdc5cf1e331b15a57ee2b7115d6d83 Mon Sep 17 00:00:00 2001 From: Jai Bhagat <j.bhagat@ucl.ac.uk> Date: Mon, 8 Jul 2019 18:38:13 +0100 Subject: [PATCH 392/393] #4, #128, #164 --- +hw/+ptb/Window.m | 7 ++++--- +srv/expServer.m | 10 +++++++++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/+hw/+ptb/Window.m b/+hw/+ptb/Window.m index 1108aeb7..764cb5ee 100644 --- a/+hw/+ptb/Window.m +++ b/+hw/+ptb/Window.m @@ -17,7 +17,7 @@ properties ForegroundColour ScreenNum; %Psychtoolbox screen number - PxDepth = 32 %the pixel colour depth (bits) + PxDepth %the pixel colour depth (bits) OpenBounds %default screen region to open window onscreen - empty for full SyncBounds %position bounding rectangle of sync region %sync region [r g b], or luminance for each consecutive flip (row-wise). @@ -25,6 +25,7 @@ SyncColourCycle = [0; 255] MonitorId %an identifier for the monitor Calibration %Struct containing calibration data + PtbHandle = -1 %a handle to the PTB screen window PtbVerbosity = 2 PtbSyncTests end @@ -41,7 +42,6 @@ end properties (SetAccess = protected, Transient) - PtbHandle = -1 %a handle to the PTB screen window RefreshInterval %refresh interval for updating the device NextSyncIdx %index into SyncColourCycle for next sync colour Invalid = false @@ -197,9 +197,10 @@ function open(obj) end Screen('Preference', 'SuppressAllWarnings', true); % setup screen window + %obj.PxDepth = 32; obj.PtbHandle = Screen('OpenWindow', obj.ScreenNum, obj.BackgroundColour,... obj.OpenBounds, obj.PxDepth); - obj.PxDepth = Screen('PixelSize', obj.PtbHandle); + %obj.PxDepth = Screen('PixelSize', obj.PtbHandle); obj.Bounds = Screen('Rect', obj.PtbHandle); %first flip will be first sync colour in cycle diff --git a/+srv/expServer.m b/+srv/expServer.m index a0452642..2db97616 100644 --- a/+srv/expServer.m +++ b/+srv/expServer.m @@ -359,6 +359,11 @@ function whiteScreen() function calibrateGamma() stimWindow = rig.stimWindow; + % move PTB window to background and bring MATLAB command window to front + Screen('Preference', 'WindowShieldingLevel', 0); + stimWindow.close(); + stimWindow.open(); + commandwindow; DaqDev = rig.daqController.DaqIds; lightIn = 'ai0'; % defaults from hw.ptb.Window clockIn = 'ai1'; @@ -374,7 +379,10 @@ function calibrateGamma() stimWindow.applyCalibration(stimWindow.Calibration); clear('lightIn','clockIn','clockOut','clockOutHint'); log('Gamma calibration complete. Quitting expServer'); - running = false; + % move PTB window to foreground + Screen(stimWindow, 'WindowShieldingLevel', 2000); + stimWindow.close(); + stimWindow.open(); end function saveGamma(cal) From b707bcf3f7660b05fb394feb271b71512cd9eb0d Mon Sep 17 00:00:00 2001 From: jaib1 <j.bhagat@ucl.ac.uk> Date: Fri, 9 Aug 2019 16:59:38 +0100 Subject: [PATCH 393/393] WIP on updating 'hw.Window' and 'hw.ptb.Window' --- +hw/+ptb/Window.m | 35 +++++++--- +hw/Window.m | 166 +++++++++++++++++++++++++++++++--------------- 2 files changed, 135 insertions(+), 66 deletions(-) diff --git a/+hw/+ptb/Window.m b/+hw/+ptb/Window.m index 764cb5ee..281a28a1 100644 --- a/+hw/+ptb/Window.m +++ b/+hw/+ptb/Window.m @@ -1,13 +1,19 @@ classdef Window < hw.Window - %HW.PTB.WINDOW A Psychtoolbox Screen implementation of Window - % Detailed explanation goes here - % - % Part of Rigbox - - % 2012-10 CB created +%HW.PTB.WINDOW A Psychtoolbox `Screen` implementation of `hw.Window` +% +% A `hw.ptb.Window` object subclasses `hw.Window` to create a window that +% interfaces nicely with Psychtoolbox (PTB) functions. +% +% See also: `hw.Window`, `Screen` +% +% Part of Rigbox +% +% 2012-10 CB created properties (Dependent) + % A numeric array [R G B] of the background colour of the window BackgroundColour + DaqVendor DaqDev DaqSyncEchoPort @@ -15,15 +21,22 @@ end properties + % A numeric array [R G B] of the default colour to use for drawing ForegroundColour - ScreenNum; %Psychtoolbox screen number - PxDepth %the pixel colour depth (bits) - OpenBounds %default screen region to open window onscreen - empty for full - SyncBounds %position bounding rectangle of sync region + % Screen number as set by PTB's `Screen` + ScreenNum + % A numeric value of the number of bits for the pixel colour depth + PxDepth + % A numeric array [left top right bottom] in pixels for the default + % bounding rectangle of the window on-screen (empty for full-screen) + OpenBounds + % A numeric array [left top right bottom] in pixels for the default + % bounding rectangle of the sync region of the window + SyncBounds %sync region [r g b], or luminance for each consecutive flip (row-wise). %Wil repeat in a cycle. Default is white->black->.... SyncColourCycle = [0; 255] - MonitorId %an identifier for the monitor + MonitorId % an identifier for the monitor Calibration %Struct containing calibration data PtbHandle = -1 %a handle to the PTB screen window PtbVerbosity = 2 diff --git a/+hw/Window.m b/+hw/Window.m index 1eb1da26..703cce25 100644 --- a/+hw/Window.m +++ b/+hw/Window.m @@ -1,90 +1,146 @@ -classdef Window < handle - %Window Some window on a device you can draw to - % This is an abstract base class for some device you draw to. All - % drawing should be done first to an "off-screen" buffer, and only - % "flipped" onto screen when you request it. i.e. no drawing will be - % visible while you call the drawing/painting but will appear at once - % when you call the flip function. - % - % Part of Rigbox - - % 2012-10 CB created +classdef (Abstract) Window < handle +%HW.WINDOW A window on a computer screen to draw to +% +% All drawing should initially occur on an off-screen buffer, and only +% be flipped onto the window when requested (i.e. drawings will only +% appear on-screen after calling a method to create the drawing AND +% calling a separate method to flip the drawing onto the window). This +% class relies on Psychtoolbox (PTB) for some functionality. +% +% *Note, whenever "time" is mentioned in this file, assume it is given in +% seconds by PTB's `GetSecs` unless otherwise specified. +% +% Part of Rigbox +% +% 2012-10 CB created properties (Abstract) - BackgroundColour %background colour to apply to the window - ForegroundColour %default colour to use for drawing + % A numeric array [R G B] of the background colour of the window + BackgroundColour + % A numeric array [R G B] of the default colour to use for drawing + ForegroundColour end properties (Abstract, SetAccess = protected) - White %the value for white (highest) - Gray %the value for mid-gray - Black %the value for black (lowest) - Red %the value for red - Green %the value for green - Blue %the value for blue - ColourRange %the range from lowest to highest - %Bounding rectangle [left top right bottom] for this window - Bounds - Invalid %True or false: anything drawn since last flip? - TimeInvalidated %Time this window was last invalidated + % The value for white (highest) + White + % The value for mid-gray + Gray + % The value for black (lowest) + Black + % The value for red + Red + % The value for green + Green + % The value for blue + Blue + % The colour range from lowest to highest + ColourRange + % A numeric array [left top right bottom] in pixels for the bounding + % rectangle of the window on-screen + Bounds + % A boolean flag set to true if anything has been drawn since the last + % screen flip, and false otherwise + Invalid + % A numeric of the time the window was last invalidated (i.e. the time + % the off-screen buffer was last drawn to) + TimeInvalidated end properties (SetAccess = protected) - %List of reasons for invalidations that accrue until a flip occurs, at - %which point the list becomes associated with that screen update (i.e. - %a list of changes which were made during the update). + % A cell string list of reasons for invalidations that accrue until a + % flip occurs, at which point the list becomes associated with that + % screen flip (i.e. a list of changes which were made during the flip). InvalidationUpdates = {} end methods (Abstract) - % Flips offscreen buffer to window, "validating" it - % - % [TIME, INVALIDFRAMES, VALIDATIONLAG] = flip(WHEN) performs flip as - % close to system time, WHEN, as possible. Returns the time the - % flipped actually occured (TIME), the whole number of frames (rounded - % down) since the last invalidation (INVALIDFRAMES), and the time for - % the last invalidation to completion of this flip (zero if the window - % was still valid). - % - % [TIME, INVALIDFRAMES, VALIDATIONLAG] = flip() as above, but performs - % flip as soon as possible + % Flips drawing from off-screen buffer to window, validating window + % + % Inputs: + % `when`: an optional input numeric of the time to perform the screen flip. + % If not included, `flip` performs the flip as soon as possible. + % + % Outputs: + % `time`: The time the flip occurs. + % `invalidFrames`: The number of frames since the flip occurred. + % `validationLag`: The time from the last invalidation to the flip + % (i.e. the time from completing the drawing on the off-screen + % buffer to flipping to the window). [time, invalidFrames, validationLag] = flip(obj, when) % Clears to background colour clear(obj) - % Draws a texture + % Draws a texture to off-screen buffer % - % drawTexture(TEX, SRCRECT, DESTRECT, ANGLE, GLOBALALPHA) + % Inputs: + % `tex`: + % `srcRect`: + % `destRect`: + % `angle`: + % `globalAlpha`: drawTexture(obj, tex, srcRect, destRect, angle, globalAlpha) - % Fills rectangular region with a colour + % Fills off-screen buffer's rectangular bounds with a colour % - % fillRect(COLOUR, RECT) + % Inputs: + % `colour`: + % `rect`: fillRect(obj, colour, rect) - %Creates the texture on this device from an image matrix + % Creates a texture on off-screen buffer from an image matrix + % + % Inputs: + % `image`: a matrix... tex = makeTexture(obj, image) - deleteTextures(obj) %Deletes any textures created on this device + % Deletes all textures from off-screen buffer + deleteTextures(obj) - %Change alpha blending factors - [oldSrcFactor, oldDestFactor] = setAlphaBlending(obj, srcFactor, destFactor) + % Changes alpha blending factors + % + % Inputs: + % `srcFactor`: + % `destFactor`: + [oldSrcFactor, oldDestFactor] = ... + setAlphaBlending(obj, srcFactor, destFactor) - [nx, ny] = drawText(obj, text, x, y, colour, vSpacing, wrapAt) + % Draws text to off-screen buffer + % + % Inputs: + % `txt`: a char array of the text to draw + % `x`: + % `y`: + % `colour`: + % `vSpacing`: + % `wrapAt`: + [nx, ny] = drawText(obj, text, x, y, colour, vSpacing, wrapAt) end + methods function invalidate(obj, description) - if nargin >= 2 && ~isempty(description) - obj.InvalidationUpdates = [obj.InvalidationUpdates, description]; - end - if ~obj.Invalid - obj.TimeInvalidated = GetSecs; - obj.Invalid = true; - end + % Invalidates the window. + % + % `invalidate` is most often called when the off-screen buffer is drawn + % to. + % + % Inputs: + % `description`: an optional char array that specifies the reason for + % the invalidation + + if nargin >= 2 && ~isempty(description) + % add new invalidation message + obj.InvalidationUpdates = [obj.InvalidationUpdates, description]; + end + if ~obj.Invalid + % invalidate window + obj.TimeInvalidated = GetSecs; + obj.Invalid = true; end end - + end + end