Prober EPP components added (#202)

* Updated issues in rebasing

* Minor style change on prober/build.gradle

* Fixed warnings for java compilation

* Fixed files to pass all style tests

* Initial Commit.

* Deleted unfinished features. Added ActionHandler and its Unit Tests.

* Included prober subproject in settings.gradle

* Added Protocol Class and its Basic Unit Tests

* Added Changes Suggested by jianglai

* Fixed Gitignore to take out AutoValue generated code

* Removed AutoValue java files

* Added gitignore within prober

* Removed all generated java

* Final Changes in .gitignore

* Added Ssl and WebWhois Action Handlers and their unit tests in addition to the ProbingAction class

* Fixed build.gradle changes requested

* Removed Files irrelevant to current pull request

* Minor fixes to ActionHandler, as responded in comments, removed package-info, and updated settings.gradle

* Fully Updated ActionHandler (missing updated JavaDoc)

* Added changed Protocol and both Inbound and Outbound Markers

* Removed AutoVaue ignore clause from .gitignore

* removed unneccessary dependencies in build.gradle

* Fixed Javadoc and comments for ActionHandler

* Fixed comments and JavaDoc on other files

* EOL added

* Removed Unnecessary Files

* fixed .gradle files styles

* Removed outbound message from ActionHandler's fields and renamed Marker Interfaces

* Fixed javadoc for Marker Interfaced

* Modified Comments on ActionHandler

* Removed LocalAddress from Protocol

* Fixed Travis Build Issues

* Rebased to Master and added in modified Handlers and ProbingAction

* Fixed changes suggested by CydeWeys

* Added missing license headers and JavaDoc

* Minor fix in NewChannelAction JavaDoc

* Minor Style Fix

* Full WebWhoIs Sequence Added

* fixed build issues

* Refactored by responses suggested by jianglai.

* Updated build.gradle file

* Modified license header dates

* Updated WebWhois tests.

* Refactored WebWhois to accomodate jianglai's suggested changes and modified tests to reflect this refactoring

* SpotlessApply run to fix style issues

* Added license header and newline where appropriate.

* Javadoc style fix in tests and removed unused methods

* Refactored ProbingAction to minimize number of unnecessary methods

* Modified tests for WebWhois according to changes suggested by laijiang.

* Removed TestProvider from TestUtils.

* Rebased to Master and added in modified Handlers and ProbingAction

* Fixed changes suggested by CydeWeys

* Added missing license headers and JavaDoc

* Minor fix in NewChannelAction JavaDoc

* Minor Style Fix

* Full WebWhoIs Sequence Added

* fixed build issues

* Refactored by responses suggested by jianglai.

* Updated build.gradle file

* Modified license header dates

* Updated WebWhois tests.

* Refactored WebWhois to accomodate jianglai's suggested changes and modified tests to reflect this refactoring

* SpotlessApply run to fix style issues

* Added license header and newline where appropriate.

* Javadoc style fix in tests and removed unused methods

* Refactored ProbingAction to minimize number of unnecessary methods

* Modified tests for WebWhois according to changes suggested by laijiang.

* Removed TestProvider from TestUtils.

* Rebased to master

* Updated issues in rebasing

* Minor style change on prober/build.gradle

* Fixed warnings for java compilation

* Fixed files to pass all style tests

* Minor syle fixes after succesful rebase onto master

* Initial Commit.

* Added Protocol Class and its Basic Unit Tests

* Fixed Gitignore to take out AutoValue generated code

* Final Changes in .gitignore

* Minor fixes to ActionHandler, as responded in comments, removed package-info, and updated settings.gradle

* Removed AutoVaue ignore clause from .gitignore

* Rebased to Master and added in modified Handlers and ProbingAction

* Full WebWhoIs Sequence Added

* fixed build issues

* Refactored by responses suggested by jianglai.

* Added missing license headers and JavaDoc

* Minor fix in NewChannelAction JavaDoc

* Refactored WebWhois to accomodate jianglai's suggested changes and modified tests to reflect this refactoring

* Full WebWhoIs Sequence Added

* fixed build issues

* Refactored by responses suggested by jianglai.

* Modified license header dates

* Refactored WebWhois to accomodate jianglai's suggested changes and modified tests to reflect this refactoring

* SpotlessApply run to fix style issues

* Added license header and newline where appropriate.

* Javadoc style fix in tests and removed unused methods

* Fixed files to pass all style tests

* Fixed changes suggested by CydeWeys

* Rebased to Master and added in modified Handlers and ProbingAction

* Added missing license headers and JavaDoc

* Minor fix in NewChannelAction JavaDoc

* Minor Style Fix

* Full WebWhoIs Sequence Added

* Refactored by responses suggested by jianglai.

* Updated build.gradle file

* Modified license header dates

* Updated WebWhois tests.

* Refactored WebWhois to accomodate jianglai's suggested changes and modified tests to reflect this refactoring

* SpotlessApply run to fix style issues

* Added license header and newline where appropriate.

* Javadoc style fix in tests and removed unused methods

* Refactored ProbingAction to minimize number of unnecessary methods

* Modified tests for WebWhois according to changes suggested by laijiang.

* Removed TestProvider from TestUtils.

* Rebased to Master and added in modified Handlers and ProbingAction

* Fixed changes suggested by CydeWeys

* Added missing license headers and JavaDoc

* Minor fix in NewChannelAction JavaDoc

* Minor Style Fix

* Full WebWhoIs Sequence Added

* fixed build issues

* Refactored by responses suggested by jianglai.

* Updated build.gradle file

* Modified license header dates

* Updated WebWhois tests.

* Added license header and newline where appropriate.

* Javadoc style fix in tests and removed unused methods

* Refactored ProbingAction to minimize number of unnecessary methods

* Modified tests for WebWhois according to changes suggested by laijiang.

* Removed TestProvider from TestUtils.

* Rebased to master

* Updated issues in rebasing

* Added circular linked list to utils

* License Header added

* Refactored probing sequence to be circular linked list iterator

* Modified ProbingStep tests to reflect new ProbingStep structure.

* Added circular linked list to utils

* Added circular linked list to utils

* License Header added

* License Header added

* Refactored probing sequence to be circular linked list iterator

* Modified ProbingStep tests to reflect new ProbingStep structure.

* Added missing license header to DefaultCircularLinkedListIterator

* Fixed changes suggested by CydeWeys

* Rebased to Master and added in modified Handlers and ProbingAction

* Added missing license headers and JavaDoc

* Minor fix in NewChannelAction JavaDoc

* Minor Style Fix

* Full WebWhoIs Sequence Added

* fixed build issues

* Refactored by responses suggested by jianglai.

* Updated build.gradle file

* Fixed max column length to be 100

* Rebased to Master and added in modified Handlers and ProbingAction

* Modified license header dates

* Updated WebWhois tests.

* Refactored WebWhois to accomodate jianglai's suggested changes and modified tests to reflect this refactoring

* Javadoc style fix in tests and removed unused methods

* Refactored ProbingAction to minimize number of unnecessary methods

* Modified tests for WebWhois according to changes suggested by laijiang.

* Fixed changes suggested by CydeWeys

* Added missing license headers and JavaDoc

* Minor fix in NewChannelAction JavaDoc

* Minor Style Fix

* Full WebWhoIs Sequence Added

* fixed build issues

* Refactored by responses suggested by jianglai.

* Updated build.gradle file

* Modified license header dates

* Updated WebWhois tests.

* Refactored WebWhois to accomodate jianglai's suggested changes and modified tests to reflect this refactoring

* SpotlessApply run to fix style issues

* Added license header and newline where appropriate.

* Javadoc style fix in tests and removed unused methods

* Refactored ProbingAction to minimize number of unnecessary methods

* Added circular linked list to utils

* Added circular linked list to utils

* License Header added

* License Header added

* Refactored probing sequence to be circular linked list iterator

* Refactored probing sequence to be circular linked list iterator

* Modified tests for WebWhois according to changes suggested by laijiang.

* Removed TestProvider from TestUtils.

* ProbingStepTest modified to have fewer unnecessary helper methods

* Updated issues in rebasing

* Fixed max column length to be 100

* Minor changes to pass style tests

* Successful rebase onto finished web-whois branch

* Removed need for TestTokens with Mockito mocks of Tokens

* Fixed style issues in DefaultCircularLinkedListIterator and AbstractCircularLinkedListIterator

* Modified CircularList according to changes suggested by jianglai.

* Added Protocol Class and its Basic Unit Tests

* Added Ssl and WebWhois Action Handlers and their unit tests in addition to the ProbingAction class

* Fixed changes suggested by CydeWeys

* Fixed changes suggested by CydeWeys

* Rebased to Master and added in modified Handlers and ProbingAction

* Rebased to Master and added in modified Handlers and ProbingAction

* Rebased to Master and added in modified Handlers and ProbingAction

* Rebased to Master and added in modified Handlers and ProbingAction

* Rebased to Master and added in modified Handlers and ProbingAction

* Added missing license headers and JavaDoc

* Minor Style Fix

* Minor Style Fix

* Full WebWhoIs Sequence Added

* Full WebWhoIs Sequence Added

* Full WebWhoIs Sequence Added

* Full WebWhoIs Sequence Added

* fixed build issues

* Refactored by responses suggested by jianglai.

* Updated build.gradle file

* Updated WebWhois tests.

* Added Basic EPP structure

* Added Basic EPP structure

* Prober Updated tests

* Prober Updated tests

* Fully functioning EPP sequences with modified WebWhois base.

* Fully functioning EPP sequences with modified WebWhois base.

* Added Modified test server infrastructure.

* Added Modified test server infrastructure.

* Allowed ActionHandler to pass status to next hanlder in pipeline (to be MetricsHandler).

* Allowed ActionHandler to pass status to next hanlder in pipeline (to be MetricsHandler).

* Javadoc on EppRequestMessage added

* Javadoc on EppRequestMessage added

* Updated EppServer to properly send successful Check responses.

* Updated EppServer to properly send successful Check responses.

* Allowed for expected failures in EPP actions.

* Refactored WebWhois to accomodate jianglai's suggested changes and modified tests to reflect this refactoring

* Refactored WebWhois to accomodate jianglai's suggested changes and modified tests to reflect this refactoring

* Refactored WebWhois to accomodate jianglai's suggested changes and modified tests to reflect this refactoring

* Refactored WebWhois to accomodate jianglai's suggested changes and modified tests to reflect this refactoring

* Fully rebased branch to prober-web-whois after refactoring

* Added license header and newline where appropriate.

* Javadoc style fix in tests and removed unused methods

* Javadoc style fix in tests and removed unused methods

* Modified tests for WebWhois according to changes suggested by laijiang.

* Modified tests for WebWhois according to changes suggested by laijiang.

* Removed TestProvider from TestUtils.

* Rebased to master

* Fixed max column length to be 100

* Fixed files to pass all style tests

* Minor changes to pass style tests

* Successful rebase onto circular-list

* Epp Refactored to accomodate circular linked list PR

* Modified construction of Epp Probing Sequences to reflect CircularList change

* Renamed ProberModule provided Duration

* Removed unnecessary ServerSideException file

* Google-Java-Format run on all prober files

* Style fix on ProbingSequence and its unit tests

* Removed subclasses of EppRequestMessage and EppResponseMessage and fixed style and other minor issues

* Style changes implemented as suggested by jianglai

* Added style fixes suggested by mindhog
This commit is contained in:
Aman Sanger 2019-08-15 16:03:36 -04:00 committed by GitHub
parent 86fefa9a03
commit 57975898d5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
66 changed files with 6089 additions and 1083 deletions

View file

@ -23,19 +23,16 @@ import google.registry.monitoring.blackbox.ProberModule.ProberComponent;
*/ */
public class Prober { public class Prober {
/** /** Main Dagger Component */
* Main Dagger Component private static ProberComponent proberComponent =
*/ DaggerProberModule_ProberComponent.builder().build();
private static ProberComponent proberComponent = DaggerProberModule_ProberComponent.builder()
.build();
public static void main(String[] args) { public static void main(String[] args) {
//Obtains WebWhois Sequence provided by proberComponent // Obtains WebWhois Sequence provided by proberComponent
ImmutableSet<ProbingSequence> sequences = ImmutableSet.copyOf(proberComponent.sequences()); ImmutableSet<ProbingSequence> sequences = ImmutableSet.copyOf(proberComponent.sequences());
//Tells Sequences to start running // Tells Sequences to start running
for (ProbingSequence sequence : sequences) { for (ProbingSequence sequence : sequences) {
sequence.start(); sequence.start();
} }

View file

@ -17,6 +17,11 @@ package google.registry.monitoring.blackbox;
import dagger.Component; import dagger.Component;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
import google.registry.monitoring.blackbox.connection.ProbingAction;
import google.registry.monitoring.blackbox.modules.CertificateModule;
import google.registry.monitoring.blackbox.modules.EppModule;
import google.registry.monitoring.blackbox.modules.WebWhoisModule;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.EventLoopGroup; import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup;
@ -35,10 +40,8 @@ import org.joda.time.Duration;
@Module @Module
public class ProberModule { public class ProberModule {
/** /** Default {@link Duration} chosen to be time between each {@link ProbingAction} call. */
* Default {@link Duration} chosen to be time between each {@link ProbingAction} call. private static final Duration DEFAULT_PROBER_INTERVAL = Duration.standardSeconds(4);
*/
private static final Duration DEFAULT_DURATION = Duration.standardSeconds(4);
/** /**
* {@link Provides} the {@link SslProvider} used by instances of {@link * {@link Provides} the {@link SslProvider} used by instances of {@link
@ -51,9 +54,7 @@ public class ProberModule {
return OpenSsl.isAvailable() ? SslProvider.OPENSSL : SslProvider.JDK; return OpenSsl.isAvailable() ? SslProvider.OPENSSL : SslProvider.JDK;
} }
/** /** {@link Provides} one global {@link EventLoopGroup} shared by each {@link ProbingSequence}. */
* {@link Provides} one global {@link EventLoopGroup} shared by each {@link ProbingSequence}.
*/
@Provides @Provides
@Singleton @Singleton
EventLoopGroup provideEventLoopGroup() { EventLoopGroup provideEventLoopGroup() {
@ -71,27 +72,36 @@ public class ProberModule {
} }
/** /**
* {@link Provides} above {@code DEFAULT_DURATION} for all provided {@link ProbingStep}s to use. * {@link Provides} above {@code DEFAULT_PROBER_INTERVAL} for all provided {@link ProbingStep}s to
* use.
*/ */
@Provides @Provides
@Singleton @Singleton
Duration provideDuration() { Duration provideProbeInterval() {
return DEFAULT_DURATION; return DEFAULT_PROBER_INTERVAL;
} }
/** /**
* Root level {@link Component} that provides each {@link ProbingSequence}. * {@link Provides} general {@link Bootstrap} for which a new instance is provided in any {@link
* ProbingSequence}.
*/ */
@Provides
Bootstrap provideBootstrap(EventLoopGroup eventLoopGroup) {
return new Bootstrap().group(eventLoopGroup).channel(NioSocketChannel.class);
}
/** Root level {@link Component} that provides each {@link ProbingSequence}. */
@Singleton @Singleton
@Component( @Component(
modules = { modules = {
ProberModule.class, ProberModule.class,
WebWhoisModule.class, WebWhoisModule.class,
EppModule.class,
CertificateModule.class
}) })
public interface ProberComponent { public interface ProberComponent {
//Standard WebWhois sequence // Standard WebWhois sequence
Set<ProbingSequence> sequences(); Set<ProbingSequence> sequences();
} }
} }

View file

@ -1,316 +0,0 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// 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.
package google.registry.monitoring.blackbox;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.flogger.StackSize.SMALL;
import static google.registry.monitoring.blackbox.Protocol.PROTOCOL_KEY;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.google.common.flogger.FluentLogger;
import google.registry.monitoring.blackbox.exceptions.UndeterminedStateException;
import google.registry.monitoring.blackbox.handlers.ActionHandler;
import google.registry.monitoring.blackbox.messages.OutboundMessageType;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPromise;
import io.netty.channel.local.LocalAddress;
import io.netty.util.AttributeKey;
import io.netty.util.HashedWheelTimer;
import io.netty.util.Timer;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import javax.inject.Provider;
import org.joda.time.Duration;
/**
* AutoValue class that represents action generated by {@link ProbingStep}
*
* <p> Inherits from {@link Callable<ChannelFuture>}, as it has can be called
* to perform its specified task, and return the {@link ChannelFuture} that will be informed when
* the task has been completed </p>
*
* <p> Is an immutable class, as it is comprised of the tools necessary for making a specific type
* of connection. It goes hand in hand with {@link Protocol}, which specifies the kind of overall
* connection to be made. {@link Protocol} gives the outline and {@link ProbingAction} gives the
* details of that connection.</p>
*
* <p> In its build, if there is no channel supplied, it will create a channel from the attributes
* already supplied. Then, it only sends the {@link OutboundMessageType} down the pipeline when
* informed that the connection is successful. If the channel is supplied, the connection future is
* automatically set to successful.</p>
*/
@AutoValue
public abstract class ProbingAction implements Callable<ChannelFuture> {
/**
* {@link AttributeKey} in channel that gives {@link ChannelFuture} that is set to success when
* channel is active.
*/
public static final AttributeKey<ChannelFuture> CONNECTION_FUTURE_KEY = AttributeKey
.valueOf("CONNECTION_FUTURE_KEY");
/**
* {@link AttributeKey} in channel that gives the information of the channel's host.
*/
public static final AttributeKey<String> REMOTE_ADDRESS_KEY = AttributeKey
.valueOf("REMOTE_ADDRESS_KEY");
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
/**
* {@link Timer} that rate limits probing
*/
private static final Timer timer = new HashedWheelTimer();
public static Builder builder() {
return new AutoValue_ProbingAction.Builder();
}
/**
* Adds provided {@link ChannelHandler}s to the {@link ChannelPipeline} specified
*
* @param channelPipeline is pipeline associated with channel that we want to add handlers to
* @param handlerProviders are a list of provider objects that give us the requisite handlers Adds
* to the pipeline, the list of handlers in the order specified
*/
private static void addHandlers(
ChannelPipeline channelPipeline,
ImmutableList<Provider<? extends ChannelHandler>> handlerProviders) {
for (Provider<? extends ChannelHandler> handlerProvider : handlerProviders) {
channelPipeline.addLast(handlerProvider.get());
}
}
/**
* Actual {@link Duration} of this delay
*/
public abstract Duration delay();
/**
* {@link OutboundMessageType} instance that we write and flush down pipeline to server
*/
public abstract OutboundMessageType outboundMessage();
/**
* {@link Channel} object that either created by or passed into this {@link ProbingAction}
* instance
*/
public abstract Channel channel();
/**
* The {@link Protocol} instance that specifies type of connection
*/
public abstract Protocol protocol();
/**
* The hostname of the remote host we have a connection or will make a connection to
*/
public abstract String host();
/**
* Performs the work of the actual action.
*
* <p>First, checks if channel is active by setting a listener to perform the bulk of the work
* when the connection future is successful.</p>
*
* <p>Once the connection is successful, we establish which of the handlers in the pipeline is
* the {@link ActionHandler}.From that, we can obtain a future that is marked as a success when we
* receive an expected response from the server.</p>
*
* <p>Next, we set a timer set to a specified delay. After the delay has passed, we send the
* {@code outboundMessage} down the channel pipeline, and when we observe a success or failure, we
* inform the {@link ProbingStep} of this.</p>
*
* @return {@link ChannelFuture} that denotes when the action has been successfully performed.
*/
@Override
public ChannelFuture call() {
//ChannelPromise that we return
ChannelPromise finished = channel().newPromise();
//Ensures channel has been set up with connection future as an attribute
checkNotNull(channel().attr(CONNECTION_FUTURE_KEY).get());
//When connection is established call super.call and set returned listener to success
channel().attr(CONNECTION_FUTURE_KEY).get().addListener(
(ChannelFuture connectionFuture) -> {
if (connectionFuture.isSuccess()) {
logger.atInfo().log(String
.format("Successful connection to remote host: %s at port: %d", host(),
protocol().port()));
ActionHandler actionHandler;
try {
actionHandler = channel().pipeline().get(ActionHandler.class);
} catch (ClassCastException e) {
//If we don't actually have an ActionHandler instance, we have an issue, and throw
// an UndeterminedStateException
logger.atSevere().withStackTrace(SMALL).log("ActionHandler not in Channel Pipeline");
throw new UndeterminedStateException("No Action Handler found in pipeline");
}
ChannelFuture channelFuture = actionHandler.getFinishedFuture();
timer.newTimeout(timeout -> {
// Write appropriate outboundMessage to pipeline
ChannelFuture unusedFutureWriteAndFlush =
channel().writeAndFlush(outboundMessage());
channelFuture.addListeners(
future -> {
if (future.isSuccess()) {
ChannelFuture unusedFuture = finished.setSuccess();
} else {
ChannelFuture unusedFuture = finished.setFailure(future.cause());
}
},
//If we don't have a persistent connection, close the connection to this
// channel
future -> {
if (!protocol().persistentConnection()) {
ChannelFuture closedFuture = channel().close();
closedFuture.addListener(
f -> {
if (f.isSuccess()) {
logger.atInfo()
.log("Closed stale channel. Moving on to next ProbingStep");
} else {
logger.atWarning()
.log(
"Could not close channel. Stale connection still exists"
+ ".");
}
}
);
}
}
);
},
delay().getStandardSeconds(),
TimeUnit.SECONDS);
} else {
//if we receive a failure, log the failure, and close the channel
logger.atSevere().withCause(connectionFuture.cause()).log(
"Cannot connect to relay channel for %s channel: %s.",
protocol().name(), this.channel());
ChannelFuture unusedFuture = channel().close();
}
}
);
return finished;
}
@Override
public final String toString() {
return String.format(
"ProbingAction with delay: %d\n"
+ "outboundMessage: %s\n"
+ "protocol: %s\n"
+ "host: %s\n",
delay().getStandardSeconds(),
outboundMessage(),
protocol(),
host()
);
}
/**
* {@link AutoValue.Builder} that does work of creating connection when not already present.
*/
@AutoValue.Builder
public abstract static class Builder {
private Bootstrap bootstrap;
public Builder setBootstrap(Bootstrap bootstrap) {
this.bootstrap = bootstrap;
return this;
}
public abstract Builder setDelay(Duration value);
public abstract Builder setOutboundMessage(OutboundMessageType value);
public abstract Builder setProtocol(Protocol value);
public abstract Builder setHost(String value);
public abstract Builder setChannel(Channel channel);
abstract Protocol protocol();
abstract Channel channel();
abstract String host();
abstract ProbingAction autoBuild();
public ProbingAction build() {
// Sets SocketAddress to bind to.
SocketAddress address;
try {
InetAddress hostAddress = InetAddress.getByName(host());
address = new InetSocketAddress(hostAddress, protocol().port());
} catch (UnknownHostException e) {
address = new LocalAddress(host());
}
//Sets channel supplied or to be created.
Channel channel;
try {
channel = channel();
} catch (IllegalStateException e) {
channel = null;
}
checkArgument(channel == null ^ bootstrap == null,
"One and only one of bootstrap and channel must be supplied.");
//If a channel is supplied, nothing is needed to be done
//Otherwise, a Bootstrap must be supplied and be used for creating the channel
if (channel == null) {
bootstrap.handler(
new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel outboundChannel)
throws Exception {
//Uses Handlers from Protocol to fill pipeline
addHandlers(outboundChannel.pipeline(), protocol().handlerProviders());
}
})
.attr(PROTOCOL_KEY, protocol())
.attr(REMOTE_ADDRESS_KEY, host());
logger.atInfo().log("Initialized bootstrap with channel Handlers");
//ChannelFuture that performs action when connection is established
ChannelFuture connectionFuture = bootstrap.connect(address);
setChannel(connectionFuture.channel());
connectionFuture.channel().attr(CONNECTION_FUTURE_KEY).set(connectionFuture);
}
//now we can actually build the ProbingAction
return autoBuild();
}
}
}

View file

@ -15,6 +15,7 @@
package google.registry.monitoring.blackbox; package google.registry.monitoring.blackbox;
import com.google.common.flogger.FluentLogger; import com.google.common.flogger.FluentLogger;
import google.registry.monitoring.blackbox.connection.ProbingAction;
import google.registry.monitoring.blackbox.exceptions.UnrecoverableStateException; import google.registry.monitoring.blackbox.exceptions.UnrecoverableStateException;
import google.registry.monitoring.blackbox.tokens.Token; import google.registry.monitoring.blackbox.tokens.Token;
import google.registry.util.CircularList; import google.registry.util.CircularList;
@ -26,29 +27,25 @@ import io.netty.channel.EventLoopGroup;
/** /**
* Represents Sequence of {@link ProbingStep}s that the Prober performs in order. * Represents Sequence of {@link ProbingStep}s that the Prober performs in order.
* *
* <p>Inherits from {@link CircularList}, with element type of * <p>Inherits from {@link CircularList}, with element type of {@link ProbingStep} as the manner in
* {@link ProbingStep} as the manner in which the sequence is carried out is similar to the {@link * which the sequence is carried out is similar to the {@link CircularList}. However, the {@link
* CircularList}. However, the {@link Builder} of {@link ProbingSequence} override * Builder} of {@link ProbingSequence} override {@link CircularList.AbstractBuilder} allowing for
* {@link CircularList.AbstractBuilder} allowing for more customized flows, that are looped, but * more customized flows, that are looped, but not necessarily entirely circular. Example: first ->
* not necessarily entirely circular. * second -> third -> fourth -> second -> third -> fourth -> second -> ...
* Example: first -> second -> third -> fourth -> second -> third -> fourth -> second -> ...
* </p>
* *
* <p>Created with {@link Builder} where we specify {@link EventLoopGroup}, {@link AbstractChannel} * <p>Created with {@link Builder} where we specify {@link EventLoopGroup}, {@link AbstractChannel}
* class type, then sequentially add in the {@link ProbingStep.Builder}s in order and mark which one * class type, then sequentially add in the {@link ProbingStep.Builder}s in order and mark which one
* is the first repeated step.</p> * is the first repeated step.
* *
* <p>{@link ProbingSequence} implicitly points each {@link ProbingStep} to the next one, so once * <p>{@link ProbingSequence} implicitly points each {@link ProbingStep} to the next one, so once
* the first one is activated with the requisite {@link Token}, the {@link ProbingStep}s do the rest * the first one is activated with the requisite {@link Token}, the {@link ProbingStep}s do the rest
* of the work.</p> * of the work.
*/ */
public class ProbingSequence extends CircularList<ProbingStep> { public class ProbingSequence extends CircularList<ProbingStep> {
private static final FluentLogger logger = FluentLogger.forEnclosingClass(); private static final FluentLogger logger = FluentLogger.forEnclosingClass();
/** /** Each {@link ProbingSequence} requires a start token to begin running. */
* Each {@link ProbingSequence} requires a start token to begin running.
*/
private Token startToken; private Token startToken;
/** /**
@ -57,38 +54,27 @@ public class ProbingSequence extends CircularList<ProbingStep> {
*/ */
private boolean lastStep = false; private boolean lastStep = false;
/** /** {@link ProbingSequence} object that represents first step in the sequence. */
* {@link ProbingSequence} object that represents first step in the sequence.
*/
private ProbingSequence first; private ProbingSequence first;
/** Standard constructor for {@link ProbingSequence} in the list that assigns value and token. */
/**
* Standard constructor for {@link ProbingSequence} in the list that assigns value and token.
*/
private ProbingSequence(ProbingStep value, Token startToken) { private ProbingSequence(ProbingStep value, Token startToken) {
super(value); super(value);
this.startToken = startToken; this.startToken = startToken;
} }
/** /** Method used in {@link Builder} to mark the last step in the sequence. */
* Method used in {@link Builder} to mark the last step in the sequence.
*/
private void markLast() { private void markLast() {
lastStep = true; lastStep = true;
} }
/** /** Obtains next {@link ProbingSequence} in sequence. */
* Obtains next {@link ProbingSequence} in sequence instead of next {@link CircularList}.
*/
@Override @Override
public ProbingSequence next() { public ProbingSequence next() {
return (ProbingSequence) super.next(); return (ProbingSequence) super.next();
} }
/** /** Starts ProbingSequence by calling first {@code runStep} with {@code startToken}. */
* Starts ProbingSequence by calling first {@code runStep} with {@code startToken}.
*/
public void start() { public void start() {
runStep(startToken); runStep(startToken);
} }
@ -97,16 +83,16 @@ public class ProbingSequence extends CircularList<ProbingStep> {
* Generates new {@link ProbingAction} from {@link ProbingStep}, calls the action, then retrieves * Generates new {@link ProbingAction} from {@link ProbingStep}, calls the action, then retrieves
* the result of the action. * the result of the action.
* *
* @param token - used to generate the {@link ProbingAction} by calling {@code * <p>Calls {@code runNextStep} to have next {@link ProbingSequence} call {@code runStep} with
* get().generateAction}. * next token depending on if the current step is the last one in the sequence.
*
* <p>Calls {@code runNextStep} to have next {@link ProbingSequence} call {@code runStep}
* with next token depending on if the current step is the last one in the sequence.
* *
* <p>If unable to generate the action, or the calling the action results in an immediate error, * <p>If unable to generate the action, or the calling the action results in an immediate error,
* we note an error. Otherwise, if the future marked as finished when the action is completed is * we note an error. Otherwise, if the future marked as finished when the action is completed is
* marked as a success, we note a success. Otherwise, if the cause of failure will either be a * marked as a success, we note a success. Otherwise, if the cause of failure will either be a
* failure or error. </p> * failure or error.
*
* @param token - used to generate the {@link ProbingAction} by calling {@code
* get().generateAction}.
*/ */
private void runStep(Token token) { private void runStep(Token token) {
ProbingAction currentAction; ProbingAction currentAction;
@ -121,8 +107,7 @@ public class ProbingSequence extends CircularList<ProbingStep> {
} catch (UnrecoverableStateException e) { } catch (UnrecoverableStateException e) {
// On an UnrecoverableStateException, terminate the sequence. // On an UnrecoverableStateException, terminate the sequence.
logger.atSevere().withCause(e).log( logger.atSevere().withCause(e).log("Unrecoverable error in generating or calling action.");
"Unrecoverable error in generating or calling action.");
return; return;
} catch (Exception e) { } catch (Exception e) {
@ -134,7 +119,8 @@ public class ProbingSequence extends CircularList<ProbingStep> {
return; return;
} }
future.addListener(f -> { future.addListener(
f -> {
if (f.isSuccess()) { if (f.isSuccess()) {
// On a successful result, we log as a successful step, and note a success. // On a successful result, we log as a successful step, and note a success.
logger.atInfo().log(String.format("Successfully completed Probing Step: %s", this)); logger.atInfo().log(String.format("Successfully completed Probing Step: %s", this));
@ -156,9 +142,8 @@ public class ProbingSequence extends CircularList<ProbingStep> {
token.setChannel(currentAction.channel()); token.setChannel(currentAction.channel());
} }
//Calls next runStep // Calls next runStep
runNextStep(token); runNextStep(token);
}); });
} }
@ -204,9 +189,7 @@ public class ProbingSequence extends CircularList<ProbingStep> {
this.startToken = startToken; this.startToken = startToken;
} }
/** /** We take special note of the first repeated step. */
* We take special note of the first repeated step.
*/
public Builder markFirstRepeated() { public Builder markFirstRepeated() {
firstRepeatedSequenceStep = current; firstRepeatedSequenceStep = current;
return this; return this;

View file

@ -15,6 +15,8 @@
package google.registry.monitoring.blackbox; package google.registry.monitoring.blackbox;
import com.google.auto.value.AutoValue; import com.google.auto.value.AutoValue;
import google.registry.monitoring.blackbox.connection.ProbingAction;
import google.registry.monitoring.blackbox.connection.Protocol;
import google.registry.monitoring.blackbox.exceptions.UndeterminedStateException; import google.registry.monitoring.blackbox.exceptions.UndeterminedStateException;
import google.registry.monitoring.blackbox.messages.OutboundMessageType; import google.registry.monitoring.blackbox.messages.OutboundMessageType;
import google.registry.monitoring.blackbox.tokens.Token; import google.registry.monitoring.blackbox.tokens.Token;
@ -25,10 +27,10 @@ import org.joda.time.Duration;
* {@link AutoValue} class that represents generator of actions performed at each step in {@link * {@link AutoValue} class that represents generator of actions performed at each step in {@link
* ProbingSequence}. * ProbingSequence}.
* *
* <p>Holds the unchanged components in a given step of the {@link ProbingSequence}, which are * <p>Holds the unchanged components in a given step of the {@link ProbingSequence}, which are the
* the {@link OutboundMessageType}, {@link Protocol}, {@link Duration}, and {@link Bootstrap} * {@link OutboundMessageType}, {@link Protocol}, {@link Duration}, and {@link Bootstrap} instances.
* instances. It then modifies these components on each loop iteration with the consumed {@link * It then modifies these components on each loop iteration with the consumed {@link Token} and from
* Token} and from that, generates a new {@link ProbingAction} to call.</p> * that, generates a new {@link ProbingAction} to call.
*/ */
@AutoValue @AutoValue
public abstract class ProbingStep { public abstract class ProbingStep {
@ -37,14 +39,10 @@ public abstract class ProbingStep {
return new AutoValue_ProbingStep.Builder(); return new AutoValue_ProbingStep.Builder();
} }
/** /** Time delay duration between actions. */
* Time delay duration between actions.
*/
abstract Duration duration(); abstract Duration duration();
/** /** {@link Protocol} type for this step. */
* {@link Protocol} type for this step.
*/
abstract Protocol protocol(); abstract Protocol protocol();
/** /**
@ -63,7 +61,8 @@ public abstract class ProbingStep {
*/ */
public ProbingAction generateAction(Token token) throws UndeterminedStateException { public ProbingAction generateAction(Token token) throws UndeterminedStateException {
OutboundMessageType message = token.modifyMessage(messageTemplate()); OutboundMessageType message = token.modifyMessage(messageTemplate());
ProbingAction.Builder probingActionBuilder = ProbingAction.builder() ProbingAction.Builder probingActionBuilder =
ProbingAction.builder()
.setDelay(duration()) .setDelay(duration())
.setProtocol(protocol()) .setProtocol(protocol())
.setOutboundMessage(message) .setOutboundMessage(message)
@ -80,15 +79,12 @@ public abstract class ProbingStep {
@Override @Override
public final String toString() { public final String toString() {
return String.format("ProbingStep with Protocol: %s\n" return String.format(
+ "OutboundMessage: %s\n", "ProbingStep with Protocol: %s\n" + "OutboundMessage: %s\n",
protocol(), protocol(), messageTemplate().getClass().getName());
messageTemplate().getClass().getName());
} }
/** /** Standard {@link AutoValue.Builder} for {@link ProbingStep}. */
* Standard {@link AutoValue.Builder} for {@link ProbingStep}.
*/
@AutoValue.Builder @AutoValue.Builder
public abstract static class Builder { public abstract static class Builder {
@ -103,4 +99,3 @@ public abstract class ProbingStep {
public abstract ProbingStep build(); public abstract ProbingStep build();
} }
} }

View file

@ -0,0 +1,293 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// 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.
package google.registry.monitoring.blackbox.connection;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.flogger.StackSize.SMALL;
import static google.registry.monitoring.blackbox.connection.Protocol.PROTOCOL_KEY;
import com.google.auto.value.AutoValue;
import com.google.common.flogger.FluentLogger;
import google.registry.monitoring.blackbox.ProbingStep;
import google.registry.monitoring.blackbox.exceptions.UndeterminedStateException;
import google.registry.monitoring.blackbox.handlers.ActionHandler;
import google.registry.monitoring.blackbox.messages.OutboundMessageType;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPromise;
import io.netty.channel.local.LocalAddress;
import io.netty.util.AttributeKey;
import io.netty.util.HashedWheelTimer;
import io.netty.util.Timer;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import javax.inject.Provider;
import org.joda.time.Duration;
/**
* AutoValue class that represents action generated by {@link ProbingStep}
*
* <p>Inherits from {@link Callable<ChannelFuture>}, as it has can be called to perform its
* specified task, and return the {@link ChannelFuture} that will be informed when the task has been
* completed
*
* <p>Is an immutable class, as it is comprised of the tools necessary for making a specific type of
* connection. It goes hand in hand with {@link Protocol}, which specifies the kind of overall
* connection to be made. {@link Protocol} gives the outline and {@link ProbingAction} gives the
* details of that connection.
*
* <p>In its build, if there is no channel supplied, it will create a channel from the attributes
* already supplied. Then, it only sends the {@link OutboundMessageType} down the pipeline when
* informed that the connection is successful. If the channel is supplied, the connection future is
* automatically set to successful.
*/
@AutoValue
public abstract class ProbingAction implements Callable<ChannelFuture> {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
/**
* {@link AttributeKey} in channel that gives {@link ChannelFuture} that is set to success when
* channel is active.
*/
public static final AttributeKey<ChannelFuture> CONNECTION_FUTURE_KEY =
AttributeKey.valueOf("CONNECTION_FUTURE_KEY");
/** {@link AttributeKey} in channel that gives the information of the channel's host. */
public static final AttributeKey<String> REMOTE_ADDRESS_KEY =
AttributeKey.valueOf("REMOTE_ADDRESS_KEY");
/** {@link Timer} that rate limits probing */
private static final Timer timer = new HashedWheelTimer();
public static Builder builder() {
return new AutoValue_ProbingAction.Builder();
}
/** Actual {@link Duration} of this delay */
public abstract Duration delay();
/** {@link OutboundMessageType} instance that we write and flush down pipeline to server */
public abstract OutboundMessageType outboundMessage();
/**
* {@link Channel} object that is either created by or passed into this {@link ProbingAction}
* instance
*/
public abstract Channel channel();
/** The {@link Protocol} instance that specifies type of connection */
public abstract Protocol protocol();
/** The hostname of the remote host we have a connection or will make a connection to */
public abstract String host();
/**
* Performs the work of the actual action.
*
* <p>First, checks if channel is active by setting a listener to perform the bulk of the work
* when the connection future is successful.
*
* <p>Once the connection is successful, we establish which of the handlers in the pipeline is the
* {@link ActionHandler}.From that, we can obtain a future that is marked as a success when we
* receive an expected response from the server.
*
* <p>Next, we set a timer set to a specified delay. After the delay has passed, we send the
* {@code outboundMessage} down the channel pipeline, and when we observe a success or failure, we
* inform the {@link ProbingStep} of this.
*
* @return {@link ChannelFuture} that denotes when the action has been successfully performed.
*/
@Override
public ChannelFuture call() {
// ChannelPromise that we return
ChannelPromise finished = channel().newPromise();
// Ensures channel has been set up with connection future as an attribute
checkNotNull(channel().attr(CONNECTION_FUTURE_KEY).get());
// When connection is established call super.call and set returned listener to success
channel()
.attr(CONNECTION_FUTURE_KEY)
.get()
.addListener(
(ChannelFuture connectionFuture) -> {
if (connectionFuture.isSuccess()) {
logger.atInfo().log(
String.format(
"Successful connection to remote host: %s at port: %d",
host(), protocol().port()));
ActionHandler actionHandler;
try {
actionHandler = channel().pipeline().get(ActionHandler.class);
} catch (ClassCastException e) {
// If we don't actually have an ActionHandler instance, we have an issue, and
// throw an UndeterminedStateException.
logger.atSevere().withStackTrace(SMALL).log(
"ActionHandler not in Channel Pipeline");
throw new UndeterminedStateException("No Action Handler found in pipeline");
}
ChannelFuture channelFuture = actionHandler.getFinishedFuture();
timer.newTimeout(
timeout -> {
// Write appropriate outboundMessage to pipeline
ChannelFuture unusedFutureWriteAndFlush =
channel().writeAndFlush(outboundMessage());
channelFuture.addListeners(
future -> {
if (future.isSuccess()) {
ChannelFuture unusedFuture = finished.setSuccess();
} else {
ChannelFuture unusedFuture = finished.setFailure(future.cause());
}
},
// If we don't have a persistent connection, close the connection to this
// channel
future -> {
if (!protocol().persistentConnection()) {
ChannelFuture closedFuture = channel().close();
closedFuture.addListener(
f -> {
if (f.isSuccess()) {
logger.atInfo().log(
"Closed stale channel. Moving on to next ProbingStep");
} else {
logger.atWarning().log(
"Issue closing stale channel. Most likely already "
+ "closed.");
}
});
}
});
},
delay().getStandardSeconds(),
TimeUnit.SECONDS);
} else {
// if we receive a failure, log the failure, and close the channel
logger.atSevere().withCause(connectionFuture.cause()).log(
"Cannot connect to relay channel for %s channel: %s.",
protocol().name(), this.channel());
ChannelFuture unusedFuture = channel().close();
}
});
return finished;
}
@Override
public final String toString() {
return String.format(
"ProbingAction with delay: %d\n"
+ "outboundMessage: %s\n"
+ "protocol: %s\n"
+ "host: %s\n",
delay().getStandardSeconds(), outboundMessage(), protocol(), host());
}
/** {@link AutoValue.Builder} that does work of creating connection when not already present. */
@AutoValue.Builder
public abstract static class Builder {
private Bootstrap bootstrap;
public Builder setBootstrap(Bootstrap bootstrap) {
this.bootstrap = bootstrap;
return this;
}
public abstract Builder setDelay(Duration value);
public abstract Builder setOutboundMessage(OutboundMessageType value);
public abstract Builder setProtocol(Protocol value);
public abstract Builder setHost(String value);
public abstract Builder setChannel(Channel channel);
abstract Protocol protocol();
abstract Channel channel();
abstract String host();
abstract ProbingAction autoBuild();
public ProbingAction build() {
// Sets SocketAddress to bind to.
SocketAddress address;
try {
InetAddress hostAddress = InetAddress.getByName(host());
address = new InetSocketAddress(hostAddress, protocol().port());
} catch (UnknownHostException e) {
// If the supplied host isn't a valid one, we assume we are using a test host, meaning we
// are using a LocalAddress as our SocketAddress. If this isn't the case, we will anyways
// throw an error from now being able to connect to the LocalAddress.
address = new LocalAddress(host());
}
// Sets channel supplied or to be created.
Channel channel;
try {
channel = channel();
} catch (IllegalStateException e) {
channel = null;
}
checkArgument(
channel == null ^ bootstrap == null,
"One and only one of bootstrap and channel must be supplied.");
// If a channel is supplied, nothing is needed to be done
// Otherwise, a Bootstrap must be supplied and be used for creating the channel
if (channel == null) {
bootstrap
.handler(
new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel outboundChannel) throws Exception {
// Uses Handlers from Protocol to fill pipeline in order of provided handlers.
for (Provider<? extends ChannelHandler> handlerProvider :
protocol().handlerProviders()) {
outboundChannel.pipeline().addLast(handlerProvider.get());
}
}
})
.attr(PROTOCOL_KEY, protocol())
.attr(REMOTE_ADDRESS_KEY, host());
logger.atInfo().log("Initialized bootstrap with channel Handlers");
// ChannelFuture that performs action when connection is established
ChannelFuture connectionFuture = bootstrap.connect(address);
setChannel(connectionFuture.channel());
connectionFuture.channel().attr(CONNECTION_FUTURE_KEY).set(connectionFuture);
}
// now we can actually build the ProbingAction
return autoBuild();
}
}
}

View file

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package google.registry.monitoring.blackbox; package google.registry.monitoring.blackbox.connection;
import com.google.auto.value.AutoValue; import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
@ -20,15 +20,11 @@ import io.netty.channel.ChannelHandler;
import io.netty.util.AttributeKey; import io.netty.util.AttributeKey;
import javax.inject.Provider; import javax.inject.Provider;
/** /** {@link AutoValue} class that stores all unchanged variables necessary for type of connection. */
* {@link AutoValue} class that stores all unchanged variables necessary for type of connection.
*/
@AutoValue @AutoValue
public abstract class Protocol { public abstract class Protocol {
/** /** {@link AttributeKey} that lets channel reference {@link Protocol} that created it. */
* {@link AttributeKey} that lets channel reference {@link Protocol} that created it.
*/
public static final AttributeKey<Protocol> PROTOCOL_KEY = AttributeKey.valueOf("PROTOCOL_KEY"); public static final AttributeKey<Protocol> PROTOCOL_KEY = AttributeKey.valueOf("PROTOCOL_KEY");
public static Builder builder() { public static Builder builder() {
@ -39,30 +35,20 @@ public abstract class Protocol {
public abstract int port(); public abstract int port();
/** /** The {@link ChannelHandler} providers to use for the protocol, in order. */
* The {@link ChannelHandler} providers to use for the protocol, in order. public abstract ImmutableList<Provider<? extends ChannelHandler>> handlerProviders();
*/
abstract ImmutableList<Provider<? extends ChannelHandler>> handlerProviders();
/** /** Boolean that notes if connection associated with Protocol is persistent. */
* Boolean that notes if connection associated with Protocol is persistent. public abstract boolean persistentConnection();
*/
abstract boolean persistentConnection();
@Override @Override
public final String toString() { public final String toString() {
return String.format( return String.format(
"Protocol with name: %s, port: %d, providers: %s, and persistent connection: %s", "Protocol with name: %s, port: %d, providers: %s, and persistent connection: %s",
name(), name(), port(), handlerProviders(), persistentConnection());
port(),
handlerProviders(),
persistentConnection()
);
} }
/** /** Default {@link AutoValue.Builder} for {@link Protocol}. */
* Default {@link AutoValue.Builder} for {@link Protocol}.
*/
@AutoValue.Builder @AutoValue.Builder
public abstract static class Builder { public abstract static class Builder {

View file

@ -0,0 +1,30 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// 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.
package google.registry.monitoring.blackbox.exceptions;
/**
* Subclass of {@link UndeterminedStateException} that represents all instances when the action
* performed failed due to the fault of the Prober when the action is an EPP action.
*/
public class EppClientException extends UndeterminedStateException {
public EppClientException(String msg) {
super(msg);
}
public EppClientException(Throwable e) {
super(e);
}
}

View file

@ -14,9 +14,7 @@
package google.registry.monitoring.blackbox.exceptions; package google.registry.monitoring.blackbox.exceptions;
/** /** Base exception class for all instances when the status of the action performed is FAILURE. */
* Base exception class for all instances when the status of the action performed is FAILURE.
*/
public class FailureException extends Exception { public class FailureException extends Exception {
public FailureException(String msg) { public FailureException(String msg) {

View file

@ -15,7 +15,7 @@
package google.registry.monitoring.blackbox.handlers; package google.registry.monitoring.blackbox.handlers;
import com.google.common.flogger.FluentLogger; import com.google.common.flogger.FluentLogger;
import google.registry.monitoring.blackbox.ProbingAction; import google.registry.monitoring.blackbox.connection.ProbingAction;
import google.registry.monitoring.blackbox.exceptions.FailureException; import google.registry.monitoring.blackbox.exceptions.FailureException;
import google.registry.monitoring.blackbox.exceptions.UndeterminedStateException; import google.registry.monitoring.blackbox.exceptions.UndeterminedStateException;
import google.registry.monitoring.blackbox.messages.InboundMessageType; import google.registry.monitoring.blackbox.messages.InboundMessageType;
@ -27,50 +27,40 @@ import io.netty.channel.SimpleChannelInboundHandler;
/** /**
* Superclass of all {@link io.netty.channel.ChannelHandler}s placed at end of channel pipeline * Superclass of all {@link io.netty.channel.ChannelHandler}s placed at end of channel pipeline
* *
* <p> {@link ActionHandler} inherits from {@link SimpleChannelInboundHandler<InboundMessageType>}, * <p>{@link ActionHandler} inherits from {@link SimpleChannelInboundHandler<InboundMessageType>},
* as it should only be passed in messages that implement the {@link InboundMessageType} * as it should only be passed in messages that implement the {@link InboundMessageType} interface.
* interface.</p>
* *
* <p> The {@link ActionHandler} skeleton exists for a few main purposes. First, it returns a * <p>The {@link ActionHandler} skeleton exists for a few main purposes. First, it returns a {@link
* {@link ChannelPromise}, which informs the {@link ProbingAction} in charge that a response has * ChannelPromise}, which informs the {@link ProbingAction} in charge that a response has been read.
* been read. Second, with any exception thrown, the connection is closed, and the ProbingAction * Second, with any exception thrown, the connection is closed, and the ProbingAction governing this
* governing this channel is informed of the error. If the error is an instance of a {@link * channel is informed of the error. If the error is an instance of a {@link FailureException}
* FailureException} {@code finished} is marked as a failure with cause {@link FailureException}. If * {@code finished} is marked as a failure with cause {@link FailureException}. If it is any other
* it is any other type of error, it is treated as an {@link UndeterminedStateException} and {@code * type of error, it is treated as an {@link UndeterminedStateException} and {@code finished} set as
* finished} set as a failure with the same cause as what caused the exception. Lastly, if no error * a failure with the same cause as what caused the exception. Lastly, if no error is thrown, we
* is thrown, we know the action completed as a success, and, as such, we mark {@code finished} as a * know the action completed as a success, and, as such, we mark {@code finished} as a success.
* success.</p>
* *
* <p>Subclasses specify further work to be done for specific kinds of channel pipelines. </p> * <p>Subclasses specify further work to be done for specific kinds of channel pipelines.
*/ */
public abstract class ActionHandler extends SimpleChannelInboundHandler<InboundMessageType> { public abstract class ActionHandler extends SimpleChannelInboundHandler<InboundMessageType> {
private static final FluentLogger logger = FluentLogger.forEnclosingClass(); private static final FluentLogger logger = FluentLogger.forEnclosingClass();
/** /** {@link ChannelPromise} that informs {@link ProbingAction} if response has been received. */
* {@link ChannelPromise} that informs {@link ProbingAction} if response has been received. protected ChannelPromise finished;
*/
private ChannelPromise finished;
/** /** Returns initialized {@link ChannelPromise} to {@link ProbingAction}. */
* Returns initialized {@link ChannelPromise} to {@link ProbingAction}.
*/
public ChannelFuture getFinishedFuture() { public ChannelFuture getFinishedFuture() {
return finished; return finished;
} }
/** /** Initializes {@link ChannelPromise} */
* Initializes {@link ChannelPromise}
*/
@Override @Override
public void handlerAdded(ChannelHandlerContext ctx) { public void handlerAdded(ChannelHandlerContext ctx) {
//Once handler is added to channel pipeline, initialize channel and future for this handler // Once handler is added to channel pipeline, initialize channel and future for this handler
finished = ctx.newPromise(); finished = ctx.newPromise();
} }
/** /** Marks {@link ChannelPromise} as success */
* Marks {@link ChannelPromise} as success
*/
@Override @Override
public void channelRead0(ChannelHandlerContext ctx, InboundMessageType inboundMessage) public void channelRead0(ChannelHandlerContext ctx, InboundMessageType inboundMessage)
throws FailureException, UndeterminedStateException { throws FailureException, UndeterminedStateException {
@ -84,28 +74,28 @@ public abstract class ActionHandler extends SimpleChannelInboundHandler<InboundM
*/ */
@Override @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
logger.atWarning().withCause(cause).log(String.format( logger.atWarning().withCause(cause).log(
String.format(
"Attempted Action was unsuccessful with channel: %s, having pipeline: %s", "Attempted Action was unsuccessful with channel: %s, having pipeline: %s",
ctx.channel().toString(), ctx.channel().toString(), ctx.channel().pipeline().toString()));
ctx.channel().pipeline().toString()));
if (cause instanceof FailureException) { if (cause instanceof FailureException) {
//On FailureException, we know the response is a failure. // On FailureException, we know the response is a failure.
//Since it wasn't a success, we still want to log to see what caused the FAILURE // Since it wasn't a success, we still want to log to see what caused the FAILURE
logger.atInfo().log(cause.getMessage()); logger.atInfo().log(cause.getMessage());
//As always, inform the ProbingStep that we successfully completed this action // As always, inform the ProbingStep that we successfully completed this action
ChannelFuture unusedFuture = finished.setFailure(cause); ChannelFuture unusedFuture = finished.setFailure(cause);
} else { } else {
//On UndeterminedStateException, we know the response type is an error. // On UndeterminedStateException, we know the response type is an error.
//Since it wasn't a success, we still log what caused the ERROR // Since it wasn't a success, we still log what caused the ERROR
logger.atWarning().log(cause.getMessage()); logger.atWarning().log(cause.getMessage());
ChannelFuture unusedFuture = finished.setFailure(cause); ChannelFuture unusedFuture = finished.setFailure(cause);
//As this was an ERROR in performing the action, we must close the channel // As this was an ERROR in performing the action, we must close the channel
ChannelFuture closedFuture = ctx.channel().close(); ChannelFuture closedFuture = ctx.channel().close();
closedFuture.addListener(f -> logger.atInfo().log("Unsuccessful channel connection closed")); closedFuture.addListener(f -> logger.atInfo().log("Unsuccessful channel connection closed"));
} }

View file

@ -0,0 +1,54 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// 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.
package google.registry.monitoring.blackbox.handlers;
import google.registry.monitoring.blackbox.exceptions.FailureException;
import google.registry.monitoring.blackbox.exceptions.UndeterminedStateException;
import google.registry.monitoring.blackbox.messages.EppResponseMessage;
import google.registry.monitoring.blackbox.messages.InboundMessageType;
import io.netty.channel.ChannelHandlerContext;
import javax.inject.Inject;
/**
* Subclass of {@link ActionHandler} that deals with the Epp Sequence
*
* <p>Main purpose is to verify {@link EppResponseMessage} received is valid. If not it throws the
* requisite error which is dealt with by the parent {@link ActionHandler}
*/
public class EppActionHandler extends ActionHandler {
@Inject
public EppActionHandler() {}
/**
* Decodes the received response to ensure that it is what we expect and resets future in case
* {@link EppActionHandler} is reused.
*
* @throws FailureException if we receive a failed response from the server
*/
@Override
public void channelRead0(ChannelHandlerContext ctx, InboundMessageType msg)
throws FailureException, UndeterminedStateException {
EppResponseMessage response = (EppResponseMessage) msg;
// Based on the expected response type, will throw ResponseFailure if we don't receive a
// successful EPP response.
response.verify();
super.channelRead0(ctx, msg);
// Reset future as there is potential to reuse same ActionHandler for a different ProbingAction
finished = ctx.channel().newPromise();
}
}

View file

@ -0,0 +1,83 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// 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.
package google.registry.monitoring.blackbox.handlers;
import com.google.common.flogger.FluentLogger;
import google.registry.monitoring.blackbox.exceptions.FailureException;
import google.registry.monitoring.blackbox.messages.EppRequestMessage;
import google.registry.monitoring.blackbox.messages.EppResponseMessage;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import javax.inject.Inject;
import javax.inject.Named;
/**
* {@link io.netty.channel.ChannelHandler} that converts inbound {@link ByteBuf} to custom type
* {@link EppResponseMessage} and similarly converts the outbound {@link EppRequestMessage} to a
* {@link ByteBuf}. Always comes right before {@link EppActionHandler} in channel pipeline.
*/
public class EppMessageHandler extends ChannelDuplexHandler {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
/**
* Corresponding {@link EppResponseMessage} that we expect to receive back from server.
*
* <p>We always expect the first response to be an {@link EppResponseMessage.Greeting}.
*/
private EppResponseMessage response;
@Inject
public EppMessageHandler(@Named("greeting") EppResponseMessage greetingResponse) {
response = greetingResponse;
}
/** Performs conversion to {@link ByteBuf}. */
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)
throws Exception {
// If the message is Hello, don't actually pass it on, just wait for server greeting.
// otherwise, we store what we expect a successful response to be
EppRequestMessage request = (EppRequestMessage) msg;
response = request.getExpectedResponse();
if (!response.name().equals("greeting")) {
// then we write the ByteBuf representation of the EPP message to the server
ChannelFuture unusedFuture = ctx.write(request.bytes(), promise);
}
}
/** Performs conversion from {@link ByteBuf} */
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws FailureException {
try {
// attempt to get response document from ByteBuf
ByteBuf buf = (ByteBuf) msg;
response.getDocument(buf);
logger.atInfo().log(response.toString());
} catch (FailureException e) {
// otherwise we log that it was unsuccessful and throw the requisite error
logger.atInfo().withCause(e).log("Failure in current step.");
throw e;
}
// pass response to the ActionHandler in the pipeline
ctx.fireChannelRead(response);
}
}

View file

@ -15,12 +15,12 @@
package google.registry.monitoring.blackbox.handlers; package google.registry.monitoring.blackbox.handlers;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static google.registry.monitoring.blackbox.ProbingAction.REMOTE_ADDRESS_KEY; import static google.registry.monitoring.blackbox.connection.ProbingAction.REMOTE_ADDRESS_KEY;
import static google.registry.monitoring.blackbox.Protocol.PROTOCOL_KEY; import static google.registry.monitoring.blackbox.connection.Protocol.PROTOCOL_KEY;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.common.flogger.FluentLogger; import com.google.common.flogger.FluentLogger;
import google.registry.monitoring.blackbox.Protocol; import google.registry.monitoring.blackbox.connection.Protocol;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler.Sharable; import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelInitializer;
@ -38,8 +38,8 @@ import javax.net.ssl.SSLParameters;
/** /**
* Adds a client side SSL handler to the channel pipeline. * Adds a client side SSL handler to the channel pipeline.
* *
* <p> Code is close to unchanged from {@link SslClientInitializer}</p> in proxy, but is modified * <p>Code is close to unchanged from {@link SslClientInitializer} in proxy, but is modified for
* for revised overall structure of connections, and to accomdate EPP connections </p> * revised overall structure of connections, and to accomdate EPP connections
* *
* <p>This <b>must</b> be the first handler provided for any handler provider list, if it is * <p>This <b>must</b> be the first handler provided for any handler provider list, if it is
* provided. The type parameter {@code C} is needed so that unit tests can construct this handler * provided. The type parameter {@code C} is needed so that unit tests can construct this handler
@ -56,16 +56,17 @@ public class SslClientInitializer<C extends Channel> extends ChannelInitializer<
private final Supplier<PrivateKey> privateKeySupplier; private final Supplier<PrivateKey> privateKeySupplier;
private final Supplier<X509Certificate[]> certificateSupplier; private final Supplier<X509Certificate[]> certificateSupplier;
public SslClientInitializer(SslProvider sslProvider) { public SslClientInitializer(SslProvider sslProvider) {
// null uses the system default trust store. // null uses the system default trust store.
//Used for WebWhois, so we don't care about privateKey and certificates, setting them to null // Used for WebWhois, so we don't care about privateKey and certificates, setting them to null
this(sslProvider, null, null, null); this(sslProvider, null, null, null);
} }
public SslClientInitializer(SslProvider sslProvider, Supplier<PrivateKey> privateKeySupplier, public SslClientInitializer(
SslProvider sslProvider,
Supplier<PrivateKey> privateKeySupplier,
Supplier<X509Certificate[]> certificateSupplier) { Supplier<X509Certificate[]> certificateSupplier) {
//We use the default trust store here as well, setting trustCertificates to null // We use the default trust store here as well, setting trustCertificates to null
this(sslProvider, null, privateKeySupplier, certificateSupplier); this(sslProvider, null, privateKeySupplier, certificateSupplier);
} }
@ -92,20 +93,17 @@ public class SslClientInitializer<C extends Channel> extends ChannelInitializer<
Protocol protocol = channel.attr(PROTOCOL_KEY).get(); Protocol protocol = channel.attr(PROTOCOL_KEY).get();
String host = channel.attr(REMOTE_ADDRESS_KEY).get(); String host = channel.attr(REMOTE_ADDRESS_KEY).get();
//Builds SslHandler from Protocol, and based on if we require a privateKey and certificate // Builds SslHandler from Protocol, and based on if we require a privateKey and certificate
checkNotNull(protocol, "Protocol is not set for channel: %s", channel); checkNotNull(protocol, "Protocol is not set for channel: %s", channel);
SslContextBuilder sslContextBuilder = SslContextBuilder sslContextBuilder =
SslContextBuilder.forClient() SslContextBuilder.forClient().sslProvider(sslProvider).trustManager(trustedCertificates);
.sslProvider(sslProvider)
.trustManager(trustedCertificates);
if (privateKeySupplier != null && certificateSupplier != null) { if (privateKeySupplier != null && certificateSupplier != null) {
sslContextBuilder = sslContextBuilder sslContextBuilder =
.keyManager(privateKeySupplier.get(), certificateSupplier.get()); sslContextBuilder.keyManager(privateKeySupplier.get(), certificateSupplier.get());
} }
SslHandler sslHandler = sslContextBuilder SslHandler sslHandler =
.build() sslContextBuilder.build().newHandler(channel.alloc(), host, protocol.port());
.newHandler(channel.alloc(), host, protocol.port());
// Enable hostname verification. // Enable hostname verification.
SSLEngine sslEngine = sslHandler.engine(); SSLEngine sslEngine = sslHandler.engine();
@ -116,4 +114,3 @@ public class SslClientInitializer<C extends Channel> extends ChannelInitializer<
channel.pipeline().addLast(sslHandler); channel.pipeline().addLast(sslHandler);
} }
} }

View file

@ -15,17 +15,17 @@
package google.registry.monitoring.blackbox.handlers; package google.registry.monitoring.blackbox.handlers;
import com.google.common.flogger.FluentLogger; import com.google.common.flogger.FluentLogger;
import google.registry.monitoring.blackbox.ProbingAction; import google.registry.monitoring.blackbox.connection.ProbingAction;
import google.registry.monitoring.blackbox.Protocol; import google.registry.monitoring.blackbox.connection.Protocol;
import google.registry.monitoring.blackbox.WebWhoisModule.HttpWhoisProtocol;
import google.registry.monitoring.blackbox.WebWhoisModule.HttpsWhoisProtocol;
import google.registry.monitoring.blackbox.WebWhoisModule.WebWhoisProtocol;
import google.registry.monitoring.blackbox.exceptions.ConnectionException; import google.registry.monitoring.blackbox.exceptions.ConnectionException;
import google.registry.monitoring.blackbox.exceptions.FailureException; import google.registry.monitoring.blackbox.exceptions.FailureException;
import google.registry.monitoring.blackbox.exceptions.UndeterminedStateException; import google.registry.monitoring.blackbox.exceptions.UndeterminedStateException;
import google.registry.monitoring.blackbox.messages.HttpRequestMessage; import google.registry.monitoring.blackbox.messages.HttpRequestMessage;
import google.registry.monitoring.blackbox.messages.HttpResponseMessage; import google.registry.monitoring.blackbox.messages.HttpResponseMessage;
import google.registry.monitoring.blackbox.messages.InboundMessageType; import google.registry.monitoring.blackbox.messages.InboundMessageType;
import google.registry.monitoring.blackbox.modules.WebWhoisModule.HttpWhoisProtocol;
import google.registry.monitoring.blackbox.modules.WebWhoisModule.HttpsWhoisProtocol;
import google.registry.monitoring.blackbox.modules.WebWhoisModule.WebWhoisProtocol;
import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
@ -38,9 +38,9 @@ import org.joda.time.Duration;
/** /**
* Subclass of {@link ActionHandler} that deals with the WebWhois Sequence * Subclass of {@link ActionHandler} that deals with the WebWhois Sequence
* *
* <p> Main purpose is to verify {@link HttpResponseMessage} received is valid. If the response * <p>Main purpose is to verify {@link HttpResponseMessage} received is valid. If the response
* implies a redirection it follows the redirection until either an Error Response is received, or * implies a redirection it follows the redirection until either an Error Response is received, or
* {@link HttpResponseStatus.OK} is received</p> * {@link HttpResponseStatus.OK} is received
*/ */
public class WebWhoisActionHandler extends ActionHandler { public class WebWhoisActionHandler extends ActionHandler {
@ -48,24 +48,16 @@ public class WebWhoisActionHandler extends ActionHandler {
/** Dagger injected components necessary for redirect responses: */ /** Dagger injected components necessary for redirect responses: */
/** /** {@link Bootstrap} necessary for remaking connection on redirect response. */
* {@link Bootstrap} necessary for remaking connection on redirect response.
*/
private final Bootstrap bootstrap; private final Bootstrap bootstrap;
/** /** {@link Protocol} for when redirected to http endpoint. */
* {@link Protocol} for when redirected to http endpoint.
*/
private final Protocol httpWhoisProtocol; private final Protocol httpWhoisProtocol;
/** /** {@link Protocol} for when redirected to https endpoint. */
* {@link Protocol} for when redirected to https endpoint.
*/
private final Protocol httpsWhoisProtocol; private final Protocol httpsWhoisProtocol;
/** /** {@link HttpRequestMessage} that represents default GET message to be sent on redirect. */
* {@link HttpRequestMessage} that represents default GET message to be sent on redirect.
*/
private final HttpRequestMessage requestMessage; private final HttpRequestMessage requestMessage;
@Inject @Inject
@ -81,7 +73,6 @@ public class WebWhoisActionHandler extends ActionHandler {
this.requestMessage = requestMessage; this.requestMessage = requestMessage;
} }
/** /**
* After receiving {@link HttpResponseMessage}, either notes success and marks future as finished, * After receiving {@link HttpResponseMessage}, either notes success and marks future as finished,
* notes an error in the received {@link URL} and throws a {@link ConnectionException}, received a * notes an error in the received {@link URL} and throws a {@link ConnectionException}, received a
@ -98,32 +89,33 @@ public class WebWhoisActionHandler extends ActionHandler {
logger.atInfo().log("Received Successful HttpResponseStatus"); logger.atInfo().log("Received Successful HttpResponseStatus");
logger.atInfo().log("Response Received: " + response); logger.atInfo().log("Response Received: " + response);
//On success, we always pass message to ActionHandler's channelRead0 method. // On success, we always pass message to ActionHandler's channelRead0 method.
super.channelRead0(ctx, msg); super.channelRead0(ctx, msg);
} else if (response.status().equals(HttpResponseStatus.MOVED_PERMANENTLY) } else if (response.status().equals(HttpResponseStatus.MOVED_PERMANENTLY)
|| response.status().equals(HttpResponseStatus.FOUND)) { || response.status().equals(HttpResponseStatus.FOUND)) {
//TODO - Fix checker to better determine when we have encountered a redirection response. // TODO - Fix checker to better determine when we have encountered a redirection response.
//Obtain url to be redirected to // Obtain url to be redirected to
URL url; URL url;
try { try {
url = new URL(response.headers().get("Location")); url = new URL(response.headers().get("Location"));
} catch (MalformedURLException e) { } catch (MalformedURLException e) {
//in case of error, log it, and let ActionHandler's exceptionThrown method deal with it // in case of error, log it, and let ActionHandler's exceptionThrown method deal with it
throw new FailureException( throw new FailureException(
"Redirected Location was invalid. Given Location was: " + response.headers() "Redirected Location was invalid. Given Location was: "
.get("Location")); + response.headers().get("Location"));
} }
//From url, extract new host, port, and path // From url, extract new host, port, and path
String newHost = url.getHost(); String newHost = url.getHost();
String newPath = url.getPath(); String newPath = url.getPath();
logger.atInfo().log(String logger.atInfo().log(
.format("Redirected to %s with host: %s, port: %d, and path: %s", url, newHost, String.format(
url.getDefaultPort(), newPath)); "Redirected to %s with host: %s, port: %d, and path: %s",
url, newHost, url.getDefaultPort(), newPath));
//Construct new Protocol to reflect redirected host, path, and port // Construct new Protocol to reflect redirected host, path, and port
Protocol newProtocol; Protocol newProtocol;
if (url.getProtocol().equals(httpWhoisProtocol.name())) { if (url.getProtocol().equals(httpWhoisProtocol.name())) {
newProtocol = httpWhoisProtocol; newProtocol = httpWhoisProtocol;
@ -134,11 +126,13 @@ public class WebWhoisActionHandler extends ActionHandler {
"Redirection Location port was invalid. Given protocol name was: " + url.getProtocol()); "Redirection Location port was invalid. Given protocol name was: " + url.getProtocol());
} }
//Obtain HttpRequestMessage with modified headers to reflect new host and path. // Obtain HttpRequestMessage with modified headers to reflect new host and path.
HttpRequestMessage httpRequest = requestMessage.modifyMessage(newHost, newPath); HttpRequestMessage httpRequest = requestMessage.modifyMessage(newHost, newPath);
//Create new probingAction that takes in the new Protocol and HttpRequestMessage with no delay // Create new probingAction that takes in the new Protocol and HttpRequestMessage with no
ProbingAction redirectedAction = ProbingAction.builder() // delay
ProbingAction redirectedAction =
ProbingAction.builder()
.setBootstrap(bootstrap) .setBootstrap(bootstrap)
.setProtocol(newProtocol) .setProtocol(newProtocol)
.setOutboundMessage(httpRequest) .setOutboundMessage(httpRequest)
@ -146,7 +140,7 @@ public class WebWhoisActionHandler extends ActionHandler {
.setHost(newHost) .setHost(newHost)
.build(); .build();
//close this channel as we no longer need it // close this channel as we no longer need it
ChannelFuture future = ctx.close(); ChannelFuture future = ctx.close();
future.addListener( future.addListener(
f -> { f -> {
@ -156,13 +150,14 @@ public class WebWhoisActionHandler extends ActionHandler {
logger.atWarning().log("Channel was unsuccessfully closed."); logger.atWarning().log("Channel was unsuccessfully closed.");
} }
//Once channel is closed, establish new connection to redirected host, and repeat // Once channel is closed, establish new connection to redirected host, and repeat
// same actions // same actions
ChannelFuture secondFuture = redirectedAction.call(); ChannelFuture secondFuture = redirectedAction.call();
//Once we have a successful call, set original ChannelPromise as success to tell // Once we have a successful call, set original ChannelPromise as success to tell
// ProbingStep we can move on // ProbingStep we can move on
secondFuture.addListener(f2 -> { secondFuture.addListener(
f2 -> {
if (f2.isSuccess()) { if (f2.isSuccess()) {
super.channelRead0(ctx, msg); super.channelRead0(ctx, msg);
} else { } else {
@ -172,18 +167,12 @@ public class WebWhoisActionHandler extends ActionHandler {
throw new UndeterminedStateException(f2.cause()); throw new UndeterminedStateException(f2.cause());
} }
} }
}); });
} });
);
} else { } else {
//Add in metrics Handling that informs MetricsCollector the response was a FAILURE // Add in metrics Handling that informs MetricsCollector the response was a FAILURE
logger.atWarning().log(String.format("Received unexpected response: %s", response.status())); logger.atWarning().log(String.format("Received unexpected response: %s", response.status()));
throw new FailureException("Response received from remote site was: " + response.status()); throw new FailureException("Response received from remote site was: " + response.status());
} }
} }
} }

View file

@ -31,12 +31,9 @@ import javax.inject.Inject;
public class WebWhoisMessageHandler extends ChannelDuplexHandler { public class WebWhoisMessageHandler extends ChannelDuplexHandler {
@Inject @Inject
public WebWhoisMessageHandler() { public WebWhoisMessageHandler() {}
}
/** /** Retains {@link HttpRequestMessage} and calls super write method. */
* Retains {@link HttpRequestMessage} and calls super write method.
*/
@Override @Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)
throws Exception { throws Exception {
@ -45,7 +42,6 @@ public class WebWhoisMessageHandler extends ChannelDuplexHandler {
super.write(ctx, request, promise); super.write(ctx, request, promise);
} }
/** /**
* Converts {@link FullHttpResponse} to {@link HttpResponseMessage}, so it is an {@link * Converts {@link FullHttpResponse} to {@link HttpResponseMessage}, so it is an {@link
* InboundMessageType} instance. * InboundMessageType} instance.

View file

@ -0,0 +1,454 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// 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.
package google.registry.monitoring.blackbox.messages;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Strings.isNullOrEmpty;
import static google.registry.util.ResourceUtils.readResourceBytes;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.flogger.FluentLogger;
import google.registry.monitoring.blackbox.exceptions.EppClientException;
import google.registry.monitoring.blackbox.exceptions.FailureException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
/**
* Superclass of {@link EppRequestMessage} and {@link EppResponseMessage} that represents skeleton
* of any kind of EPP message, whether inbound or outbound.
*
* <p>NOTE: Most static methods are copied over from
* //java/com/google/domain/registry/monitoring/blackbox/EppHelper.java
*
* <p>Houses number of static methods for use of conversion between String and bytes to {@link
* Document} type, which represents an XML Document, the type of which is used for EPP messages.
*/
public class EppMessage {
/** Key that allows for substitution of {@code domainName} to xml template. */
public static final String DOMAIN_KEY = "//domainns:name";
/** Key that allows for substitution of epp user id to xml template. */
public static final String CLIENT_ID_KEY = "//eppns:clID";
/** Key that allows for substitution of epp password to xml template. */
public static final String CLIENT_PASSWORD_KEY = "//eppns:pw";
/** Key that allows for substitution of{@code clTrid} to xml template. */
public static final String CLIENT_TRID_KEY = "//eppns:clTRID";
/** Key that allows for substitution of{@code svTrid} to xml template. */
public static final String SERVER_TRID_KEY = "//eppns:svTRID";
/**
* Expression that expresses a result code in the {@link EppResponseMessage} that means success.
*/
@VisibleForTesting
public static final String XPASS_EXPRESSION =
String.format("//eppns:result[@code>='%s'][@code<'%s']", 1000, 2000);
/**
* Expression that expresses a result code in the {@link EppResponseMessage} that means failure.
*/
@VisibleForTesting
public static final String XFAIL_EXPRESSION =
String.format("//eppns:result[@code>='%s'][@code<'%s']", 2000, 3000);
// "Security" errors from RFC 5730, plus the error we get when we end
// up no longer logged (see b/28196510).
// 2002 "Command use error"
// 2200 "Authentication error"
// 2201 "Authorization error"
// 2202 "Invalid authorization information"
@VisibleForTesting
static final String AUTHENTICATION_ERROR =
"//eppns:epp/eppns:response/eppns:result[@code!='2002' and @code!='2200' "
+ "and @code!='2201' and @code!='2202']";
static final String VALID_SLD_LABEL_REGEX;
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
/**
* Static variables necessary for static methods that serve as tools for {@link Document}
* creation, conversion, and verification.
*/
private static final DocumentBuilderFactory docBuilderFactory =
DocumentBuilderFactory.newInstance();
private static final XPath xpath;
private static final Schema eppSchema;
// As per RFC 1035 section 2.3.4 http://tools.ietf.org/html/rfc1035#page-10 and updated by
// http://tools.ietf.org/html/rfc1123#page-13 which suggests a domain part length
// of 255 SHOULD be supported, so we are permitting something close to that, but reserve
// at least two characters for a TLD and a full stop (".") character.
private static final int MAX_DOMAIN_PART_LENGTH = 252;
private static final int MAX_SLD_DOMAIN_LABEL_LENGTH = 255;
private static final String VALID_DOMAIN_PART_REGEX;
private static final String VALID_TLD_PART_REGEX;
/** Standard EPP header number of bytes (size of int). */
protected static int HEADER_LENGTH = 4;
static {
// tld label part may contain a dot and end with a dot, and must
// start and end with 0-9 or a-zA-Z but may contain any number of
// dashes (minus signs "-") in between (even consecutive)
VALID_TLD_PART_REGEX =
String.format(
"\\p{Alnum}[-\\p{Alnum}]{0,%s}\\.{0,1}[-\\p{Alnum}]{0,%s}\\p{Alnum}",
(MAX_DOMAIN_PART_LENGTH - 2), (MAX_DOMAIN_PART_LENGTH - 2));
// domain label part ("left" of the tld) must start and end with 0-9 or a-zA-Z but
// may contain any number of dashes (minus signs "-") in between (even consecutive)
VALID_DOMAIN_PART_REGEX =
String.format("\\p{Alnum}[-\\p{Alnum}]{0,%s}\\p{Alnum}", (MAX_DOMAIN_PART_LENGTH - 2));
VALID_SLD_LABEL_REGEX =
String.format(
"(?=.{4,%s})%s\\.%s(\\.)*",
MAX_SLD_DOMAIN_LABEL_LENGTH, VALID_DOMAIN_PART_REGEX, VALID_TLD_PART_REGEX);
xpath = XPathFactory.newInstance().newXPath();
xpath.setNamespaceContext(new EppNamespaceContext());
docBuilderFactory.setNamespaceAware(true);
String path = "./xsd/";
StreamSource[] sources;
try {
sources =
new StreamSource[] {
new StreamSource(readResource(path + "eppcom.xsd")),
new StreamSource(readResource(path + "epp.xsd")),
new StreamSource(readResource(path + "host.xsd")),
new StreamSource(readResource(path + "contact.xsd")),
new StreamSource(readResource(path + "domain.xsd")),
new StreamSource(readResource(path + "rgp.xsd")),
new StreamSource(readResource(path + "mark.xsd")),
new StreamSource(readResource(path + "dsig.xsd")),
new StreamSource(readResource(path + "smd.xsd")),
new StreamSource(readResource(path + "launch.xsd")),
};
SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
eppSchema = schemaFactory.newSchema(sources);
} catch (SAXException | IOException e) {
throw new ExceptionInInitializerError(e);
}
}
/**
* {@link Document} that represents the actual XML document sent inbound or outbound through
* channel pipeline.
*/
protected Document message;
/** Helper method that reads resource existing in same package as {@link EppMessage} class. */
private static InputStream readResource(String filename) throws IOException {
return readResourceBytes(EppMessage.class, filename).openStream();
}
/**
* Validate an EPP XML document against the set of XSD files that make up the EPP XML Schema.
*
* <p>Note that the document must have the namespace attributes set where expected (i.e. must be
* built using a DocumentBuilder with namespace awareness set if using a DocumentBuilder).
*
* @param xml an XML Document to validate
* @throws SAXException if the document is not valid
*/
public static void eppValidate(Document xml) throws SAXException, IOException {
Validator schemaValidator = eppSchema.newValidator();
schemaValidator.validate(new DOMSource(xml));
}
/**
* Verify an XML Document as an EPP Response using the provided XPath expressions.
*
* <p>This will first validate the document against the EPP schema, then run through the list of
* xpath expressions -- so those need only look for specific EPP elements + values.
*
* @param xml the XML Document containing the EPP reponse to verify
* @param expressions a list of XPath expressions to query the document with.
* @param validate a boolean flag to control if schema validation occurs (useful for testing)
* @throws IOException if InputStream throws one
* @throws EppClientException if the EPP response cannot be read, parsed, or doesn't containing
* matching data specified in expressions
*/
protected static void verifyEppResponse(Document xml, List<String> expressions, boolean validate)
throws FailureException {
if (validate) {
try {
eppValidate(xml);
} catch (SAXException | IOException e) {
throw new FailureException(e);
}
}
try {
for (String exp : expressions) {
NodeList nodes = (NodeList) xpath.evaluate(exp, xml, XPathConstants.NODESET);
if (nodes.getLength() == 0) {
throw new FailureException("invalid EPP response. failed expression " + exp);
}
}
} catch (XPathExpressionException e) {
throw new FailureException(e);
}
}
/**
* A helper method to extract a value from an element in an XML document.
*
* @return the text value for the element, or null is the element is not found
*/
public static String getElementValue(Document xml, String expression) {
try {
return (String) xpath.evaluate(expression, xml, XPathConstants.STRING);
} catch (XPathExpressionException e) {
logger.atSevere().withCause(e).log("Bad expression: %s", expression);
return null;
}
}
/**
* A helper method to transform an XML Document to a string. - e.g. a returned string might look
* like the following for a Document with a root element of "foo" that has a child element of
* "bar" which has text of "baz":<br>
* {@code '<foo><bar>baz</bar></foo>'}
*
* @param xml the Document to transform
* @return the resulting string or {@code null} if {@code xml} is {@code null}.
* @throws EppClientException if the transform fails
*/
@Nullable
public static String xmlDocToString(@Nullable Document xml) throws EppClientException {
if (xml == null) {
return null;
}
try {
Transformer transformer = TransformerFactory.newInstance().newTransformer();
StreamResult result = new StreamResult(new StringWriter());
DOMSource source = new DOMSource(xml);
transformer.transform(source, result);
return result.getWriter().toString();
} catch (TransformerException e) {
throw new EppClientException(e);
}
}
/**
* A helper method to transform an XML Document to a byte array using the XML Encoding when
* converting from a String (see xmlDocToString).
*
* @param xml the Document to transform
* @return the resulting byte array.
* @throws EppClientException if the transform fails
*/
public static byte[] xmlDocToByteArray(Document xml) throws EppClientException {
try {
Transformer transformer = TransformerFactory.newInstance().newTransformer();
StreamResult result = new StreamResult(new StringWriter());
DOMSource source = new DOMSource(xml);
transformer.transform(source, result);
String resultString = result.getWriter().toString();
if (isNullOrEmpty(resultString)) {
throw new EppClientException("unknown error converting Document to intermediate string");
}
String encoding = xml.getXmlEncoding();
// this is actually not a problem since we can just use the default
if (encoding == null) {
encoding = Charset.defaultCharset().name();
}
return resultString.getBytes(encoding);
} catch (TransformerException | UnsupportedEncodingException e) {
throw new EppClientException(e);
}
}
/**
* A helper method to transform an byte array to an XML {@link Document} using {@code
* docBuilderFactory}
*
* @param responseBuffer the byte array to transform
* @return the resulting Document
* @throws EppClientException if the transform fails
*/
public static Document byteArrayToXmlDoc(byte[] responseBuffer) throws FailureException {
Document xml;
try {
DocumentBuilder builder = docBuilderFactory.newDocumentBuilder();
ByteArrayInputStream byteStream = new ByteArrayInputStream(responseBuffer);
xml = builder.parse(byteStream);
} catch (ParserConfigurationException | SAXException | IOException e) {
throw new FailureException(e);
}
return xml;
}
/**
* Reads one of a set of EPP templates (included as resources in our jar) and finds nodes using an
* xpath expression, then replaces the node value of the first child, returning the transformed
* XML as a Document.
*
* <p>E.g. to replace the value "@@CLTRID@@" in the {@code <clTRID>} node with a client
* transaction ID, use the mapping {@code <"//domainns:clTRID", "AAA-123-BBBB">} (or whatever the
* ID is).
*
* @param template the relative (unqualified) name of the template file to use
* @param replacements a map of strings to replace in the template keyed by the xpath expression
* to use to find the nodes to operate on with the value being the text to use as the
* replacement
* @return the transformed EPP document
* @throws IOException if the template cannot be read
* @throws EppClientException if there are issues parsing the template or evaluating the xpath
* expression, or if the resulting document is not valid EPP
* @throws IllegalArgumentException if the xpath expression query yields anything other than an
* Element node type
*/
public static Document getEppDocFromTemplate(String template, Map<String, String> replacements)
throws IOException, EppClientException {
Document xmlDoc;
try (InputStream is = readResource(template)) {
DocumentBuilder builder = docBuilderFactory.newDocumentBuilder();
xmlDoc = builder.parse(is);
for (String key : replacements.keySet()) {
NodeList nodes = (NodeList) xpath.evaluate(key, xmlDoc, XPathConstants.NODESET);
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
if (node.getNodeType() != Node.ELEMENT_NODE) {
throw new IllegalArgumentException(
String.format(
"xpath expression (%s) must result in Element nodes, got %s",
key, node.getNodeType()));
}
node.getFirstChild().setNodeValue(replacements.get(key));
}
}
eppValidate(xmlDoc);
} catch (SAXException | XPathExpressionException | ParserConfigurationException e) {
throw new EppClientException(e);
}
return xmlDoc;
}
@Nullable
public String getElementValue(String expression) {
try {
return (String) xpath.evaluate(expression, message, XPathConstants.STRING);
} catch (XPathExpressionException e) {
logger.atSevere().withCause(e).log("Bad expression: %s", expression);
return null;
}
}
@Override
public String toString() {
try {
return xmlDocToString(message);
} catch (EppClientException e) {
return "No Message Found";
}
}
/**
* Implements the {@link NamespaceContext} interface and adds an EPP namespace URI (prefix eppns).
*/
static class EppNamespaceContext implements NamespaceContext {
final Map<String, String> nsPrefixMap = new HashMap<>();
final Map<String, Set<String>> nsUriMap = new HashMap<>();
public EppNamespaceContext() {
try {
addNamespace(XMLConstants.XML_NS_PREFIX, XMLConstants.XML_NS_URI);
addNamespace(XMLConstants.XMLNS_ATTRIBUTE, XMLConstants.XMLNS_ATTRIBUTE_NS_URI);
addNamespace("eppns", "urn:ietf:params:xml:ns:epp-1.0");
addNamespace("contactns", "urn:ietf:params:xml:ns:contact-1.0");
addNamespace("domainns", "urn:ietf:params:xml:ns:domain-1.0");
addNamespace("hostns", "urn:ietf:params:xml:ns:host-1.0");
addNamespace("launchns", "urn:ietf:params:xml:ns:launch-1.0");
} catch (Exception e) {
// this should never happen here but if it does, we just turn it into a runtime exception
throw new IllegalArgumentException(e);
}
}
void addNamespace(String prefix, String namespaceURI) throws Exception {
checkArgument(!isNullOrEmpty(prefix), "prefix");
checkArgument(!isNullOrEmpty(namespaceURI), "namespaceURI");
if (nsPrefixMap.containsKey(prefix)) {
throw new RuntimeException("key already exists: " + prefix);
}
nsPrefixMap.put(prefix, namespaceURI);
if (nsUriMap.containsKey(namespaceURI)) {
nsUriMap.get(namespaceURI).add(prefix);
} else {
Set<String> prefixSet = new HashSet<>();
prefixSet.add(prefix);
nsUriMap.put(namespaceURI, prefixSet);
}
}
@Override
public String getNamespaceURI(String prefix) {
checkArgument(!isNullOrEmpty(prefix), "prefix");
return nsPrefixMap.getOrDefault(prefix, XMLConstants.NULL_NS_URI);
}
@Override
public String getPrefix(String namespaceURI) {
checkArgument(!isNullOrEmpty(namespaceURI), "namespaceURI");
return getPrefixes(namespaceURI).next();
}
@Override
public Iterator<String> getPrefixes(String namespaceURI) {
checkArgument(!isNullOrEmpty(namespaceURI), "namespaceURI");
if (nsUriMap.containsKey(namespaceURI)) {
return nsUriMap.get(namespaceURI).iterator();
} else {
return Collections.emptyIterator();
}
}
}
}

View file

@ -0,0 +1,132 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// 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.
package google.registry.monitoring.blackbox.messages;
import com.google.common.collect.ImmutableMap;
import google.registry.monitoring.blackbox.exceptions.EppClientException;
import google.registry.monitoring.blackbox.exceptions.UndeterminedStateException;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import java.io.IOException;
import java.util.Map;
import java.util.function.BiFunction;
/**
* {@link EppMessage} subclass that implements {@link OutboundMessageType}, which represents an
* outbound Epp message.
*
* <p>In modifying the {@code getReplacements} field of {@link EppRequestMessage} and the template,
* we can represent the the 5 basic EPP commands we are attempting to probe. The original 5 are:
* LOGIN, CREATE, CHECK, DELETE, LOGOUT.
*
* <p>In turn, we equivalently create 10 different EPP commands probed: Hello - checks for Greeting
* response, Login expecting success, Login expecting Failure, Create expecting Success, Create
* expecting Failure, Check that the domain exists, Check that the domain doesn't exist, Delete
* expecting Success, Delete expecting Failure, and Logout expecting Success.
*
* <p>The main difference is that we added a hello command that simply waits for the server to send
* a greeting, then moves on to the Login action.
*
* <p>Stores a clTRID and domainName which is modified each time the token calls {@code
* modifyMessage}. These will also modify the EPP request sent to the server.
*/
public class EppRequestMessage extends EppMessage implements OutboundMessageType {
/** Corresponding {@link EppResponseMessage} that we expect to receive on a successful request. */
private EppResponseMessage expectedResponse;
/** Filename for template of current request type. */
private String template;
/**
* {@link ImmutableMap} of replacements that is indicative of each type of {@link
* EppRequestMessage}
*/
private BiFunction<String, String, Map<String, String>> getReplacements;
/**
* Private constructor for {@link EppRequestMessage} that its subclasses use for instantiation.
*/
public EppRequestMessage(
EppResponseMessage expectedResponse,
String template,
BiFunction<String, String, Map<String, String>> getReplacements) {
this.expectedResponse = expectedResponse;
this.template = template;
this.getReplacements = getReplacements;
}
/**
* From the input {@code clTrid} and {@code domainName}, modifies the template EPP XML document
* and the {@code expectedResponse} to reflect new parameters.
*
* @param args - should always be two Strings: The first one is {@code clTrid} and the second one
* is {@code domainName}.
* @return the current {@link EppRequestMessage} instance.
* @throws EppClientException - On the occasion that the prober can't appropriately modify the EPP
* XML document, the blame falls on the prober, not the server, so it throws an {@link
* EppClientException}, which is a subclass of the {@link UndeterminedStateException}.
*/
@Override
public EppRequestMessage modifyMessage(String... args) throws EppClientException {
// First argument should always be clTRID.
String clTrid = args[0];
// Second argument should always be domainName.
String domainName = args[1];
if (template != null) {
// Checks if we are sending an actual EPP request to the server (if template is null, than
// we just expect a response.
try {
message = getEppDocFromTemplate(template, getReplacements.apply(clTrid, domainName));
} catch (IOException e) {
throw new EppClientException(e);
}
}
// Update the EppResponseMessage associated with this EppRequestMessage to reflect changed
// parameters on this step.
expectedResponse.updateInformation(clTrid, domainName);
return this;
}
/**
* Converts the current {@link org.w3c.dom.Document} message to a {@link ByteBuf} with the
* requisite bytes
*
* @return the {@link ByteBuf} instance that stores the bytes representing the requisite EPP
* Request
* @throws EppClientException- On the occasion that the prober can't appropriately convert the EPP
* XML document to a {@link ByteBuf}, the blame falls on the prober, not the server, so it
* throws an {@link EppClientException}, which is a subclass of the {@link
* UndeterminedStateException}.
*/
public ByteBuf bytes() throws EppClientException {
// obtain byte array of our modified xml document
byte[] bytestream = xmlDocToByteArray(message);
// Move bytes to a ByteBuf.
ByteBuf buf = Unpooled.buffer(bytestream.length);
buf.writeBytes(bytestream);
return buf;
}
/** */
public EppResponseMessage getExpectedResponse() {
return expectedResponse;
}
}

View file

@ -0,0 +1,87 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// 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.
package google.registry.monitoring.blackbox.messages;
import google.registry.monitoring.blackbox.exceptions.FailureException;
import io.netty.buffer.ByteBuf;
import java.util.List;
import java.util.function.BiFunction;
/**
* {@link EppMessage} subclass that implements {@link InboundMessageType}, which represents an
* inbound EPP message and serves to verify the response received from the server.
*
* <p>There are 4 created types of this {@link EppRequestMessage}, which represent the expected
* successful response types. Their names are: success, failure greeting, domainExists,
* domainNotExists. Success implies a response declaring the command was completed successfully.
* Failure implies a response declaring the command was not completed successfully. Greeting is the
* standard initial response the server sends after a connection is established. DomainExists is a
* response to a Check request saying the domain exists on the server. DomainNotExists is a response
* that essentially says the opposite.
*
* <p>Stores an expected clTRID and domainName which are the ones used by the {@link
* EppRequestMessage} pointing to this {@link EppRequestMessage}.
*
* <p>From the {@link ByteBuf} input, stores the corresponding {@link org.w3c.dom.Document}
* represented and to be validated.
*/
public class EppResponseMessage extends EppMessage implements InboundMessageType {
/**
* Specifies type of {@link EppResponseMessage}.
*
* <p>All possible names are: success, failure, domainExists, domainNotExists, greeting.
*/
private final String name;
/** Lambda expression that returns a checkList from input clTRID and domain Strings. */
private final BiFunction<String, String, List<String>> getCheckList;
/** Domain name we expect to receive in current response. */
private String expectedDomainName;
/** ClTRID we expect to receive in current response. */
private String expectedClTrid;
/** Verifies that the response recorded is what we expect from the request sent. */
public void verify() throws FailureException {
verifyEppResponse(message, getCheckList.apply(expectedClTrid, expectedDomainName), true);
}
/** Extracts {@link org.w3c.dom.Document} from the {@link ByteBuf} input. */
public void getDocument(ByteBuf buf) throws FailureException {
// Convert ByteBuf to byte array.
byte[] response = new byte[buf.readableBytes()];
buf.readBytes(response);
// Convert byte array to Document.
message = byteArrayToXmlDoc(response);
}
/** Updates {@code expectedClTrid} and {@code expectedDomainName} fields. */
void updateInformation(String expectedClTrid, String expectedDomainName) {
this.expectedClTrid = expectedClTrid;
this.expectedDomainName = expectedDomainName;
}
public EppResponseMessage(String name, BiFunction<String, String, List<String>> getCheckList) {
this.name = name;
this.getCheckList = getCheckList;
}
public String name() {
return name;
}
}

View file

@ -25,9 +25,9 @@ import javax.inject.Inject;
/** /**
* {@link OutboundMessageType} subtype that acts identically to {@link DefaultFullHttpRequest}. * {@link OutboundMessageType} subtype that acts identically to {@link DefaultFullHttpRequest}.
* *
* <p>As it is an {@link OutboundMessageType} subtype, there is a {@code modifyMessage} method * <p>As it is an {@link OutboundMessageType} subtype, there is a {@code modifyMessage} method that
* that modifies the request to reflect the new host and optional path. We also implement a {@code * modifies the request to reflect the new host and optional path. We also implement a {@code name}
* name} method, which returns a standard name and the current hostname.</p> * method, which returns a standard name and the current hostname.
*/ */
public class HttpRequestMessage extends DefaultFullHttpRequest implements OutboundMessageType { public class HttpRequestMessage extends DefaultFullHttpRequest implements OutboundMessageType {
@ -40,15 +40,12 @@ public class HttpRequestMessage extends DefaultFullHttpRequest implements Outbou
super(httpVersion, method, uri); super(httpVersion, method, uri);
} }
private HttpRequestMessage(HttpVersion httpVersion, HttpMethod method, String uri, private HttpRequestMessage(
ByteBuf content) { HttpVersion httpVersion, HttpMethod method, String uri, ByteBuf content) {
super(httpVersion, method, uri, content); super(httpVersion, method, uri, content);
} }
/** Used for conversion from {@link FullHttpRequest} to {@link HttpRequestMessage} */
/**
* Used for conversion from {@link FullHttpRequest} to {@link HttpRequestMessage}
*/
public HttpRequestMessage(FullHttpRequest request) { public HttpRequestMessage(FullHttpRequest request) {
this(request.protocolVersion(), request.method(), request.uri(), request.content()); this(request.protocolVersion(), request.method(), request.uri(), request.content());
request.headers().forEach((entry) -> headers().set(entry.getKey(), entry.getValue())); request.headers().forEach((entry) -> headers().set(entry.getKey(), entry.getValue()));
@ -60,9 +57,7 @@ public class HttpRequestMessage extends DefaultFullHttpRequest implements Outbou
return this; return this;
} }
/** /** Modifies headers to reflect new host and new path if applicable. */
* Modifies headers to reflect new host and new path if applicable.
*/
@Override @Override
public HttpRequestMessage modifyMessage(String... args) throws IllegalArgumentException { public HttpRequestMessage modifyMessage(String... args) throws IllegalArgumentException {
if (args.length == 1 || args.length == 2) { if (args.length == 1 || args.length == 2) {
@ -78,7 +73,8 @@ public class HttpRequestMessage extends DefaultFullHttpRequest implements Outbou
String.format( String.format(
"Wrong number of arguments present for modifying HttpRequestMessage." "Wrong number of arguments present for modifying HttpRequestMessage."
+ " Received %d arguments instead of 2. Received arguments: " + " Received %d arguments instead of 2. Received arguments: "
+ Arrays.toString(args), args.length)); + Arrays.toString(args),
args.length));
} }
} }
@ -86,5 +82,4 @@ public class HttpRequestMessage extends DefaultFullHttpRequest implements Outbou
public String toString() { public String toString() {
return String.format("Http(s) Request on: %s", headers().get("host")); return String.format("Http(s) Request on: %s", headers().get("host"));
} }
} }

View file

@ -20,18 +20,14 @@ import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.codec.http.HttpVersion;
/** /** {@link InboundMessageType} subtype that acts identically to {@link DefaultFullHttpResponse} */
* {@link InboundMessageType} subtype that acts identically to {@link DefaultFullHttpResponse}
*/
public class HttpResponseMessage extends DefaultFullHttpResponse implements InboundMessageType { public class HttpResponseMessage extends DefaultFullHttpResponse implements InboundMessageType {
private HttpResponseMessage(HttpVersion version, HttpResponseStatus status, ByteBuf content) { private HttpResponseMessage(HttpVersion version, HttpResponseStatus status, ByteBuf content) {
super(version, status, content); super(version, status, content);
} }
/** /** Used for pipeline conversion from {@link FullHttpResponse} to {@link HttpResponseMessage} */
* Used for pipeline conversion from {@link FullHttpResponse} to {@link HttpResponseMessage}
*/
public HttpResponseMessage(FullHttpResponse response) { public HttpResponseMessage(FullHttpResponse response) {
this(response.protocolVersion(), response.status(), response.content()); this(response.protocolVersion(), response.status(), response.content());

View file

@ -18,6 +18,4 @@ package google.registry.monitoring.blackbox.messages;
* Marker Interface that is implemented by all classes that serve as {@code inboundMessages} in * Marker Interface that is implemented by all classes that serve as {@code inboundMessages} in
* channel pipeline * channel pipeline
*/ */
public interface InboundMessageType { public interface InboundMessageType {}
}

View file

@ -0,0 +1,125 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// 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.
package google.registry.monitoring.blackbox.modules;
import static com.google.common.base.Suppliers.memoizeWithExpiration;
import static google.registry.util.ResourceUtils.readResourceBytes;
import static google.registry.util.ResourceUtils.readResourceUtf8;
import dagger.Module;
import dagger.Provides;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import javax.inject.Provider;
import javax.inject.Qualifier;
import javax.inject.Singleton;
import org.joda.time.Duration;
/**
* Dagger module that provides bindings needed to inject server certificate chain and private key.
*
* <p>Currently the sandbox certificates and private key are stored as local secrets in a .p12 file,
* however, in production, all certificates will be stored in a .pem file that is encrypted by Cloud
* KMS. The .pem file can be generated by concatenating the .crt certificate files on the chain and
* the .key private file.
*
* <p>The production certificates in the .pem file must be stored in order, where the next
* certificate's subject is the previous certificate's issuer.
*
* @see <a href="https://cloud.google.com/kms/">Cloud Key Management Service</a>
*/
@Module
public class CertificateModule {
/** {@link Qualifier} to identify components provided from Local Secrets. */
// TODO - remove this qualifier and replace it with using KMS to retrieve private key and
// certificate
@Qualifier
public @interface LocalSecrets {}
private static InputStream readResource(String filename) throws IOException {
return readResourceBytes(CertificateModule.class, filename).openStream();
}
@Provides
@LocalSecrets
static Duration provideCacheDuration() {
return Duration.standardSeconds(2);
}
@Singleton
@Provides
@LocalSecrets
static String keystorePasswordProvider() {
return readResourceUtf8(CertificateModule.class, "secrets/keystore_password.txt");
}
@Provides
@LocalSecrets
static PrivateKey providePrivateKey(@LocalSecrets Provider<String> passwordProvider) {
try {
InputStream inStream = readResource("secrets/prober-client-tls-sandbox.p12");
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(inStream, passwordProvider.get().toCharArray());
String alias = ks.aliases().nextElement();
return (PrivateKey) ks.getKey(alias, passwordProvider.get().toCharArray());
} catch (IOException | GeneralSecurityException e) {
throw new RuntimeException(e);
}
}
@Provides
@LocalSecrets
static X509Certificate[] provideCertificates(@LocalSecrets Provider<String> passwordProvider) {
try {
InputStream inStream = readResource("secrets/prober-client-tls-sandbox.p12");
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(inStream, passwordProvider.get().toCharArray());
String alias = ks.aliases().nextElement();
return new X509Certificate[] {(X509Certificate) ks.getCertificate(alias)};
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Singleton
@Provides
@LocalSecrets
static Supplier<PrivateKey> providePrivatekeySupplier(
@LocalSecrets Provider<PrivateKey> privateKeyProvider, @LocalSecrets Duration duration) {
return memoizeWithExpiration(
privateKeyProvider::get, duration.getStandardSeconds(), TimeUnit.SECONDS);
}
@Singleton
@Provides
@LocalSecrets
static Supplier<X509Certificate[]> provideCertificatesSupplier(
@LocalSecrets Provider<X509Certificate[]> certificatesProvider,
@LocalSecrets Duration duration) {
return memoizeWithExpiration(
certificatesProvider::get, duration.getStandardSeconds(), TimeUnit.SECONDS);
}
}

View file

@ -0,0 +1,582 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// 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.
package google.registry.monitoring.blackbox.modules;
import static google.registry.monitoring.blackbox.messages.EppRequestMessage.CLIENT_ID_KEY;
import static google.registry.monitoring.blackbox.messages.EppRequestMessage.CLIENT_PASSWORD_KEY;
import static google.registry.monitoring.blackbox.messages.EppRequestMessage.CLIENT_TRID_KEY;
import static google.registry.monitoring.blackbox.messages.EppRequestMessage.DOMAIN_KEY;
import static google.registry.util.ResourceUtils.readResourceUtf8;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoSet;
import google.registry.monitoring.blackbox.ProbingSequence;
import google.registry.monitoring.blackbox.ProbingStep;
import google.registry.monitoring.blackbox.connection.Protocol;
import google.registry.monitoring.blackbox.handlers.EppActionHandler;
import google.registry.monitoring.blackbox.handlers.EppMessageHandler;
import google.registry.monitoring.blackbox.handlers.SslClientInitializer;
import google.registry.monitoring.blackbox.messages.EppMessage;
import google.registry.monitoring.blackbox.messages.EppRequestMessage;
import google.registry.monitoring.blackbox.messages.EppResponseMessage;
import google.registry.monitoring.blackbox.modules.CertificateModule.LocalSecrets;
import google.registry.monitoring.blackbox.tokens.EppToken;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelHandler;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.ssl.SslProvider;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.function.Supplier;
import javax.inject.Named;
import javax.inject.Provider;
import javax.inject.Qualifier;
import javax.inject.Singleton;
import org.joda.time.Duration;
/**
* A module that provides the components necessary for and the overall {@link ProbingSequence} to
* probe EPP.
*/
@Module
public class EppModule {
private static final int EPP_PORT = 700;
private static final String EPP_PROTOCOL_NAME = "epp";
/** Maximum length of EPP messages received. Same as max length for HTTP messages, is 0.5 MB. */
// TODO - replace with config specified maximum length.
private static final int maximumMessageLengthBytes = 512 * 1024;
/** Standard EPP header length. */
// TODO - replace with config specified header length (still 4).
private static final int eppHeaderLengthBytes = 4;
/** Dagger provided {@link ProbingSequence} that probes EPP login and logout actions. */
@Provides
@Singleton
@EppProtocol
static Bootstrap provideEppBootstrap(Provider<Bootstrap> bootstrapProvider) {
return bootstrapProvider.get();
}
/**
* Dagger provided {@link ProbingSequence} that probes EPP login, create, check, and delete
* actions with a persistent connection.
*/
@Provides
@Singleton
@IntoSet
static ProbingSequence provideEppLoginCreateCheckDeleteCheckProbingSequence(
EppToken.Persistent token,
@Named("hello") ProbingStep helloStep,
@Named("loginSuccess") ProbingStep loginSuccessStep,
@Named("createSuccess") ProbingStep createSuccessStep,
@Named("checkExists") ProbingStep checkStepFirst,
@Named("deleteSuccess") ProbingStep deleteSuccessStep,
@Named("checkNotExists") ProbingStep checkStepSecond) {
return new ProbingSequence.Builder(token)
.add(helloStep)
.add(loginSuccessStep)
.add(createSuccessStep)
.markFirstRepeated()
.add(checkStepFirst)
.add(deleteSuccessStep)
.add(checkStepSecond)
.build();
}
/**
* Dagger provided {@link ProbingSequence} that probes EPP login, create, check, delete, and
* logout actions with a transient connection.
*/
@Provides
@Singleton
@IntoSet
static ProbingSequence provideEppLoginCreateCheckDeleteCheckLogoutProbingSequence(
EppToken.Transient token,
@Named("hello") ProbingStep helloStep,
@Named("loginSuccess") ProbingStep loginSuccessStep,
@Named("createSuccess") ProbingStep createSuccessStep,
@Named("checkExists") ProbingStep checkStepFirst,
@Named("deleteSuccess") ProbingStep deleteSuccessStep,
@Named("checkNotExists") ProbingStep checkStepSecond,
@Named("logout") ProbingStep logoutStep) {
return new ProbingSequence.Builder(token)
.add(helloStep)
.add(loginSuccessStep)
.add(createSuccessStep)
.add(checkStepFirst)
.add(deleteSuccessStep)
.add(checkStepSecond)
.add(logoutStep)
.build();
}
/**
* Provides {@link ProbingStep} that establishes initial connection to EPP server.
*
* <p>Always necessary as first step for any EPP {@link ProbingSequence} and first repeated step
* for any {@link ProbingSequence} that doesn't stay logged in (transient).
*/
@Provides
@Named("hello")
static ProbingStep provideEppHelloStep(
@EppProtocol Protocol eppProtocol,
Duration duration,
@Named("hello") EppRequestMessage helloRequest,
@EppProtocol Bootstrap bootstrap) {
return ProbingStep.builder()
.setProtocol(eppProtocol)
.setDuration(duration)
.setMessageTemplate(helloRequest)
.setBootstrap(bootstrap)
.build();
}
/** {@link Provides} {@link ProbingStep} that logs into the EPP server. */
@Provides
@Named("loginSuccess")
static ProbingStep provideEppLoginSuccessStep(
@EppProtocol Protocol eppProtocol,
Duration duration,
@Named("loginSuccess") EppRequestMessage loginSuccessRequest,
@EppProtocol Bootstrap bootstrap) {
return ProbingStep.builder()
.setProtocol(eppProtocol)
.setDuration(duration)
.setMessageTemplate(loginSuccessRequest)
.setBootstrap(bootstrap)
.build();
}
/** {@link Provides} {@link ProbingStep} that creates a new domain on EPP server. */
@Provides
@Named("createSuccess")
static ProbingStep provideEppCreateSuccessStep(
@EppProtocol Protocol eppProtocol,
Duration duration,
@Named("createSuccess") EppRequestMessage createSuccessRequest,
@EppProtocol Bootstrap bootstrap) {
return ProbingStep.builder()
.setProtocol(eppProtocol)
.setDuration(duration)
.setMessageTemplate(createSuccessRequest)
.setBootstrap(bootstrap)
.build();
}
/** {@link Provides} {@link ProbingStep} that built, checks a domain exists on EPP server. */
@Provides
@Named("checkExists")
static ProbingStep provideEppCheckExistsStep(
@EppProtocol Protocol eppProtocol,
Duration duration,
@Named("checkExists") EppRequestMessage checkExistsRequest,
@EppProtocol Bootstrap bootstrap) {
return ProbingStep.builder()
.setProtocol(eppProtocol)
.setDuration(duration)
.setMessageTemplate(checkExistsRequest)
.setBootstrap(bootstrap)
.build();
}
/** {@link Provides} {@link ProbingStep} that checks a domain doesn't exist on EPP server. */
@Provides
@Named("checkNotExists")
static ProbingStep provideEppCheckNotExistsStep(
@EppProtocol Protocol eppProtocol,
Duration duration,
@Named("checkNotExists") EppRequestMessage checkNotExistsRequest,
@EppProtocol Bootstrap bootstrap) {
return ProbingStep.builder()
.setProtocol(eppProtocol)
.setDuration(duration)
.setMessageTemplate(checkNotExistsRequest)
.setBootstrap(bootstrap)
.build();
}
/** {@link Provides} {@link ProbingStep} that deletes a domain from EPP server. */
@Provides
@Named("deleteSuccess")
static ProbingStep provideEppDeleteSuccessStep(
@EppProtocol Protocol eppProtocol,
Duration duration,
@Named("deleteSuccess") EppRequestMessage deleteSuccessRequest,
@EppProtocol Bootstrap bootstrap) {
return ProbingStep.builder()
.setProtocol(eppProtocol)
.setDuration(duration)
.setMessageTemplate(deleteSuccessRequest)
.setBootstrap(bootstrap)
.build();
}
/** {@link Provides} {@link ProbingStep} that logs out of EPP server. */
@Provides
@Named("logout")
static ProbingStep provideEppLogoutStep(
@EppProtocol Protocol eppProtocol,
Duration duration,
@Named("logout") EppRequestMessage logoutRequest,
@EppProtocol Bootstrap bootstrap) {
return ProbingStep.builder()
.setProtocol(eppProtocol)
.setDuration(duration)
.setMessageTemplate(logoutRequest)
.setBootstrap(bootstrap)
.build();
}
/** {@link Provides} hello {@link EppRequestMessage} with only expected response of greeting. */
@Provides
@Named("hello")
static EppRequestMessage provideHelloRequestMessage(
@Named("greeting") EppResponseMessage greetingResponse) {
return new EppRequestMessage(greetingResponse, null, (a, b) -> ImmutableMap.of());
}
/**
* Set of all possible {@link EppRequestMessage}s paired with their expected {@link
* EppResponseMessage}s.
*/
/** {@link Provides} login {@link EppRequestMessage} with expected response of success. */
@Provides
@Named("loginSuccess")
static EppRequestMessage provideLoginSuccessRequestMessage(
@Named("success") EppResponseMessage successResponse,
@Named("login") String loginTemplate,
@Named("eppUserId") String userId,
@Named("eppPassword") String userPassword) {
return new EppRequestMessage(
successResponse,
loginTemplate,
(clTrid, domain) ->
ImmutableMap.of(
CLIENT_TRID_KEY, clTrid,
CLIENT_ID_KEY, userId,
CLIENT_PASSWORD_KEY, userPassword));
}
/** {@link Provides} login {@link EppRequestMessage} with expected response of failure. */
@Provides
@Named("loginFailure")
static EppRequestMessage provideLoginFailureRequestMessage(
@Named("failure") EppResponseMessage failureResponse,
@Named("login") String loginTemplate,
@Named("eppUserId") String userId,
@Named("eppPassword") String userPassword) {
return new EppRequestMessage(
failureResponse,
loginTemplate,
(clTrid, domain) ->
ImmutableMap.of(
CLIENT_TRID_KEY, clTrid,
CLIENT_ID_KEY, userId,
CLIENT_PASSWORD_KEY, userPassword));
}
/** {@link Provides} create {@link EppRequestMessage} with expected response of success. */
@Provides
@Named("createSuccess")
static EppRequestMessage provideCreateSuccessRequestMessage(
@Named("success") EppResponseMessage successResponse,
@Named("create") String createTemplate) {
return new EppRequestMessage(
successResponse,
createTemplate,
(clTrid, domain) ->
ImmutableMap.of(
CLIENT_TRID_KEY, clTrid,
DOMAIN_KEY, domain));
}
/** {@link Provides} create {@link EppRequestMessage} with expected response of failure. */
@Provides
@Named("createFailure")
static EppRequestMessage provideCreateFailureRequestMessage(
@Named("failure") EppResponseMessage failureResponse,
@Named("create") String createTemplate) {
return new EppRequestMessage(
failureResponse,
createTemplate,
(clTrid, domain) ->
ImmutableMap.of(
CLIENT_TRID_KEY, clTrid,
DOMAIN_KEY, domain));
}
/** {@link Provides} delete {@link EppRequestMessage} with expected response of success. */
@Provides
@Named("deleteSuccess")
static EppRequestMessage provideDeleteSuccessRequestMessage(
@Named("success") EppResponseMessage successResponse,
@Named("delete") String deleteTemplate) {
return new EppRequestMessage(
successResponse,
deleteTemplate,
(clTrid, domain) ->
ImmutableMap.of(
CLIENT_TRID_KEY, clTrid,
DOMAIN_KEY, domain));
}
/** {@link Provides} delete {@link EppRequestMessage} with expected response of failure. */
@Provides
@Named("deleteFailure")
static EppRequestMessage provideDeleteFailureRequestMessage(
@Named("failure") EppResponseMessage failureResponse,
@Named("delete") String deleteTemplate) {
return new EppRequestMessage(
failureResponse,
deleteTemplate,
(clTrid, domain) ->
ImmutableMap.of(
CLIENT_TRID_KEY, clTrid,
DOMAIN_KEY, domain));
}
/** {@link Provides} logout {@link EppRequestMessage} with expected response of success. */
@Provides
@Named("logout")
static EppRequestMessage provideLogoutSuccessRequestMessage(
@Named("success") EppResponseMessage successResponse,
@Named("logout") String logoutTemplate) {
return new EppRequestMessage(
successResponse,
logoutTemplate,
(clTrid, domain) -> ImmutableMap.of(CLIENT_TRID_KEY, clTrid));
}
/** {@link Provides} check {@link EppRequestMessage} with expected response of domainExists. */
@Provides
@Named("checkExists")
static EppRequestMessage provideCheckExistsRequestMessage(
@Named("domainExists") EppResponseMessage domainExistsResponse,
@Named("check") String checkTemplate) {
return new EppRequestMessage(
domainExistsResponse,
checkTemplate,
(clTrid, domain) ->
ImmutableMap.of(
CLIENT_TRID_KEY, clTrid,
DOMAIN_KEY, domain));
}
/** {@link Provides} check {@link EppRequestMessage} with expected response of domainExists. */
@Provides
@Named("checkNotExists")
static EppRequestMessage provideCheckNotExistsRequestMessage(
@Named("domainNotExists") EppResponseMessage domainNotExistsResponse,
@Named("check") String checkTemplate) {
return new EppRequestMessage(
domainNotExistsResponse,
checkTemplate,
(clTrid, domain) ->
ImmutableMap.of(
CLIENT_TRID_KEY, clTrid,
DOMAIN_KEY, domain));
}
@Provides
@Named("success")
static EppResponseMessage provideSuccessResponse() {
return new EppResponseMessage(
"success",
(clTrid, domain) ->
ImmutableList.of(
String.format("//eppns:clTRID[.='%s']", clTrid), EppMessage.XPASS_EXPRESSION));
}
@Provides
@Named("failure")
static EppResponseMessage provideFailureResponse() {
return new EppResponseMessage(
"failure",
(clTrid, domain) ->
ImmutableList.of(
String.format("//eppns:clTRID[.='%s']", clTrid), EppMessage.XFAIL_EXPRESSION));
}
@Provides
@Named("domainExists")
static EppResponseMessage provideDomainExistsResponse() {
return new EppResponseMessage(
"domainExists",
(clTrid, domain) ->
ImmutableList.of(
String.format("//eppns:clTRID[.='%s']", clTrid),
String.format("//domainns:name[@avail='false'][.='%s']", domain),
EppMessage.XPASS_EXPRESSION));
}
@Provides
@Named("domainNotExists")
static EppResponseMessage provideDomainNotExistsResponse() {
return new EppResponseMessage(
"domainNotExists",
(clTrid, domain) ->
ImmutableList.of(
String.format("//eppns:clTRID[.='%s']", clTrid),
String.format("//domainns:name[@avail='true'][.='%s']", domain),
EppMessage.XPASS_EXPRESSION));
}
@Provides
@Named("greeting")
static EppResponseMessage provideGreetingResponse() {
return new EppResponseMessage(
"greeting", (clTrid, domain) -> ImmutableList.of("//eppns:greeting"));
}
/** {@link Provides} filename of template for login EPP request. */
@Provides
@Named("login")
static String provideLoginTemplate() {
return "login.xml";
}
/** {@link Provides} filename of template for create EPP request. */
@Provides
@Named("create")
static String provideCreateTemplate() {
return "create.xml";
}
/** {@link Provides} filename of template for delete EPP request. */
@Provides
@Named("delete")
static String provideDeleteTemplate() {
return "delete.xml";
}
/** {@link Provides} filename of template for logout EPP request. */
@Provides
@Named("logout")
static String provideLogoutTemplate() {
return "logout.xml";
}
/** {@link Provides} filename of template for check EPP request. */
@Provides
@Named("check")
static String provideCheckTemplate() {
return "check.xml";
}
/** {@link Provides} {@link Protocol} that represents an EPP connection. */
@Singleton
@Provides
@EppProtocol
static Protocol provideEppProtocol(
@EppProtocol int eppPort,
@EppProtocol ImmutableList<Provider<? extends ChannelHandler>> handlerProviders) {
return Protocol.builder()
.setName(EPP_PROTOCOL_NAME)
.setPort(eppPort)
.setHandlerProviders(handlerProviders)
.setPersistentConnection(true)
.build();
}
/**
* {@link Provides} the list of providers of {@link ChannelHandler}s that are used for the EPP
* Protocol.
*/
@Provides
@EppProtocol
static ImmutableList<Provider<? extends ChannelHandler>> provideEppHandlerProviders(
@EppProtocol Provider<SslClientInitializer<NioSocketChannel>> sslClientInitializerProvider,
Provider<LengthFieldBasedFrameDecoder> lengthFieldBasedFrameDecoderProvider,
Provider<LengthFieldPrepender> lengthFieldPrependerProvider,
Provider<EppMessageHandler> eppMessageHandlerProvider,
Provider<EppActionHandler> eppActionHandlerProvider) {
return ImmutableList.of(
sslClientInitializerProvider,
lengthFieldBasedFrameDecoderProvider,
lengthFieldPrependerProvider,
eppMessageHandlerProvider,
eppActionHandlerProvider);
}
@Provides
static LengthFieldBasedFrameDecoder provideLengthFieldBasedFrameDecoder() {
return new LengthFieldBasedFrameDecoder(
maximumMessageLengthBytes,
0,
eppHeaderLengthBytes,
-eppHeaderLengthBytes,
eppHeaderLengthBytes);
}
@Provides
static LengthFieldPrepender provideLengthFieldPrepender() {
return new LengthFieldPrepender(eppHeaderLengthBytes, true);
}
/** {@link Provides} the {@link SslClientInitializer} used for the {@link EppProtocol}. */
@Provides
@EppProtocol
static SslClientInitializer<NioSocketChannel> provideSslClientInitializer(
SslProvider sslProvider,
@LocalSecrets Supplier<PrivateKey> privateKeySupplier,
@LocalSecrets Supplier<X509Certificate[]> certificatesSupplier) {
return new SslClientInitializer<>(sslProvider, privateKeySupplier, certificatesSupplier);
}
@Provides
@Named("eppUserId")
static String provideEppUserId() {
return readResourceUtf8(EppModule.class, "secrets/user_id.txt");
}
@Provides
@Named("eppPassword")
static String provideEppPassphrase() {
return readResourceUtf8(EppModule.class, "secrets/password.txt");
}
@Provides
@Named("eppHost")
static String provideEppHost() {
return readResourceUtf8(EppModule.class, "secrets/epp_host.txt");
}
@Provides
@Named("eppTld")
static String provideTld() {
return "oa-0.test";
}
@Provides
@EppProtocol
static int provideEppPort() {
return EPP_PORT;
}
/** Dagger qualifier to provide EPP protocol related handlers and other bindings. */
@Qualifier
public @interface EppProtocol {}
}

View file

@ -12,12 +12,15 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package google.registry.monitoring.blackbox; package google.registry.monitoring.blackbox.modules;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
import dagger.multibindings.IntoSet; import dagger.multibindings.IntoSet;
import google.registry.monitoring.blackbox.ProbingSequence;
import google.registry.monitoring.blackbox.ProbingStep;
import google.registry.monitoring.blackbox.connection.Protocol;
import google.registry.monitoring.blackbox.handlers.SslClientInitializer; import google.registry.monitoring.blackbox.handlers.SslClientInitializer;
import google.registry.monitoring.blackbox.handlers.WebWhoisActionHandler; import google.registry.monitoring.blackbox.handlers.WebWhoisActionHandler;
import google.registry.monitoring.blackbox.handlers.WebWhoisMessageHandler; import google.registry.monitoring.blackbox.handlers.WebWhoisMessageHandler;
@ -38,7 +41,8 @@ import javax.inject.Singleton;
import org.joda.time.Duration; import org.joda.time.Duration;
/** /**
* A module that provides the {@link Protocol}s to send HTTP(S) web WHOIS requests. * A module that provides the components necessary for and the overall {@link ProbingSequence} to
* probe WebWHOIS.
*/ */
@Module @Module
public class WebWhoisModule { public class WebWhoisModule {
@ -48,14 +52,10 @@ public class WebWhoisModule {
private static final int HTTP_WHOIS_PORT = 80; private static final int HTTP_WHOIS_PORT = 80;
private static final int HTTPS_WHOIS_PORT = 443; private static final int HTTPS_WHOIS_PORT = 443;
/** /** Standard length of messages used by Proxy. Equates to 0.5 MB. */
* Standard length of messages used by Proxy. Equates to 0.5 MB.
*/
private static final int maximumMessageLengthBytes = 512 * 1024; private static final int maximumMessageLengthBytes = 512 * 1024;
/** /** {@link Provides} only step used in WebWhois sequence. */
* {@link Provides} only step used in WebWhois sequence.
*/
@Provides @Provides
@WebWhoisProtocol @WebWhoisProtocol
static ProbingStep provideWebWhoisStep( static ProbingStep provideWebWhoisStep(
@ -72,9 +72,7 @@ public class WebWhoisModule {
.build(); .build();
} }
/** /** {@link Provides} the {@link Protocol} that corresponds to http connection. */
* {@link Provides} the {@link Protocol} that corresponds to http connection.
*/
@Singleton @Singleton
@Provides @Provides
@HttpWhoisProtocol @HttpWhoisProtocol
@ -89,9 +87,7 @@ public class WebWhoisModule {
.build(); .build();
} }
/** /** {@link Provides} the {@link Protocol} that corresponds to https connection. */
* {@link Provides} the {@link Protocol} that corresponds to https connection.
*/
@Singleton @Singleton
@Provides @Provides
@HttpsWhoisProtocol @HttpsWhoisProtocol
@ -123,7 +119,6 @@ public class WebWhoisModule {
messageHandlerProvider, messageHandlerProvider,
webWhoisActionHandlerProvider); webWhoisActionHandlerProvider);
} }
/** /**
* {@link Provides} the list of providers of {@link ChannelHandler}s that are used for https * {@link Provides} the list of providers of {@link ChannelHandler}s that are used for https
* protocol. * protocol.
@ -155,9 +150,7 @@ public class WebWhoisModule {
return new HttpObjectAggregator(maxContentLength); return new HttpObjectAggregator(maxContentLength);
} }
/** /** {@link Provides} the {@link SslClientInitializer} used for the {@link HttpsWhoisProtocol}. */
* {@link Provides} the {@link SslClientInitializer} used for the {@link HttpsWhoisProtocol}.
*/
@Provides @Provides
@HttpsWhoisProtocol @HttpsWhoisProtocol
static SslClientInitializer<NioSocketChannel> provideSslClientInitializer( static SslClientInitializer<NioSocketChannel> provideSslClientInitializer(
@ -165,33 +158,23 @@ public class WebWhoisModule {
return new SslClientInitializer<>(sslProvider); return new SslClientInitializer<>(sslProvider);
} }
/** /** {@link Provides} the {@link Bootstrap} used by the WebWhois sequence. */
* {@link Provides} the {@link Bootstrap} used by the WebWhois sequence.
*/
@Singleton @Singleton
@Provides @Provides
@WebWhoisProtocol @WebWhoisProtocol
static Bootstrap provideBootstrap( static Bootstrap provideBootstrap(
EventLoopGroup eventLoopGroup, EventLoopGroup eventLoopGroup, Class<? extends Channel> channelClazz) {
Class<? extends Channel> channelClazz) { return new Bootstrap().group(eventLoopGroup).channel(channelClazz);
return new Bootstrap()
.group(eventLoopGroup)
.channel(channelClazz);
} }
/** /** {@link Provides} standard WebWhois sequence. */
* {@link Provides} standard WebWhois sequence.
*/
@Provides @Provides
@Singleton @Singleton
@IntoSet @IntoSet
ProbingSequence provideWebWhoisSequence( ProbingSequence provideWebWhoisSequence(
@WebWhoisProtocol ProbingStep probingStep, @WebWhoisProtocol ProbingStep probingStep, WebWhoisToken webWhoisToken) {
WebWhoisToken webWhoisToken) {
return new ProbingSequence.Builder(webWhoisToken) return new ProbingSequence.Builder(webWhoisToken).add(probingStep).build();
.add(probingStep)
.build();
} }
@Provides @Provides
@ -200,16 +183,12 @@ public class WebWhoisModule {
return maximumMessageLengthBytes; return maximumMessageLengthBytes;
} }
/** /** {@link Provides} the list of top level domains to be probed */
* {@link Provides} the list of top level domains to be probed
*/
@Singleton @Singleton
@Provides @Provides
@WebWhoisProtocol @WebWhoisProtocol
CircularList<String> provideTopLevelDomains() { CircularList<String> provideTopLevelDomains() {
return new CircularList.Builder<String>() return new CircularList.Builder<String>().add("how", "soy", "xn--q9jyb4c").build();
.add("how", "soy", "xn--q9jyb4c")
.build();
} }
@Provides @Provides
@ -224,28 +203,15 @@ public class WebWhoisModule {
return HTTPS_WHOIS_PORT; return HTTPS_WHOIS_PORT;
} }
/** /** Dagger qualifier to provide HTTP whois protocol related handlers and other bindings. */
* Dagger qualifier to provide HTTP whois protocol related handlers and other bindings.
*/
@Qualifier @Qualifier
public @interface HttpWhoisProtocol { public @interface HttpWhoisProtocol {}
} /** Dagger qualifier to provide HTTPS whois protocol related handlers and other bindings. */
/**
* Dagger qualifier to provide HTTPS whois protocol related handlers and other bindings.
*/
@Qualifier @Qualifier
public @interface HttpsWhoisProtocol { public @interface HttpsWhoisProtocol {}
} /** Dagger qualifier to provide any WebWhois related bindings. */
/**
* Dagger qualifier to provide any WebWhois related bindings.
*/
@Qualifier @Qualifier
public @interface WebWhoisProtocol { public @interface WebWhoisProtocol {}
}
} }

View file

@ -0,0 +1,138 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// 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.
package google.registry.monitoring.blackbox.tokens;
import com.google.common.annotations.VisibleForTesting;
import google.registry.monitoring.blackbox.exceptions.UndeterminedStateException;
import google.registry.monitoring.blackbox.messages.EppRequestMessage;
import google.registry.monitoring.blackbox.messages.OutboundMessageType;
import io.netty.channel.Channel;
import java.util.concurrent.atomic.AtomicInteger;
import javax.inject.Inject;
import javax.inject.Named;
/** {@link Token} subtype that deals performs specified actions for the EPP sequence. */
public abstract class EppToken extends Token {
/** Describes the maximum possible length of generated domain name. */
private static final int MAX_DOMAIN_PART_LENGTH = 50;
/** Suffix added for differentiation between two TRID in case they have the same timestamp. */
private static AtomicInteger clientIdSuffix = new AtomicInteger();
protected final String tld;
private String host;
private String currentDomainName;
/**
* Always the constructor used to provide any {@link EppToken}, with {@code tld} and {@code host}
* specified by Dagger.
*/
protected EppToken(String tld, String host) {
this.tld = tld;
this.host = host;
currentDomainName = newDomainName(getNewTRID());
}
/** Constructor used when passing on same {@link Channel} to next {@link Token}. */
protected EppToken(String tld, String host, Channel channel) {
this(tld, host);
setChannel(channel);
}
/** Modifies the message to reflect the new domain name and TRID */
@Override
public OutboundMessageType modifyMessage(OutboundMessageType originalMessage)
throws UndeterminedStateException {
return ((EppRequestMessage) originalMessage).modifyMessage(getNewTRID(), currentDomainName);
}
@Override
public String host() {
return host;
}
@VisibleForTesting
String getCurrentDomainName() {
return currentDomainName;
}
/**
* Return a unique string usable as an EPP client transaction ID.
*
* <p><b>Warning:</b> The prober cleanup servlet relies on the timestamp being in the third
* position when splitting on dashes. Do not change this format without updating that code as
* well.
*/
private String getNewTRID() {
return String.format(
"prober-%s-%d-%d",
"localhost", System.currentTimeMillis(), clientIdSuffix.incrementAndGet());
}
/** Return a fully qualified domain label to use, derived from the client transaction ID. */
private String newDomainName(String clTrid) {
String sld;
// not sure if the local hostname will stick to RFC validity rules
if (clTrid.length() > MAX_DOMAIN_PART_LENGTH) {
sld = clTrid.substring(clTrid.length() - MAX_DOMAIN_PART_LENGTH);
} else {
sld = clTrid;
}
// insert top level domain here
return String.format("%s.%s", sld, tld);
}
/**
* {@link EppToken} Subclass that represents a token used in a transient sequence, meaning the
* connection is remade on each new iteration of the {@link
* google.registry.monitoring.blackbox.ProbingSequence}.
*/
public static class Transient extends EppToken {
@Inject
public Transient(@Named("eppTld") String tld, @Named("eppHost") String host) {
super(tld, host);
}
@Override
public Token next() {
return new Transient(tld, host());
}
}
/**
* {@link EppToken} Subclass that represents a token used in a persistent sequence, meaning the
* connection is maintained on each new iteration of the {@link
* google.registry.monitoring.blackbox.ProbingSequence}.
*/
public static class Persistent extends EppToken {
@Inject
public Persistent(@Named("eppTld") String tld, @Named("eppHost") String host) {
super(tld, host);
}
/** Constructor used on call to {@code next} to preserve channel. */
private Persistent(String tld, String host, Channel channel) {
super(tld, host, channel);
}
@Override
public Token next() {
return new Persistent(tld, host(), channel());
}
}
}

View file

@ -24,12 +24,12 @@ import io.netty.channel.Channel;
* Superclass that represents information passed to each {@link ProbingStep} in a single loop of a * Superclass that represents information passed to each {@link ProbingStep} in a single loop of a
* {@link ProbingSequence}. * {@link ProbingSequence}.
* *
* <p>Modifies the message passed in to reflect information relevant to a single loop * <p>Modifies the message passed in to reflect information relevant to a single loop in a {@link
* in a {@link ProbingSequence}. Additionally, passes on channel that remains unchanged within a * ProbingSequence}. Additionally, passes on channel that remains unchanged within a loop of the
* loop of the sequence.</p> * sequence.
* *
* <p>Also obtains the next {@link Token} corresponding to the next iteration of a loop * <p>Also obtains the next {@link Token} corresponding to the next iteration of a loop in the
* in the sequence.</p> * sequence.
*/ */
public abstract class Token { public abstract class Token {
@ -40,32 +40,22 @@ public abstract class Token {
*/ */
protected Channel channel; protected Channel channel;
/** /** Obtains next {@link Token} for next loop in sequence. */
* Obtains next {@link Token} for next loop in sequence.
*/
public abstract Token next(); public abstract Token next();
/** /** String corresponding to host that is relevant for loop in sequence. */
* String corresponding to host that is relevant for loop in sequence.
*/
public abstract String host(); public abstract String host();
/** /** Modifies the {@link OutboundMessageType} in the manner necessary for each loop */
* Modifies the {@link OutboundMessageType} in the manner necessary for each loop
*/
public abstract OutboundMessageType modifyMessage(OutboundMessageType messageType) public abstract OutboundMessageType modifyMessage(OutboundMessageType messageType)
throws UndeterminedStateException; throws UndeterminedStateException;
/** /** Set method for {@code channel} */
* Set method for {@code channel}
*/
public void setChannel(Channel channel) { public void setChannel(Channel channel) {
this.channel = channel; this.channel = channel;
} }
/** /** Get method for {@code channel}. */
* Get method for {@code channel}.
*/
public Channel channel() { public Channel channel() {
return this.channel; return this.channel;
} }

View file

@ -15,29 +15,25 @@
package google.registry.monitoring.blackbox.tokens; package google.registry.monitoring.blackbox.tokens;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import google.registry.monitoring.blackbox.WebWhoisModule.WebWhoisProtocol;
import google.registry.monitoring.blackbox.exceptions.UndeterminedStateException; import google.registry.monitoring.blackbox.exceptions.UndeterminedStateException;
import google.registry.monitoring.blackbox.messages.OutboundMessageType; import google.registry.monitoring.blackbox.messages.OutboundMessageType;
import google.registry.monitoring.blackbox.modules.WebWhoisModule.WebWhoisProtocol;
import google.registry.util.CircularList; import google.registry.util.CircularList;
import javax.inject.Inject; import javax.inject.Inject;
/** /**
* {@link Token} subtype designed for WebWhois sequence. * {@link Token} subtype designed for WebWhois sequence.
* *
* <p>Between loops of a WebWhois sequence the only thing changing is the tld we * <p>Between loops of a WebWhois sequence the only thing changing is the tld we are probing. As a
* are probing. As a result, we maintain the list of {@code topLevelDomains} and on each call to * result, we maintain the list of {@code topLevelDomains} and on each call to next, have our index
* next, have our index looking at the next {@code topLevelDomain}. </p> * looking at the next {@code topLevelDomain}.
*/ */
public class WebWhoisToken extends Token { public class WebWhoisToken extends Token {
/** /** For each top level domain (tld), we probe "prefix.tld". */
* For each top level domain (tld), we probe "prefix.tld".
*/
private static final String PREFIX = "whois.nic."; private static final String PREFIX = "whois.nic.";
/** /** {@link ImmutableList} of all top level domains to be probed. */
* {@link ImmutableList} of all top level domains to be probed.
*/
private CircularList<String> topLevelDomainsList; private CircularList<String> topLevelDomainsList;
@Inject @Inject
@ -46,18 +42,14 @@ public class WebWhoisToken extends Token {
this.topLevelDomainsList = topLevelDomainsList; this.topLevelDomainsList = topLevelDomainsList;
} }
/** /** Moves on to next top level domain in {@code topLevelDomainsList}. */
* Moves on to next top level domain in {@code topLevelDomainsList}.
*/
@Override @Override
public WebWhoisToken next() { public WebWhoisToken next() {
topLevelDomainsList = topLevelDomainsList.next(); topLevelDomainsList = topLevelDomainsList.next();
return this; return this;
} }
/** /** Modifies message to reflect the new host coming from the new top level domain. */
* Modifies message to reflect the new host coming from the new top level domain.
*/
@Override @Override
public OutboundMessageType modifyMessage(OutboundMessageType original) public OutboundMessageType modifyMessage(OutboundMessageType original)
throws UndeterminedStateException { throws UndeterminedStateException {
@ -73,4 +65,3 @@ public class WebWhoisToken extends Token {
return PREFIX + topLevelDomainsList.get(); return PREFIX + topLevelDomainsList.get();
} }
} }

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<check>
<domain:check xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>@@DOMAINNAME@@</domain:name>
</domain:check>
</check>
<clTRID>@@CLTRID@@</clTRID>
</command>
</epp>

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<check>
<domain:check xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>@@DOMAINNAME@@</domain:name>
</domain:check>
</check>
<extension>
<launch:check
xmlns:launch="urn:ietf:params:xml:ns:launch-1.0" type="claims">
<launch:phase>claims</launch:phase>
</launch:check>
</extension>
<clTRID>@@CLTRID@@</clTRID>
</command>
</epp>

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<create>
<domain:create xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>@@DOMAINNAME@@</domain:name>
<domain:period unit="y">1</domain:period>
<domain:ns>
<domain:hostObj>ns.fake-domain.tld</domain:hostObj>
</domain:ns>
<domain:registrant>google-mon</domain:registrant>
<domain:contact type="admin">google-mon</domain:contact>
<domain:contact type="tech">google-mon</domain:contact>
<domain:authInfo>
<domain:pw>insecure</domain:pw>
</domain:authInfo>
</domain:create>
</create>
<clTRID>@@CLTRID@@</clTRID>
</command>
</epp>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<delete>
<domain:delete xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>@@DOMAINNAME@@</domain:name>
</domain:delete>
</delete>
<clTRID>@@CLTRID@@</clTRID>
</command>
</epp>

View file

@ -0,0 +1,19 @@
<?xml version='1.0' encoding='UTF-8'?>
<epp xmlns='urn:ietf:params:xml:ns:epp-1.0'>
<response>
<result code='1000'>
<msg>Generic Message</msg>
</result>
<resData>
<domain:chkData xmlns:domain='urn:ietf:params:xml:ns:domain-1.0'>
<domain:cd>
<domain:name avail='false'>@@DOMAINNAME@@</domain:name>
</domain:cd>
</domain:chkData>
</resData>
<trID>
<clTRID>@@CLTRID@@</clTRID>
<svTRID>@@SVTRID@@</svTRID>
</trID>
</response>
</epp>

View file

@ -0,0 +1,19 @@
<?xml version='1.0' encoding='UTF-8'?>
<epp xmlns='urn:ietf:params:xml:ns:epp-1.0'>
<response>
<result code='1000'>
<msg>Generic Message</msg>
</result>
<resData>
<domain:chkData xmlns:domain='urn:ietf:params:xml:ns:domain-1.0'>
<domain:cd>
<domain:name avail='true'>@@DOMAINNAME@@</domain:name>
</domain:cd>
</domain:chkData>
</resData>
<trID>
<clTRID>@@CLTRID@@</clTRID>
<svTRID>@@SVTRID@@</svTRID>
</trID>
</response>
</epp>

View file

@ -0,0 +1,12 @@
<?xml version='1.0' encoding='UTF-8'?>
<epp xmlns='urn:ietf:params:xml:ns:epp-1.0'>
<response>
<result code='2500'>
<msg>Throwaway Message</msg>
</result>
<trID>
<clTRID>@@CLTRID@@</clTRID>
<svTRID>@@SVTRID@@</svTRID>
</trID>
</response>
</epp>

View file

@ -0,0 +1,21 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<greeting>
<svID>Test EPP Server</svID>
<svDate>2000-06-08T22:00:00.0Z</svDate>
<svcMenu>
<version>1.0</version>
<lang>en</lang>
<objURI>urn:ietf:params:xml:ns:contact-1.0</objURI>
<objURI>urn:ietf:params:xml:ns:domain-1.0</objURI>
<objURI>urn:ietf:params:xml:ns:host-1.0</objURI>
</svcMenu>
<dcp>
<access><all/></access>
<statement>
<purpose><admin/><prov/></purpose>
<recipient><ours/></recipient>
<retention><indefinite/></retention>
</statement>
</dcp>
</greeting>
</epp>

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<login>
<clID>@@CLID@@</clID>
<pw>@@PWD@@</pw>
<options>
<version>1.0</version>
<lang>en</lang>
</options>
<svcs>
<objURI>urn:ietf:params:xml:ns:domain-1.0</objURI>
<objURI>urn:ietf:params:xml:ns:contact-1.0</objURI>
<objURI>urn:ietf:params:xml:ns:host-1.0</objURI>
<svcExtension>
<extURI>urn:ietf:params:xml:ns:launch-1.0</extURI>
</svcExtension>
</svcs>
</login>
<clTRID>@@CLTRID@@</clTRID>
</command>
</epp>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<logout/>
<clTRID>ABC-12345</clTRID>
</command>
</epp>

View file

@ -0,0 +1,12 @@
<?xml version='1.0' encoding='UTF-8'?>
<epp xmlns='urn:ietf:params:xml:ns:epp-1.0'>
<response>
<result code='1000'>
<msg>Throwaway Message</msg>
</result>
<trID>
<clTRID>@@CLTRID@@</clTRID>
<svTRID>@@SVTRID@@</svTRID>
</trID>
</response>
</epp>

View file

@ -0,0 +1,389 @@
<?xml version="1.0" encoding="UTF-8"?>
<schema targetNamespace="urn:ietf:params:xml:ns:contact-1.0"
xmlns:contact="urn:ietf:params:xml:ns:contact-1.0"
xmlns:epp="urn:ietf:params:xml:ns:epp-1.0"
xmlns:eppcom="urn:ietf:params:xml:ns:eppcom-1.0"
xmlns="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified">
<!--
Import common element types.
-->
<import namespace="urn:ietf:params:xml:ns:eppcom-1.0"
schemaLocation="eppcom.xsd"/>
<import namespace="urn:ietf:params:xml:ns:epp-1.0"
schemaLocation="epp.xsd"/>
<annotation>
<documentation>
Extensible Provisioning Protocol v1.0
contact provisioning schema.
</documentation>
</annotation>
<!--
Child elements found in EPP commands.
-->
<element name="check" type="contact:mIDType"/>
<element name="create" type="contact:createType"/>
<element name="delete" type="contact:sIDType"/>
<element name="info" type="contact:authIDType"/>
<element name="transfer" type="contact:authIDType"/>
<element name="update" type="contact:updateType"/>
<!--
Utility types.
-->
<simpleType name="ccType">
<restriction base="token">
<length value="2"/>
</restriction>
</simpleType>
<complexType name="e164Type">
<simpleContent>
<extension base="contact:e164StringType">
<attribute name="x" type="token"/>
</extension>
</simpleContent>
</complexType>
<simpleType name="e164StringType">
<restriction base="token">
<pattern value="(\+[0-9]{1,3}\.[0-9]{1,14})?"/>
<maxLength value="17"/>
</restriction>
</simpleType>
<simpleType name="pcType">
<restriction base="token">
<maxLength value="16"/>
</restriction>
</simpleType>
<simpleType name="postalLineType">
<restriction base="normalizedString">
<minLength value="1"/>
<maxLength value="255"/>
</restriction>
</simpleType>
<simpleType name="optPostalLineType">
<restriction base="normalizedString">
<maxLength value="255"/>
</restriction>
</simpleType>
<!--
Child elements of the <create> command.
-->
<complexType name="createType">
<sequence>
<element name="id" type="eppcom:clIDType"/>
<element name="postalInfo" type="contact:postalInfoType"
maxOccurs="2"/>
<element name="voice" type="contact:e164Type"
minOccurs="0"/>
<element name="fax" type="contact:e164Type"
minOccurs="0"/>
<element name="email" type="eppcom:minTokenType"/>
<element name="authInfo" type="contact:authInfoType"/>
<element name="disclose" type="contact:discloseType"
minOccurs="0"/>
</sequence>
</complexType>
<complexType name="postalInfoType">
<sequence>
<element name="name" type="contact:postalLineType"/>
<element name="org" type="contact:optPostalLineType"
minOccurs="0"/>
<element name="addr" type="contact:addrType"/>
</sequence>
<attribute name="type" type="contact:postalInfoEnumType"
use="required"/>
</complexType>
<simpleType name="postalInfoEnumType">
<restriction base="token">
<enumeration value="loc"/>
<enumeration value="int"/>
</restriction>
</simpleType>
<complexType name="addrType">
<sequence>
<element name="street" type="contact:optPostalLineType"
minOccurs="0" maxOccurs="3"/>
<element name="city" type="contact:postalLineType"/>
<element name="sp" type="contact:optPostalLineType"
minOccurs="0"/>
<element name="pc" type="contact:pcType"
minOccurs="0"/>
<element name="cc" type="contact:ccType"/>
</sequence>
</complexType>
<complexType name="authInfoType">
<choice>
<element name="pw" type="eppcom:pwAuthInfoType"/>
<element name="ext" type="eppcom:extAuthInfoType"/>
</choice>
</complexType>
<complexType name="discloseType">
<sequence>
<element name="name" type="contact:intLocType"
minOccurs="0" maxOccurs="2"/>
<element name="org" type="contact:intLocType"
minOccurs="0" maxOccurs="2"/>
<element name="addr" type="contact:intLocType"
minOccurs="0" maxOccurs="2"/>
<element name="voice" minOccurs="0"/>
<element name="fax" minOccurs="0"/>
<element name="email" minOccurs="0"/>
</sequence>
<attribute name="flag" type="boolean" use="required"/>
</complexType>
<complexType name="intLocType">
<attribute name="type" type="contact:postalInfoEnumType"
use="required"/>
</complexType>
<!--
Child element of commands that require only an identifier.
-->
<complexType name="sIDType">
<sequence>
<element name="id" type="eppcom:clIDType"/>
</sequence>
</complexType>
<!--
Child element of commands that accept multiple identifiers.
-->
<complexType name="mIDType">
<sequence>
<element name="id" type="eppcom:clIDType"
maxOccurs="unbounded"/>
</sequence>
</complexType>
<!--
Child elements of the <info> and <transfer> commands.
-->
<complexType name="authIDType">
<sequence>
<element name="id" type="eppcom:clIDType"/>
<element name="authInfo" type="contact:authInfoType"
minOccurs="0"/>
</sequence>
</complexType>
<!--
Child elements of the <update> command.
-->
<complexType name="updateType">
<sequence>
<element name="id" type="eppcom:clIDType"/>
<element name="add" type="contact:addRemType"
minOccurs="0"/>
<element name="rem" type="contact:addRemType"
minOccurs="0"/>
<element name="chg" type="contact:chgType"
minOccurs="0"/>
</sequence>
</complexType>
<!--
Data elements that can be added or removed.
-->
<complexType name="addRemType">
<sequence>
<element name="status" type="contact:statusType"
maxOccurs="7"/>
</sequence>
</complexType>
<!--
Data elements that can be changed.
-->
<complexType name="chgType">
<sequence>
<element name="postalInfo" type="contact:chgPostalInfoType"
minOccurs="0" maxOccurs="2"/>
<element name="voice" type="contact:e164Type"
minOccurs="0"/>
<element name="fax" type="contact:e164Type"
minOccurs="0"/>
<element name="email" type="eppcom:minTokenType"
minOccurs="0"/>
<element name="authInfo" type="contact:authInfoType"
minOccurs="0"/>
<element name="disclose" type="contact:discloseType"
minOccurs="0"/>
</sequence>
</complexType>
<complexType name="chgPostalInfoType">
<sequence>
<element name="name" type="contact:postalLineType"
minOccurs="0"/>
<element name="org" type="contact:optPostalLineType"
minOccurs="0"/>
<element name="addr" type="contact:addrType"
minOccurs="0"/>
</sequence>
<attribute name="type" type="contact:postalInfoEnumType"
use="required"/>
</complexType>
<!--
Child response elements.
-->
<element name="chkData" type="contact:chkDataType"/>
<element name="creData" type="contact:creDataType"/>
<element name="infData" type="contact:infDataType"/>
<element name="panData" type="contact:panDataType"/>
<element name="trnData" type="contact:trnDataType"/>
<!--
<check> response elements.
-->
<complexType name="chkDataType">
<sequence>
<element name="cd" type="contact:checkType"
maxOccurs="unbounded"/>
</sequence>
</complexType>
<complexType name="checkType">
<sequence>
<element name="id" type="contact:checkIDType"/>
<element name="reason" type="eppcom:reasonType"
minOccurs="0"/>
</sequence>
</complexType>
<complexType name="checkIDType">
<simpleContent>
<extension base="eppcom:clIDType">
<attribute name="avail" type="boolean"
use="required"/>
</extension>
</simpleContent>
</complexType>
<!--
<create> response elements.
-->
<complexType name="creDataType">
<sequence>
<element name="id" type="eppcom:clIDType"/>
<element name="crDate" type="dateTime"/>
</sequence>
</complexType>
<!--
<info> response elements.
-->
<complexType name="infDataType">
<sequence>
<element name="id" type="eppcom:clIDType"/>
<element name="roid" type="eppcom:roidType"/>
<element name="status" type="contact:statusType"
maxOccurs="7"/>
<element name="postalInfo" type="contact:postalInfoType"
maxOccurs="2"/>
<element name="voice" type="contact:e164Type"
minOccurs="0"/>
<element name="fax" type="contact:e164Type"
minOccurs="0"/>
<element name="email" type="eppcom:minTokenType"/>
<element name="clID" type="eppcom:clIDType"/>
<element name="crID" type="eppcom:clIDType"/>
<element name="crDate" type="dateTime"/>
<element name="upID" type="eppcom:clIDType"
minOccurs="0"/>
<element name="upDate" type="dateTime"
minOccurs="0"/>
<element name="trDate" type="dateTime"
minOccurs="0"/>
<element name="authInfo" type="contact:authInfoType"
minOccurs="0"/>
<element name="disclose" type="contact:discloseType"
minOccurs="0"/>
</sequence>
</complexType>
<!--
Status is a combination of attributes and an optional human-readable
message that may be expressed in languages other than English.
-->
<complexType name="statusType">
<simpleContent>
<extension base="normalizedString">
<attribute name="s" type="contact:statusValueType"
use="required"/>
<attribute name="lang" type="language"
default="en"/>
</extension>
</simpleContent>
</complexType>
<simpleType name="statusValueType">
<restriction base="token">
<enumeration value="clientDeleteProhibited"/>
<enumeration value="clientTransferProhibited"/>
<enumeration value="clientUpdateProhibited"/>
<enumeration value="linked"/>
<enumeration value="ok"/>
<enumeration value="pendingCreate"/>
<enumeration value="pendingDelete"/>
<enumeration value="pendingTransfer"/>
<enumeration value="pendingUpdate"/>
<enumeration value="serverDeleteProhibited"/>
<enumeration value="serverTransferProhibited"/>
<enumeration value="serverUpdateProhibited"/>
</restriction>
</simpleType>
<!--
Pending action notification response elements.
-->
<complexType name="panDataType">
<sequence>
<element name="id" type="contact:paCLIDType"/>
<element name="paTRID" type="epp:trIDType"/>
<element name="paDate" type="dateTime"/>
</sequence>
</complexType>
<complexType name="paCLIDType">
<simpleContent>
<extension base="eppcom:clIDType">
<attribute name="paResult" type="boolean"
use="required"/>
</extension>
</simpleContent>
</complexType>
<!--
<transfer> response elements.
-->
<complexType name="trnDataType">
<sequence>
<element name="id" type="eppcom:clIDType"/>
<element name="trStatus" type="eppcom:trStatusType"/>
<element name="reID" type="eppcom:clIDType"/>
<element name="reDate" type="dateTime"/>
<element name="acID" type="eppcom:clIDType"/>
<element name="acDate" type="dateTime"/>
</sequence>
</complexType>
<!--
End of schema.
-->
</schema>

View file

@ -0,0 +1,434 @@
<?xml version="1.0" encoding="UTF-8"?>
<schema targetNamespace="urn:ietf:params:xml:ns:domain-1.0"
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0"
xmlns:host="urn:ietf:params:xml:ns:host-1.0"
xmlns:epp="urn:ietf:params:xml:ns:epp-1.0"
xmlns:eppcom="urn:ietf:params:xml:ns:eppcom-1.0"
xmlns="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified">
<!--
Import common element types.
-->
<import namespace="urn:ietf:params:xml:ns:eppcom-1.0"
schemaLocation="eppcom.xsd"/>
<import namespace="urn:ietf:params:xml:ns:epp-1.0"
schemaLocation="epp.xsd"/>
<import namespace="urn:ietf:params:xml:ns:host-1.0"
schemaLocation="host.xsd"/>
<annotation>
<documentation>
Extensible Provisioning Protocol v1.0
domain provisioning schema.
</documentation>
</annotation>
<!--
Child elements found in EPP commands.
-->
<element name="check" type="domain:mNameType"/>
<element name="create" type="domain:createType"/>
<element name="delete" type="domain:sNameType"/>
<element name="info" type="domain:infoType"/>
<element name="renew" type="domain:renewType"/>
<element name="transfer" type="domain:transferType"/>
<element name="update" type="domain:updateType"/>
<!--
Child elements of the <create> command.
-->
<complexType name="createType">
<sequence>
<element name="name" type="eppcom:labelType"/>
<element name="period" type="domain:periodType"
minOccurs="0"/>
<element name="ns" type="domain:nsType"
minOccurs="0"/>
<element name="registrant" type="eppcom:clIDType"
minOccurs="0"/>
<element name="contact" type="domain:contactType"
minOccurs="0" maxOccurs="unbounded"/>
<element name="authInfo" type="domain:authInfoType"/>
</sequence>
</complexType>
<complexType name="periodType">
<simpleContent>
<extension base="domain:pLimitType">
<attribute name="unit" type="domain:pUnitType"
use="required"/>
</extension>
</simpleContent>
</complexType>
<simpleType name="pLimitType">
<restriction base="unsignedShort">
<minInclusive value="1"/>
<maxInclusive value="99"/>
</restriction>
</simpleType>
<simpleType name="pUnitType">
<restriction base="token">
<enumeration value="y"/>
<enumeration value="m"/>
</restriction>
</simpleType>
<complexType name="nsType">
<choice>
<element name="hostObj" type="eppcom:labelType"
maxOccurs="unbounded"/>
<element name="hostAttr" type="domain:hostAttrType"
maxOccurs="unbounded"/>
</choice>
</complexType>
<!--
Name servers are either host objects or attributes.
-->
<complexType name="hostAttrType">
<sequence>
<element name="hostName" type="eppcom:labelType"/>
<element name="hostAddr" type="host:addrType"
minOccurs="0" maxOccurs="unbounded"/>
</sequence>
</complexType>
<!--
If attributes, addresses are optional and follow the
structure defined in the host mapping.
-->
<complexType name="contactType">
<simpleContent>
<extension base="eppcom:clIDType">
<attribute name="type" type="domain:contactAttrType"/>
</extension>
</simpleContent>
</complexType>
<simpleType name="contactAttrType">
<restriction base="token">
<enumeration value="admin"/>
<enumeration value="billing"/>
<enumeration value="tech"/>
</restriction>
</simpleType>
<complexType name="authInfoType">
<choice>
<element name="pw" type="eppcom:pwAuthInfoType"/>
<element name="ext" type="eppcom:extAuthInfoType"/>
</choice>
</complexType>
<!--
Child element of commands that require a single name.
-->
<complexType name="sNameType">
<sequence>
<element name="name" type="eppcom:labelType"/>
</sequence>
</complexType>
<!--
Child element of commands that accept multiple names.
-->
<complexType name="mNameType">
<sequence>
<element name="name" type="eppcom:labelType"
maxOccurs="unbounded"/>
</sequence>
</complexType>
<!--
Child elements of the <info> command.
-->
<complexType name="infoType">
<sequence>
<element name="name" type="domain:infoNameType"/>
<element name="authInfo" type="domain:authInfoType"
minOccurs="0"/>
</sequence>
</complexType>
<complexType name="infoNameType">
<simpleContent>
<extension base = "eppcom:labelType">
<attribute name="hosts" type="domain:hostsType"
default="all"/>
</extension>
</simpleContent>
</complexType>
<simpleType name="hostsType">
<restriction base="token">
<enumeration value="all"/>
<enumeration value="del"/>
<enumeration value="none"/>
<enumeration value="sub"/>
</restriction>
</simpleType>
<!--
Child elements of the <renew> command.
-->
<complexType name="renewType">
<sequence>
<element name="name" type="eppcom:labelType"/>
<element name="curExpDate" type="date"/>
<element name="period" type="domain:periodType"
minOccurs="0"/>
</sequence>
</complexType>
<!--
Child elements of the <transfer> command.
-->
<complexType name="transferType">
<sequence>
<element name="name" type="eppcom:labelType"/>
<element name="period" type="domain:periodType"
minOccurs="0"/>
<element name="authInfo" type="domain:authInfoType"
minOccurs="0"/>
</sequence>
</complexType>
<!--
Child elements of the <update> command.
-->
<complexType name="updateType">
<sequence>
<element name="name" type="eppcom:labelType"/>
<element name="add" type="domain:addRemType"
minOccurs="0"/>
<element name="rem" type="domain:addRemType"
minOccurs="0"/>
<element name="chg" type="domain:chgType"
minOccurs="0"/>
</sequence>
</complexType>
<!--
Data elements that can be added or removed.
-->
<complexType name="addRemType">
<sequence>
<element name="ns" type="domain:nsType"
minOccurs="0"/>
<element name="contact" type="domain:contactType"
minOccurs="0" maxOccurs="unbounded"/>
<element name="status" type="domain:statusType"
minOccurs="0" maxOccurs="11"/>
</sequence>
</complexType>
<!--
Data elements that can be changed.
-->
<complexType name="chgType">
<sequence>
<element name="registrant" type="domain:clIDChgType"
minOccurs="0"/>
<element name="authInfo" type="domain:authInfoChgType"
minOccurs="0"/>
</sequence>
</complexType>
<!--
Allow the registrant value to be nullified by changing the
minLength restriction to "0".
-->
<simpleType name="clIDChgType">
<restriction base="token">
<minLength value="0"/>
<maxLength value="16"/>
</restriction>
</simpleType>
<!--
Allow the authInfo value to be nullified by including an
empty element within the choice.
-->
<complexType name="authInfoChgType">
<choice>
<element name="pw" type="eppcom:pwAuthInfoType"/>
<element name="ext" type="eppcom:extAuthInfoType"/>
<element name="null"/>
</choice>
</complexType>
<!--
Child response elements.
-->
<element name="chkData" type="domain:chkDataType"/>
<element name="creData" type="domain:creDataType"/>
<element name="infData" type="domain:infDataType"/>
<element name="panData" type="domain:panDataType"/>
<element name="renData" type="domain:renDataType"/>
<element name="trnData" type="domain:trnDataType"/>
<!--
<check> response elements.
-->
<complexType name="chkDataType">
<sequence>
<element name="cd" type="domain:checkType"
maxOccurs="unbounded"/>
</sequence>
</complexType>
<complexType name="checkType">
<sequence>
<element name="name" type="domain:checkNameType"/>
<element name="reason" type="eppcom:reasonType"
minOccurs="0"/>
</sequence>
</complexType>
<complexType name="checkNameType">
<simpleContent>
<extension base="eppcom:labelType">
<attribute name="avail" type="boolean"
use="required"/>
</extension>
</simpleContent>
</complexType>
<!--
<create> response elements.
-->
<complexType name="creDataType">
<sequence>
<element name="name" type="eppcom:labelType"/>
<element name="crDate" type="dateTime"/>
<element name="exDate" type="dateTime"
minOccurs="0"/>
</sequence>
</complexType>
<!--
<info> response elements.
-->
<complexType name="infDataType">
<sequence>
<element name="name" type="eppcom:labelType"/>
<element name="roid" type="eppcom:roidType"/>
<element name="status" type="domain:statusType"
minOccurs="0" maxOccurs="11"/>
<element name="registrant" type="eppcom:clIDType"
minOccurs="0"/>
<element name="contact" type="domain:contactType"
minOccurs="0" maxOccurs="unbounded"/>
<element name="ns" type="domain:nsType"
minOccurs="0"/>
<element name="host" type="eppcom:labelType"
minOccurs="0" maxOccurs="unbounded"/>
<element name="clID" type="eppcom:clIDType"/>
<element name="crID" type="eppcom:clIDType"
minOccurs="0"/>
<element name="crDate" type="dateTime"
minOccurs="0"/>
<element name="upID" type="eppcom:clIDType"
minOccurs="0"/>
<element name="upDate" type="dateTime"
minOccurs="0"/>
<element name="exDate" type="dateTime"
minOccurs="0"/>
<element name="trDate" type="dateTime"
minOccurs="0"/>
<element name="authInfo" type="domain:authInfoType"
minOccurs="0"/>
</sequence>
</complexType>
<!--
Status is a combination of attributes and an optional
human-readable message that may be expressed in languages other
than English.
-->
<complexType name="statusType">
<simpleContent>
<extension base="normalizedString">
<attribute name="s" type="domain:statusValueType"
use="required"/>
<attribute name="lang" type="language"
default="en"/>
</extension>
</simpleContent>
</complexType>
<simpleType name="statusValueType">
<restriction base="token">
<enumeration value="clientDeleteProhibited"/>
<enumeration value="clientHold"/>
<enumeration value="clientRenewProhibited"/>
<enumeration value="clientTransferProhibited"/>
<enumeration value="clientUpdateProhibited"/>
<enumeration value="inactive"/>
<enumeration value="ok"/>
<enumeration value="pendingCreate"/>
<enumeration value="pendingDelete"/>
<enumeration value="pendingRenew"/>
<enumeration value="pendingTransfer"/>
<enumeration value="pendingUpdate"/>
<enumeration value="serverDeleteProhibited"/>
<enumeration value="serverHold"/>
<enumeration value="serverRenewProhibited"/>
<enumeration value="serverTransferProhibited"/>
<enumeration value="serverUpdateProhibited"/>
</restriction>
</simpleType>
<!--
Pending action notification response elements.
-->
<complexType name="panDataType">
<sequence>
<element name="name" type="domain:paNameType"/>
<element name="paTRID" type="epp:trIDType"/>
<element name="paDate" type="dateTime"/>
</sequence>
</complexType>
<complexType name="paNameType">
<simpleContent>
<extension base="eppcom:labelType">
<attribute name="paResult" type="boolean"
use="required"/>
</extension>
</simpleContent>
</complexType>
<!--
<renew> response elements.
-->
<complexType name="renDataType">
<sequence>
<element name="name" type="eppcom:labelType"/>
<element name="exDate" type="dateTime"
minOccurs="0"/>
</sequence>
</complexType>
<!--
<transfer> response elements.
-->
<complexType name="trnDataType">
<sequence>
<element name="name" type="eppcom:labelType"/>
<element name="trStatus" type="eppcom:trStatusType"/>
<element name="reID" type="eppcom:clIDType"/>
<element name="reDate" type="dateTime"/>
<element name="acID" type="eppcom:clIDType"/>
<element name="acDate" type="dateTime"/>
<element name="exDate" type="dateTime"
minOccurs="0"/>
</sequence>
</complexType>
<!--
End of schema.
-->
</schema>

View file

@ -0,0 +1,292 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Schema extracted from http://www.w3.org/2000/09/xmldsig# -->
<schema xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
targetNamespace="http://www.w3.org/2000/09/xmldsig#"
version="0.1" elementFormDefault="qualified">
<!-- Basic Types Defined for Signatures -->
<simpleType name="CryptoBinary">
<restriction base="base64Binary">
</restriction>
</simpleType>
<!-- Start Signature -->
<element name="Signature" type="ds:SignatureType"/>
<complexType name="SignatureType">
<sequence>
<element ref="ds:SignedInfo"/>
<element ref="ds:SignatureValue"/>
<element ref="ds:KeyInfo" minOccurs="0"/>
<element ref="ds:Object" minOccurs="0" maxOccurs="unbounded"/>
</sequence>
<attribute name="Id" type="ID" use="optional"/>
</complexType>
<element name="SignatureValue" type="ds:SignatureValueType"/>
<complexType name="SignatureValueType">
<simpleContent>
<extension base="base64Binary">
<attribute name="Id" type="ID" use="optional"/>
</extension>
</simpleContent>
</complexType>
<!-- Start SignedInfo -->
<element name="SignedInfo" type="ds:SignedInfoType"/>
<complexType name="SignedInfoType">
<sequence>
<element ref="ds:CanonicalizationMethod"/>
<element ref="ds:SignatureMethod"/>
<element ref="ds:Reference" maxOccurs="unbounded"/>
</sequence>
<attribute name="Id" type="ID" use="optional"/>
</complexType>
<element name="CanonicalizationMethod" type="ds:CanonicalizationMethodType"/>
<complexType name="CanonicalizationMethodType" mixed="true">
<sequence>
<any namespace="##any" minOccurs="0" maxOccurs="unbounded"/>
<!-- (0,unbounded) elements from (1,1) namespace -->
</sequence>
<attribute name="Algorithm" type="anyURI" use="required"/>
</complexType>
<element name="SignatureMethod" type="ds:SignatureMethodType"/>
<complexType name="SignatureMethodType" mixed="true">
<sequence>
<element name="HMACOutputLength" minOccurs="0" type="ds:HMACOutputLengthType"/>
<any namespace="##other" minOccurs="0" maxOccurs="unbounded"/>
<!-- (0,unbounded) elements from (1,1) external namespace -->
</sequence>
<attribute name="Algorithm" type="anyURI" use="required"/>
</complexType>
<!-- Start Reference -->
<element name="Reference" type="ds:ReferenceType"/>
<complexType name="ReferenceType">
<sequence>
<element ref="ds:Transforms" minOccurs="0"/>
<element ref="ds:DigestMethod"/>
<element ref="ds:DigestValue"/>
</sequence>
<attribute name="Id" type="ID" use="optional"/>
<attribute name="URI" type="anyURI" use="optional"/>
<attribute name="Type" type="anyURI" use="optional"/>
</complexType>
<element name="Transforms" type="ds:TransformsType"/>
<complexType name="TransformsType">
<sequence>
<element ref="ds:Transform" maxOccurs="unbounded"/>
</sequence>
</complexType>
<element name="Transform" type="ds:TransformType"/>
<complexType name="TransformType" mixed="true">
<choice minOccurs="0" maxOccurs="unbounded">
<any namespace="##other" processContents="lax"/>
<!-- (1,1) elements from (0,unbounded) namespaces -->
<element name="XPath" type="string"/>
</choice>
<attribute name="Algorithm" type="anyURI" use="required"/>
</complexType>
<!-- End Reference -->
<element name="DigestMethod" type="ds:DigestMethodType"/>
<complexType name="DigestMethodType" mixed="true">
<sequence>
<any namespace="##other" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
</sequence>
<attribute name="Algorithm" type="anyURI" use="required"/>
</complexType>
<element name="DigestValue" type="ds:DigestValueType"/>
<simpleType name="DigestValueType">
<restriction base="base64Binary"/>
</simpleType>
<!-- End SignedInfo -->
<!-- Start KeyInfo -->
<element name="KeyInfo" type="ds:KeyInfoType"/>
<complexType name="KeyInfoType" mixed="true">
<choice maxOccurs="unbounded">
<element ref="ds:KeyName"/>
<element ref="ds:KeyValue"/>
<element ref="ds:RetrievalMethod"/>
<element ref="ds:X509Data"/>
<element ref="ds:PGPData"/>
<element ref="ds:SPKIData"/>
<element ref="ds:MgmtData"/>
<any processContents="lax" namespace="##other"/>
<!-- (1,1) elements from (0,unbounded) namespaces -->
</choice>
<attribute name="Id" type="ID" use="optional"/>
</complexType>
<element name="KeyName" type="string"/>
<element name="MgmtData" type="string"/>
<element name="KeyValue" type="ds:KeyValueType"/>
<complexType name="KeyValueType" mixed="true">
<choice>
<element ref="ds:DSAKeyValue"/>
<element ref="ds:RSAKeyValue"/>
<any namespace="##other" processContents="lax"/>
</choice>
</complexType>
<element name="RetrievalMethod" type="ds:RetrievalMethodType"/>
<complexType name="RetrievalMethodType">
<sequence>
<element ref="ds:Transforms" minOccurs="0"/>
</sequence>
<attribute name="URI" type="anyURI"/>
<attribute name="Type" type="anyURI" use="optional"/>
</complexType>
<!-- Start X509Data -->
<element name="X509Data" type="ds:X509DataType"/>
<complexType name="X509DataType">
<sequence maxOccurs="unbounded">
<choice>
<element name="X509IssuerSerial" type="ds:X509IssuerSerialType"/>
<element name="X509SKI" type="base64Binary"/>
<element name="X509SubjectName" type="string"/>
<element name="X509Certificate" type="base64Binary"/>
<element name="X509CRL" type="base64Binary"/>
<any namespace="##other" processContents="lax"/>
</choice>
</sequence>
</complexType>
<complexType name="X509IssuerSerialType">
<sequence>
<element name="X509IssuerName" type="string"/>
<element name="X509SerialNumber" type="integer"/>
</sequence>
</complexType>
<!-- End X509Data -->
<!-- Begin PGPData -->
<element name="PGPData" type="ds:PGPDataType"/>
<complexType name="PGPDataType">
<choice>
<sequence>
<element name="PGPKeyID" type="base64Binary"/>
<element name="PGPKeyPacket" type="base64Binary" minOccurs="0"/>
<any namespace="##other" processContents="lax" minOccurs="0"
maxOccurs="unbounded"/>
</sequence>
<sequence>
<element name="PGPKeyPacket" type="base64Binary"/>
<any namespace="##other" processContents="lax" minOccurs="0"
maxOccurs="unbounded"/>
</sequence>
</choice>
</complexType>
<!-- End PGPData -->
<!-- Begin SPKIData -->
<element name="SPKIData" type="ds:SPKIDataType"/>
<complexType name="SPKIDataType">
<sequence maxOccurs="unbounded">
<element name="SPKISexp" type="base64Binary"/>
<any namespace="##other" processContents="lax" minOccurs="0"/>
</sequence>
</complexType>
<!-- End SPKIData -->
<!-- End KeyInfo -->
<!-- Start Object (Manifest, SignatureProperty) -->
<element name="Object" type="ds:ObjectType"/>
<complexType name="ObjectType" mixed="true">
<sequence minOccurs="0" maxOccurs="unbounded">
<any namespace="##any" processContents="lax"/>
</sequence>
<attribute name="Id" type="ID" use="optional"/>
<attribute name="MimeType" type="string" use="optional"/> <!-- add a grep facet -->
<attribute name="Encoding" type="anyURI" use="optional"/>
</complexType>
<element name="Manifest" type="ds:ManifestType"/>
<complexType name="ManifestType">
<sequence>
<element ref="ds:Reference" maxOccurs="unbounded"/>
</sequence>
<attribute name="Id" type="ID" use="optional"/>
</complexType>
<element name="SignatureProperties" type="ds:SignaturePropertiesType"/>
<complexType name="SignaturePropertiesType">
<sequence>
<element ref="ds:SignatureProperty" maxOccurs="unbounded"/>
</sequence>
<attribute name="Id" type="ID" use="optional"/>
</complexType>
<element name="SignatureProperty" type="ds:SignaturePropertyType"/>
<complexType name="SignaturePropertyType" mixed="true">
<choice maxOccurs="unbounded">
<any namespace="##other" processContents="lax"/>
<!-- (1,1) elements from (1,unbounded) namespaces -->
</choice>
<attribute name="Target" type="anyURI" use="required"/>
<attribute name="Id" type="ID" use="optional"/>
</complexType>
<!-- End Object (Manifest, SignatureProperty) -->
<!-- Start Algorithm Parameters -->
<simpleType name="HMACOutputLengthType">
<restriction base="integer"/>
</simpleType>
<!-- Start KeyValue Element-types -->
<element name="DSAKeyValue" type="ds:DSAKeyValueType"/>
<complexType name="DSAKeyValueType">
<sequence>
<sequence minOccurs="0">
<element name="P" type="ds:CryptoBinary"/>
<element name="Q" type="ds:CryptoBinary"/>
</sequence>
<element name="G" type="ds:CryptoBinary" minOccurs="0"/>
<element name="Y" type="ds:CryptoBinary"/>
<element name="J" type="ds:CryptoBinary" minOccurs="0"/>
<sequence minOccurs="0">
<element name="Seed" type="ds:CryptoBinary"/>
<element name="PgenCounter" type="ds:CryptoBinary"/>
</sequence>
</sequence>
</complexType>
<element name="RSAKeyValue" type="ds:RSAKeyValueType"/>
<complexType name="RSAKeyValueType">
<sequence>
<element name="Modulus" type="ds:CryptoBinary"/>
<element name="Exponent" type="ds:CryptoBinary"/>
</sequence>
</complexType>
<!-- End KeyValue Element-types -->
<!-- End Signature -->
</schema>

View file

@ -0,0 +1,445 @@
<?xml version="1.0" encoding="UTF-8"?>
<schema targetNamespace="urn:ietf:params:xml:ns:epp-1.0"
xmlns:epp="urn:ietf:params:xml:ns:epp-1.0"
xmlns:eppcom="urn:ietf:params:xml:ns:eppcom-1.0"
xmlns="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified">
<!--
Import common element types.
-->
<import namespace="urn:ietf:params:xml:ns:eppcom-1.0"
schemaLocation="eppcom.xsd"/>
<annotation>
<documentation>
Extensible Provisioning Protocol v1.0 schema.
</documentation>
</annotation>
<!--
Every EPP XML instance must begin with this element.
-->
<element name="epp" type="epp:eppType"/>
<!--
An EPP XML instance must contain a greeting, hello, command,
response, or extension.
-->
<complexType name="eppType">
<choice>
<element name="greeting" type="epp:greetingType"/>
<element name="hello"/>
<element name="command" type="epp:commandType"/>
<element name="response" type="epp:responseType"/>
<element name="extension" type="epp:extAnyType"/>
</choice>
</complexType>
<!--
A greeting is sent by a server in response to a client connection
or <hello>.
-->
<complexType name="greetingType">
<sequence>
<element name="svID" type="epp:sIDType"/>
<element name="svDate" type="dateTime"/>
<element name="svcMenu" type="epp:svcMenuType"/>
<element name="dcp" type="epp:dcpType"/>
</sequence>
</complexType>
<!--
Server IDs are strings with minimum and maximum length restrictions.
-->
<simpleType name="sIDType">
<restriction base="normalizedString">
<minLength value="3"/>
<maxLength value="64"/>
</restriction>
</simpleType>
<!--
A server greeting identifies available object services.
-->
<complexType name="svcMenuType">
<sequence>
<element name="version" type="epp:versionType"
maxOccurs="unbounded"/>
<element name="lang" type="language"
maxOccurs="unbounded"/>
<element name="objURI" type="anyURI"
maxOccurs="unbounded"/>
<element name="svcExtension" type="epp:extURIType"
minOccurs="0"/>
</sequence>
</complexType>
<!--
Data Collection Policy types.
-->
<complexType name="dcpType">
<sequence>
<element name="access" type="epp:dcpAccessType"/>
<element name="statement" type="epp:dcpStatementType"
maxOccurs="unbounded"/>
<element name="expiry" type="epp:dcpExpiryType"
minOccurs="0"/>
</sequence>
</complexType>
<complexType name="dcpAccessType">
<choice>
<element name="all"/>
<element name="none"/>
<element name="null"/>
<element name="other"/>
<element name="personal"/>
<element name="personalAndOther"/>
</choice>
</complexType>
<complexType name="dcpStatementType">
<sequence>
<element name="purpose" type="epp:dcpPurposeType"/>
<element name="recipient" type="epp:dcpRecipientType"/>
<element name="retention" type="epp:dcpRetentionType"/>
</sequence>
</complexType>
<complexType name="dcpPurposeType">
<sequence>
<element name="admin"
minOccurs="0"/>
<element name="contact"
minOccurs="0"/>
<element name="other"
minOccurs="0"/>
<element name="prov"
minOccurs="0"/>
</sequence>
</complexType>
<complexType name="dcpRecipientType">
<sequence>
<element name="other"
minOccurs="0"/>
<element name="ours" type="epp:dcpOursType"
minOccurs="0" maxOccurs="unbounded"/>
<element name="public"
minOccurs="0"/>
<element name="same"
minOccurs="0"/>
<element name="unrelated"
minOccurs="0"/>
</sequence>
</complexType>
<complexType name="dcpOursType">
<sequence>
<element name="recDesc" type="epp:dcpRecDescType"
minOccurs="0"/>
</sequence>
</complexType>
<simpleType name="dcpRecDescType">
<restriction base="token">
<minLength value="1"/>
<maxLength value="255"/>
</restriction>
</simpleType>
<complexType name="dcpRetentionType">
<choice>
<element name="business"/>
<element name="indefinite"/>
<element name="legal"/>
<element name="none"/>
<element name="stated"/>
</choice>
</complexType>
<complexType name="dcpExpiryType">
<choice>
<element name="absolute" type="dateTime"/>
<element name="relative" type="duration"/>
</choice>
</complexType>
<!--
Extension framework types.
-->
<complexType name="extAnyType">
<sequence>
<any namespace="##other"
maxOccurs="unbounded"/>
</sequence>
</complexType>
<complexType name="extURIType">
<sequence>
<element name="extURI" type="anyURI"
maxOccurs="unbounded"/>
</sequence>
</complexType>
<!--
An EPP version number is a dotted pair of decimal numbers.
-->
<simpleType name="versionType">
<restriction base="token">
<pattern value="[1-9]+\.[0-9]+"/>
<enumeration value="1.0"/>
</restriction>
</simpleType>
<!--
Command types.
-->
<complexType name="commandType">
<sequence>
<choice>
<element name="check" type="epp:readWriteType"/>
<element name="create" type="epp:readWriteType"/>
<element name="delete" type="epp:readWriteType"/>
<element name="info" type="epp:readWriteType"/>
<element name="login" type="epp:loginType"/>
<element name="logout"/>
<element name="poll" type="epp:pollType"/>
<element name="renew" type="epp:readWriteType"/>
<element name="transfer" type="epp:transferType"/>
<element name="update" type="epp:readWriteType"/>
</choice>
<element name="extension" type="epp:extAnyType"
minOccurs="0"/>
<element name="clTRID" type="epp:trIDStringType"
minOccurs="0"/>
</sequence>
</complexType>
<!--
The <login> command.
-->
<complexType name="loginType">
<sequence>
<element name="clID" type="eppcom:clIDType"/>
<element name="pw" type="epp:pwType"/>
<element name="newPW" type="epp:pwType"
minOccurs="0"/>
<element name="options" type="epp:credsOptionsType"/>
<element name="svcs" type="epp:loginSvcType"/>
</sequence>
</complexType>
<complexType name="credsOptionsType">
<sequence>
<element name="version" type="epp:versionType"/>
<element name="lang" type="language"/>
</sequence>
</complexType>
<simpleType name="pwType">
<restriction base="token">
<minLength value="6"/>
<maxLength value="16"/>
</restriction>
</simpleType>
<complexType name="loginSvcType">
<sequence>
<element name="objURI" type="anyURI"
maxOccurs="unbounded"/>
<element name="svcExtension" type="epp:extURIType"
minOccurs="0"/>
</sequence>
</complexType>
<!--
The <poll> command.
-->
<complexType name="pollType">
<attribute name="op" type="epp:pollOpType"
use="required"/>
<attribute name="msgID" type="token"/>
</complexType>
<simpleType name="pollOpType">
<restriction base="token">
<enumeration value="ack"/>
<enumeration value="req"/>
</restriction>
</simpleType>
<!--
The <transfer> command. This is object-specific, and uses attributes
to identify the requested operation.
-->
<complexType name="transferType">
<sequence>
<any namespace="##other"/>
</sequence>
<attribute name="op" type="epp:transferOpType"
use="required"/>
</complexType>
<simpleType name="transferOpType">
<restriction base="token">
<enumeration value="approve"/>
<enumeration value="cancel"/>
<enumeration value="query"/>
<enumeration value="reject"/>
<enumeration value="request"/>
</restriction>
</simpleType>
<!--
All other object-centric commands. EPP doesn't specify the syntax or
semantics of object-centric command elements. The elements MUST be
described in detail in another schema specific to the object.
-->
<complexType name="readWriteType">
<sequence>
<any namespace="##other"/>
</sequence>
</complexType>
<complexType name="trIDType">
<sequence>
<element name="clTRID" type="epp:trIDStringType"
minOccurs="0"/>
<element name="svTRID" type="epp:trIDStringType"/>
</sequence>
</complexType>
<simpleType name="trIDStringType">
<restriction base="token">
<minLength value="3"/>
<maxLength value="64"/>
</restriction>
</simpleType>
<!--
Response types.
-->
<complexType name="responseType">
<sequence>
<element name="result" type="epp:resultType"
maxOccurs="unbounded"/>
<element name="msgQ" type="epp:msgQType"
minOccurs="0"/>
<element name="resData" type="epp:extAnyType"
minOccurs="0"/>
<element name="extension" type="epp:extAnyType"
minOccurs="0"/>
<element name="trID" type="epp:trIDType"/>
</sequence>
</complexType>
<complexType name="resultType">
<sequence>
<element name="msg" type="epp:msgType"/>
<choice minOccurs="0" maxOccurs="unbounded">
<element name="value" type="epp:errValueType"/>
<element name="extValue" type="epp:extErrValueType"/>
</choice>
</sequence>
<attribute name="code" type="epp:resultCodeType"
use="required"/>
</complexType>
<complexType name="errValueType" mixed="true">
<sequence>
<any namespace="##any" processContents="skip"/>
</sequence>
<anyAttribute namespace="##any" processContents="skip"/>
</complexType>
<complexType name="extErrValueType">
<sequence>
<element name="value" type="epp:errValueType"/>
<element name="reason" type="epp:msgType"/>
</sequence>
</complexType>
<complexType name="msgQType">
<sequence>
<element name="qDate" type="dateTime"
minOccurs="0"/>
<element name="msg" type="epp:mixedMsgType"
minOccurs="0"/>
</sequence>
<attribute name="count" type="unsignedLong"
use="required"/>
<attribute name="id" type="eppcom:minTokenType"
use="required"/>
</complexType>
<complexType name="mixedMsgType" mixed="true">
<sequence>
<any processContents="skip"
minOccurs="0" maxOccurs="unbounded"/>
</sequence>
<attribute name="lang" type="language"
default="en"/>
</complexType>
<!--
Human-readable text may be expressed in languages other than English.
-->
<complexType name="msgType">
<simpleContent>
<extension base="normalizedString">
<attribute name="lang" type="language"
default="en"/>
</extension>
</simpleContent>
</complexType>
<!--
EPP result codes.
-->
<simpleType name="resultCodeType">
<restriction base="unsignedShort">
<enumeration value="1000"/>
<enumeration value="1001"/>
<enumeration value="1300"/>
<enumeration value="1301"/>
<enumeration value="1500"/>
<enumeration value="2000"/>
<enumeration value="2001"/>
<enumeration value="2002"/>
<enumeration value="2003"/>
<enumeration value="2004"/>
<enumeration value="2005"/>
<enumeration value="2100"/>
<enumeration value="2101"/>
<enumeration value="2102"/>
<enumeration value="2103"/>
<enumeration value="2104"/>
<enumeration value="2105"/>
<enumeration value="2106"/>
<enumeration value="2200"/>
<enumeration value="2201"/>
<enumeration value="2202"/>
<enumeration value="2300"/>
<enumeration value="2301"/>
<enumeration value="2302"/>
<enumeration value="2303"/>
<enumeration value="2304"/>
<enumeration value="2305"/>
<enumeration value="2306"/>
<enumeration value="2307"/>
<enumeration value="2308"/>
<enumeration value="2400"/>
<enumeration value="2500"/>
<enumeration value="2501"/>
<enumeration value="2502"/>
</restriction>
</simpleType>
<!--
End of schema.
-->
</schema>

View file

@ -0,0 +1,107 @@
<?xml version="1.0" encoding="UTF-8"?>
<schema targetNamespace="urn:ietf:params:xml:ns:eppcom-1.0"
xmlns:eppcom="urn:ietf:params:xml:ns:eppcom-1.0"
xmlns="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified">
<annotation>
<documentation>
Extensible Provisioning Protocol v1.0
shared structures schema.
</documentation>
</annotation>
<!--
Object authorization information types.
-->
<complexType name="pwAuthInfoType">
<simpleContent>
<extension base="normalizedString">
<attribute name="roid" type="eppcom:roidType"/>
</extension>
</simpleContent>
</complexType>
<complexType name="extAuthInfoType">
<sequence>
<any namespace="##other"/>
</sequence>
</complexType>
<!--
<check> response types.
-->
<complexType name="reasonType">
<simpleContent>
<extension base="eppcom:reasonBaseType">
<attribute name="lang" type="language"/>
</extension>
</simpleContent>
</complexType>
<simpleType name="reasonBaseType">
<restriction base="token">
<minLength value="1"/>
<maxLength value="32"/>
</restriction>
</simpleType>
<!--
Abstract client and object identifier type.
-->
<simpleType name="clIDType">
<restriction base="token">
<minLength value="3"/>
<maxLength value="16"/>
</restriction>
</simpleType>
<!--
DNS label type.
-->
<simpleType name="labelType">
<restriction base="token">
<minLength value="1"/>
<maxLength value="255"/>
</restriction>
</simpleType>
<!--
Non-empty token type.
-->
<simpleType name="minTokenType">
<restriction base="token">
<minLength value="1"/>
</restriction>
</simpleType>
<!--
Repository Object IDentifier type.
-->
<simpleType name="roidType">
<restriction base="token">
<pattern value="(\w|_){1,80}-\w{1,8}"/>
</restriction>
</simpleType>
<!--
Transfer status identifiers.
-->
<simpleType name="trStatusType">
<restriction base="token">
<enumeration value="clientApproved"/>
<enumeration value="clientCancelled"/>
<enumeration value="clientRejected"/>
<enumeration value="pending"/>
<enumeration value="serverApproved"/>
<enumeration value="serverCancelled"/>
</restriction>
</simpleType>
<!--
End of schema.
-->
</schema>

View file

@ -0,0 +1,242 @@
<?xml version="1.0" encoding="UTF-8"?>
<schema targetNamespace="urn:ietf:params:xml:ns:host-1.0"
xmlns:host="urn:ietf:params:xml:ns:host-1.0"
xmlns:epp="urn:ietf:params:xml:ns:epp-1.0"
xmlns:eppcom="urn:ietf:params:xml:ns:eppcom-1.0"
xmlns="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified">
<!--
Import common element types.
-->
<import namespace="urn:ietf:params:xml:ns:eppcom-1.0"
schemaLocation="eppcom.xsd"/>
<import namespace="urn:ietf:params:xml:ns:epp-1.0"
schemaLocation="epp.xsd"/>
<annotation>
<documentation>
Extensible Provisioning Protocol v1.0
host provisioning schema.
</documentation>
</annotation>
<!--
Child elements found in EPP commands.
-->
<element name="check" type="host:mNameType"/>
<element name="create" type="host:createType"/>
<element name="delete" type="host:sNameType"/>
<element name="info" type="host:sNameType"/>
<element name="update" type="host:updateType"/>
<!--
Child elements of the <create> command.
-->
<complexType name="createType">
<sequence>
<element name="name" type="eppcom:labelType"/>
<element name="addr" type="host:addrType"
minOccurs="0" maxOccurs="unbounded"/>
</sequence>
</complexType>
<complexType name="addrType">
<simpleContent>
<extension base="host:addrStringType">
<attribute name="ip" type="host:ipType"
default="v4"/>
</extension>
</simpleContent>
</complexType>
<simpleType name="addrStringType">
<restriction base="token">
<minLength value="3"/>
<maxLength value="45"/>
</restriction>
</simpleType>
<simpleType name="ipType">
<restriction base="token">
<enumeration value="v4"/>
<enumeration value="v6"/>
</restriction>
</simpleType>
<!--
Child elements of the <delete> and <info> commands.
-->
<complexType name="sNameType">
<sequence>
<element name="name" type="eppcom:labelType"/>
</sequence>
</complexType>
<!--
Child element of commands that accept multiple names.
-->
<complexType name="mNameType">
<sequence>
<element name="name" type="eppcom:labelType"
maxOccurs="unbounded"/>
</sequence>
</complexType>
<!--
Child elements of the <update> command.
-->
<complexType name="updateType">
<sequence>
<element name="name" type="eppcom:labelType"/>
<element name="add" type="host:addRemType"
minOccurs="0"/>
<element name="rem" type="host:addRemType"
minOccurs="0"/>
<element name="chg" type="host:chgType"
minOccurs="0"/>
</sequence>
</complexType>
<!--
Data elements that can be added or removed.
-->
<complexType name="addRemType">
<sequence>
<element name="addr" type="host:addrType"
minOccurs="0" maxOccurs="unbounded"/>
<element name="status" type="host:statusType"
minOccurs="0" maxOccurs="7"/>
</sequence>
</complexType>
<!--
Data elements that can be changed.
-->
<complexType name="chgType">
<sequence>
<element name="name" type="eppcom:labelType"/>
</sequence>
</complexType>
<!--
Child response elements.
-->
<element name="chkData" type="host:chkDataType"/>
<element name="creData" type="host:creDataType"/>
<element name="infData" type="host:infDataType"/>
<element name="panData" type="host:panDataType"/>
<!--
<check> response elements.
-->
<complexType name="chkDataType">
<sequence>
<element name="cd" type="host:checkType"
maxOccurs="unbounded"/>
</sequence>
</complexType>
<complexType name="checkType">
<sequence>
<element name="name" type="host:checkNameType"/>
<element name="reason" type="eppcom:reasonType"
minOccurs="0"/>
</sequence>
</complexType>
<complexType name="checkNameType">
<simpleContent>
<extension base="eppcom:labelType">
<attribute name="avail" type="boolean"
use="required"/>
</extension>
</simpleContent>
</complexType>
<!--
<create> response elements.
-->
<complexType name="creDataType">
<sequence>
<element name="name" type="eppcom:labelType"/>
<element name="crDate" type="dateTime"/>
</sequence>
</complexType>
<!--
<info> response elements.
-->
<complexType name="infDataType">
<sequence>
<element name="name" type="eppcom:labelType"/>
<element name="roid" type="eppcom:roidType"/>
<element name="status" type="host:statusType"
maxOccurs="7"/>
<element name="addr" type="host:addrType"
minOccurs="0" maxOccurs="unbounded"/>
<element name="clID" type="eppcom:clIDType"/>
<element name="crID" type="eppcom:clIDType"/>
<element name="crDate" type="dateTime"/>
<element name="upID" type="eppcom:clIDType"
minOccurs="0"/>
<element name="upDate" type="dateTime"
minOccurs="0"/>
<element name="trDate" type="dateTime"
minOccurs="0"/>
</sequence>
</complexType>
<!--
Status is a combination of attributes and an optional human-readable
message that may be expressed in languages other than English.
-->
<complexType name="statusType">
<simpleContent>
<extension base="normalizedString">
<attribute name="s" type="host:statusValueType"
use="required"/>
<attribute name="lang" type="language"
default="en"/>
</extension>
</simpleContent>
</complexType>
<simpleType name="statusValueType">
<restriction base="token">
<enumeration value="clientDeleteProhibited"/>
<enumeration value="clientUpdateProhibited"/>
<enumeration value="linked"/>
<enumeration value="ok"/>
<enumeration value="pendingCreate"/>
<enumeration value="pendingDelete"/>
<enumeration value="pendingTransfer"/>
<enumeration value="pendingUpdate"/>
<enumeration value="serverDeleteProhibited"/>
<enumeration value="serverUpdateProhibited"/>
</restriction>
</simpleType>
<!--
Pending action notification response elements.
-->
<complexType name="panDataType">
<sequence>
<element name="name" type="host:paNameType"/>
<element name="paTRID" type="epp:trIDType"/>
<element name="paDate" type="dateTime"/>
</sequence>
</complexType>
<complexType name="paNameType">
<simpleContent>
<extension base="eppcom:labelType">
<attribute name="paResult" type="boolean"
use="required"/>
</extension>
</simpleContent>
</complexType>
<!--
End of schema.
-->
</schema>

View file

@ -0,0 +1,246 @@
<?xml version="1.0" encoding="UTF-8"?>
<schema targetNamespace="urn:ietf:params:xml:ns:launch-1.0" xmlns:launch="urn:ietf:params:xml:ns:launch-1.0"
xmlns:eppcom="urn:ietf:params:xml:ns:eppcom-1.0" xmlns:mark="urn:ietf:params:xml:ns:mark-1.0"
xmlns:smd="urn:ietf:params:xml:ns:signedMark-1.0" xmlns="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified">
<!-- Import common element types. -->
<import namespace="urn:ietf:params:xml:ns:eppcom-1.0" schemaLocation="eppcom.xsd" />
<import namespace="urn:ietf:params:xml:ns:mark-1.0" schemaLocation="mark.xsd" />
<import namespace="urn:ietf:params:xml:ns:signedMark-1.0" schemaLocation="smd.xsd" />
<annotation>
<documentation>
Extensible Provisioning Protocol v1.0
domain name extension
schema
for the launch phase processing.
</documentation>
</annotation>
<!-- Child elements found in EPP commands. -->
<element name="check" type="launch:checkType" />
<element name="info" type="launch:infoType" />
<element name="create" type="launch:createType" />
<element name="update" type="launch:idContainerType" />
<element name="delete" type="launch:idContainerType" />
<!-- Common container of id (identifier) element -->
<complexType name="idContainerType">
<sequence>
<element name="phase" type="launch:phaseType" />
<element name="applicationID" type="launch:applicationIDType" />
</sequence>
</complexType>
<!-- Definition for application identifier -->
<simpleType name="applicationIDType">
<restriction base="token" />
</simpleType>
<!-- Definition for launch phase. Name is an optional attribute used to extend
the phase type. For example, when using the phase type value of &qt;custom&gt;, the
name can be used to specify the custom phase. -->
<complexType name="phaseType">
<simpleContent>
<extension base="launch:phaseTypeValue">
<attribute name="name" type="token" />
</extension>
</simpleContent>
</complexType>
<!-- Enumeration of for launch phase values. -->
<simpleType name="phaseTypeValue">
<restriction base="token">
<enumeration value="sunrise" />
<enumeration value="landrush" />
<enumeration value="claims" />
<enumeration value="open" />
<enumeration value="custom" />
</restriction>
</simpleType>
<!-- Definition for the sunrise code -->
<simpleType name="codeValue">
<restriction base="token">
<minLength value="1" />
</restriction>
</simpleType>
<complexType name="codeType">
<simpleContent>
<extension base="launch:codeValue">
<attribute name="validatorID" type="launch:validatorIDType"
use="optional" />
</extension>
</simpleContent>
</complexType>
<!-- Definition for the notice identifier -->
<simpleType name="noticeIDValue">
<restriction base="token">
<minLength value="1" />
</restriction>
</simpleType>
<complexType name="noticeIDType">
<simpleContent>
<extension base="launch:noticeIDValue">
<attribute name="validatorID" type="launch:validatorIDType"
use="optional" />
</extension>
</simpleContent>
</complexType>
<!-- Definition for the validator identifier -->
<simpleType name="validatorIDType">
<restriction base="token">
<minLength value="1" />
</restriction>
</simpleType>
<!-- Possible status values for sunrise application -->
<simpleType name="statusValueType">
<restriction base="token">
<enumeration value="pendingValidation" />
<enumeration value="validated" />
<enumeration value="invalid" />
<enumeration value="pendingAllocation" />
<enumeration value="allocated" />
<enumeration value="rejected" />
<enumeration value="custom" />
</restriction>
</simpleType>
<!-- Status type definition -->
<complexType name="statusType">
<simpleContent>
<extension base="normalizedString">
<attribute name="s" type="launch:statusValueType" use="required" />
<attribute name="lang" type="language" default="en" />
<attribute name="name" type="token" />
</extension>
</simpleContent>
</complexType>
<!-- codeMark Type that contains an optional code with mark information. -->
<complexType name="codeMarkType">
<sequence>
<element name="code" type="launch:codeType" minOccurs="0" />
<element ref="mark:abstractMark" minOccurs="0" />
</sequence>
</complexType>
<!-- Child elements for the create command -->
<complexType name="createType">
<sequence>
<element name="phase" type="launch:phaseType" />
<choice minOccurs="0">
<element name="codeMark" type="launch:codeMarkType" maxOccurs="unbounded" />
<element ref="smd:abstractSignedMark" maxOccurs="unbounded" />
<element ref="smd:encodedSignedMark" maxOccurs="unbounded" />
</choice>
<element name="notice" minOccurs="0" type="launch:createNoticeType" />
</sequence>
<attribute name="type" type="launch:objectType" />
</complexType>
<!-- Type of launch object -->
<simpleType name="objectType">
<restriction base="token">
<enumeration value="application" />
<enumeration value="registration" />
</restriction>
</simpleType>
<!-- Child elements of the create notice element. -->
<complexType name="createNoticeType">
<sequence>
<element name="noticeID" type="launch:noticeIDType" />
<element name="notAfter" type="dateTime" />
<element name="acceptedDate" type="dateTime" />
</sequence>
</complexType>
<!-- Child elements of check (Claims Check Command). -->
<complexType name="checkType">
<sequence>
<element name="phase" type="launch:phaseType" />
</sequence>
<attribute name="type" type="launch:checkFormType" default="claims" />
</complexType>
<!-- Type of check form (claims check or availability check) -->
<simpleType name="checkFormType">
<restriction base="token">
<enumeration value="claims" />
<enumeration value="avail" />
</restriction>
</simpleType>
<!-- Child elements of info command. -->
<complexType name="infoType">
<sequence>
<element name="phase" type="launch:phaseType" />
<element name="applicationID" type="launch:applicationIDType"
minOccurs="0" />
</sequence>
<attribute name="includeMark" type="boolean" default="false" />
</complexType>
<!-- Child response elements. -->
<element name="chkData" type="launch:chkDataType" />
<element name="creData" type="launch:idContainerType" />
<element name="infData" type="launch:infDataType" />
<!-- <check> response elements. -->
<complexType name="chkDataType">
<sequence>
<element name="phase" type="launch:phaseType" />
<element name="cd" type="launch:cdType" maxOccurs="unbounded" />
</sequence>
</complexType>
<complexType name="cdType">
<sequence>
<element name="name" type="launch:cdNameType" />
<element name="claimKey" type="launch:claimKeyType" minOccurs="0" />
</sequence>
</complexType>
<complexType name="cdNameType">
<simpleContent>
<extension base="eppcom:labelType">
<attribute name="exists" type="boolean" use="required" />
</extension>
</simpleContent>
</complexType>
<complexType name="claimKeyType">
<simpleContent>
<extension base="token">
<attribute name="validatorID" type="launch:validatorIDType"
use="optional" />
</extension>
</simpleContent>
</complexType>
<!-- <info> response elements -->
<complexType name="infDataType">
<sequence>
<element name="phase" type="launch:phaseType" />
<element name="applicationID" type="launch:applicationIDType"
minOccurs="0" />
<element name="status" type="launch:statusType" minOccurs="0" />
<element ref="mark:abstractMark" minOccurs="0" maxOccurs="unbounded" />
</sequence>
</complexType>
</schema>

View file

@ -0,0 +1,246 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Schema extracted from http://tools.ietf.org/html/draft-lozano-tmch-smd -->
<schema
targetNamespace="urn:ietf:params:xml:ns:mark-1.0"
xmlns:mark="urn:ietf:params:xml:ns:mark-1.0"
xmlns="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified">
<annotation>
<documentation>
Schema for representing a Trademark, also referred to
as Mark.
</documentation>
</annotation>
<!--
Abstract mark for replacement via substitution.
-->
<element name="abstractMark" type="mark:abstractMarkType"
abstract="true"/>
<!--
<mark:mark> element definition
-->
<element name="mark" type="mark:markType"
substitutionGroup="mark:abstractMark"/>
<!--
Empty type for use in extending for a mark
-->
<complexType name="abstractMarkType"/>
<!--
<mark:mark> child elements
-->
<complexType name="markType">
<complexContent>
<extension base="mark:abstractMarkType">
<sequence>
<element name="trademark" type="mark:trademarkType"
minOccurs="0" maxOccurs="unbounded"/>
<element name="treatyOrStatute"
type="mark:treatyOrStatuteType" minOccurs="0"
maxOccurs="unbounded"/>
<element name="court" type="mark:courtType" minOccurs="0"
maxOccurs="unbounded"/>
</sequence>
</extension>
</complexContent>
</complexType>
<complexType name="holderType">
<sequence>
<element name="name" type="token" minOccurs="0"/>
<element name="org" type="token" minOccurs="0"/>
<element name="addr" type="mark:addrType"/>
<element name="voice" type="mark:e164Type" minOccurs="0"/>
<element name="fax" type="mark:e164Type" minOccurs="0"/>
<element name="email" type="mark:minTokenType" minOccurs="0"/>
</sequence>
<attribute name="entitlement" type="mark:entitlementType"/>
</complexType>
<complexType name="contactType">
<sequence>
<element name="name" type="token"/>
<element name="org" type="token" minOccurs="0"/>
<element name="addr" type="mark:addrType"/>
<element name="voice" type="mark:e164Type"/>
<element name="fax" type="mark:e164Type" minOccurs="0"/>
<element name="email" type="mark:minTokenType"/>
</sequence>
<attribute name="type" type="mark:contactTypeType"/>
</complexType>
<complexType name="trademarkType">
<sequence>
<element name="id" type="mark:idType"/>
<element name="markName" type="token"/>
<element name="holder" type="mark:holderType"
maxOccurs="unbounded" />
<element name="contact" type="mark:contactType" minOccurs="0"
maxOccurs="unbounded"/>
<element name="jurisdiction" type="mark:ccType"/>
<element name="class" type="integer" minOccurs="0"
maxOccurs="unbounded"/>
<element name="label" type="mark:labelType" minOccurs="0"
maxOccurs="unbounded"/>
<element name="goodsAndServices" type="token" />
<element name="apId" type="token" minOccurs="0"/>
<element name="apDate" type="dateTime" minOccurs="0"/>
<element name="regNum" type="token"/>
<element name="regDate" type="dateTime"/>
<element name="exDate" type="dateTime" minOccurs="0"/>
</sequence>
</complexType>
<complexType name="treatyOrStatuteType">
<sequence>
<element name="id" type="mark:idType"/>
<element name="markName" type="token"/>
<element name="holder" type="mark:holderType"
maxOccurs="unbounded" />
<element name="contact" type="mark:contactType" minOccurs="0"
maxOccurs="unbounded"/>
<element name="protection" type="mark:protectionType"
maxOccurs="unbounded"/>
<element name="label" type="mark:labelType" minOccurs="0"
maxOccurs="unbounded"/>
<element name="goodsAndServices" type="token" />
<element name="refNum" type="token"/>
<element name="proDate" type="dateTime"/>
<element name="title" type="token"/>
<element name="execDate" type="dateTime"/>
</sequence>
</complexType>
<complexType name="courtType">
<sequence>
<element name="id" type="mark:idType"/>
<element name="markName" type="token"/>
<element name="holder" type="mark:holderType"
maxOccurs="unbounded" />
<element name="contact" type="mark:contactType" minOccurs="0"
maxOccurs="unbounded"/>
<element name="label" type="mark:labelType" minOccurs="0"
maxOccurs="unbounded"/>
<element name="goodsAndServices" type="token" />
<element name="refNum" type="token"/>
<element name="proDate" type="dateTime"/>
<element name="cc" type="mark:ccType"/>
<element name="region" type="token" minOccurs="0"
maxOccurs="unbounded"/>
<element name="courtName" type="token"/>
</sequence>
</complexType>
<!--
Address (<mark:addr>) child elements
-->
<complexType name="addrType">
<sequence>
<element name="street" type="token" minOccurs="1" maxOccurs="3"/>
<element name="city" type="token"/>
<element name="sp" type="token" minOccurs="0"/>
<element name="pc" type="mark:pcType" minOccurs="0"/>
<element name="cc" type="mark:ccType"/>
</sequence>
</complexType>
<!--
<mark:protection> child elements
-->
<complexType name="protectionType">
<sequence>
<element name="cc" type="mark:ccType"/>
<element name="region" type="token" minOccurs="0"/>
<element name="ruling" type="mark:ccType"
minOccurs="0" maxOccurs="unbounded"/>
</sequence>
</complexType>
<!--
Postal code definition
-->
<simpleType name="pcType">
<restriction base="token">
<maxLength value="16"/>
</restriction>
</simpleType>
<!--
Country code definition
-->
<simpleType name="ccType">
<restriction base="token">
<length value="2"/>
</restriction>
</simpleType>
<!--
Phone number with extension definition
-->
<complexType name="e164Type">
<simpleContent>
<extension base="mark:e164StringType">
<attribute name="x" type="token"/>
</extension>
</simpleContent>
</complexType>
<!--
Phone number with extension definition
-->
<simpleType name="e164StringType">
<restriction base="token">
<pattern value="(\+[0-9]{1,3}\.[0-9]{1,14})?"/>
<maxLength value="17"/>
</restriction>
</simpleType>
<!--
Id type definition
-->
<simpleType name="idType">
<restriction base="token">
<pattern value="\d+-\d+"/>
</restriction>
</simpleType>
<!--
DNS label type definition
-->
<simpleType name="labelType">
<restriction base="token">
<minLength value="1"/>
<maxLength value="63"/>
<pattern value="[a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?"/>
</restriction>
</simpleType>
<!--
Type used for email addresses
-->
<simpleType name="minTokenType">
<restriction base="token">
<minLength value="1"/>
</restriction>
</simpleType>
<simpleType name="entitlementType">
<restriction base="token">
<enumeration value="owner"/>
<enumeration value="assignee"/>
<enumeration value="licensee"/>
</restriction>
</simpleType>
<simpleType name="contactTypeType">
<restriction base="token">
<enumeration value="owner"/>
<enumeration value="agent"/>
<enumeration value="thirdparty"/>
</restriction>
</simpleType>
</schema>

View file

@ -0,0 +1,133 @@
<?xml version="1.0" encoding="UTF-8"?>
<schema targetNamespace="urn:ietf:params:xml:ns:rgp-1.0"
xmlns:rgp="urn:ietf:params:xml:ns:rgp-1.0"
xmlns="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified">
<annotation>
<documentation>
Extensible Provisioning Protocol v1.0
domain name extension schema for registry grace period
processing.
</documentation>
</annotation>
<!--
Child elements found in EPP commands.
-->
<element name="update" type="rgp:updateType"/>
<!--
Child elements of the <update> command for the
redemption grace period.
-->
<complexType name="updateType">
<sequence>
<element name="restore" type="rgp:restoreType"/>
</sequence>
</complexType>
<complexType name="restoreType">
<sequence>
<element name="report" type="rgp:reportType"
minOccurs="0"/>
</sequence>
<attribute name="op" type="rgp:rgpOpType" use="required"/>
</complexType>
<!--
New redemption grace period operations can be defined
by adding to this enumeration.
-->
<simpleType name="rgpOpType">
<restriction base="token">
<enumeration value="request"/>
<enumeration value="report"/>
</restriction>
</simpleType>
<complexType name="reportType">
<sequence>
<element name="preData" type="rgp:mixedType"/>
<element name="postData" type="rgp:mixedType"/>
<element name="delTime" type="dateTime"/>
<element name="resTime" type="dateTime"/>
<element name="resReason" type="rgp:reportTextType"/>
<element name="statement" type="rgp:reportTextType"
maxOccurs="2"/>
<element name="other" type="rgp:mixedType"
minOccurs="0"/>
</sequence>
</complexType>
<complexType name="mixedType">
<complexContent mixed="true">
<restriction base="anyType">
<sequence>
<any processContents="lax"
minOccurs="0" maxOccurs="unbounded"/>
</sequence>
</restriction>
</complexContent>
</complexType>
<complexType name="reportTextType">
<complexContent mixed="true">
<restriction base="anyType">
<sequence>
<any processContents="lax"
minOccurs="0" maxOccurs="unbounded"/>
</sequence>
<attribute name="lang" type="language" default="en"/>
</restriction>
</complexContent>
</complexType>
<!--
Child response elements.
-->
<element name="infData" type="rgp:respDataType"/>
<element name="upData" type="rgp:respDataType"/>
<!--
Response elements.
-->
<complexType name="respDataType">
<sequence>
<element name="rgpStatus" type="rgp:statusType"
maxOccurs="unbounded"/>
</sequence>
</complexType>
<!--
Status is a combination of attributes and an optional
human-readable message that may be expressed in languages
other than English.
-->
<complexType name="statusType">
<simpleContent>
<extension base="normalizedString">
<attribute name="s" type="rgp:statusValueType"
use="required"/>
<attribute name="lang" type="language" default="en"/>
</extension>
</simpleContent>
</complexType>
<simpleType name="statusValueType">
<restriction base="token">
<enumeration value="addPeriod"/>
<enumeration value="autoRenewPeriod"/>
<enumeration value="renewPeriod"/>
<enumeration value="transferPeriod"/>
<enumeration value="pendingDelete"/>
<enumeration value="pendingRestore"/>
<enumeration value="redemptionPeriod"/>
</restriction>
</simpleType>
<!--
End of schema.
-->
</schema>

View file

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Schema extracted from http://tools.ietf.org/html/draft-lozano-tmch-smd -->
<schema
targetNamespace="urn:ietf:params:xml:ns:signedMark-1.0"
xmlns:smd="urn:ietf:params:xml:ns:signedMark-1.0"
xmlns:mark="urn:ietf:params:xml:ns:mark-1.0"
xmlns:dsig="http://www.w3.org/2000/09/xmldsig#"
xmlns="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified">
<annotation>
<documentation>
Schema for representing a Signed Trademark.
</documentation>
</annotation>
<import namespace="urn:ietf:params:xml:ns:mark-1.0"
schemaLocation="mark.xsd" />
<import namespace="http://www.w3.org/2000/09/xmldsig#"
schemaLocation="dsig.xsd"/>
<!--
Abstract signed mark for replacement via substitution.
-->
<element name="abstractSignedMark" type="smd:abstractSignedMarkType"
abstract="true"/>
<!--
Empty type for use in extending for a signed mark
-->
<complexType name="abstractSignedMarkType"/>
<element name="signedMark" type="smd:signedMarkType"
substitutionGroup="smd:abstractSignedMark"/>
<element name="encodedSignedMark" type="smd:encodedSignedMarkType"/>
<complexType name="signedMarkType">
<complexContent>
<extension base="smd:abstractSignedMarkType">
<sequence>
<element name="id" type="mark:idType"/>
<element name="issuerInfo" type="smd:issuerInfoType"/>
<element name="notBefore" type="dateTime"/>
<element name="notAfter" type="dateTime"/>
<element ref="mark:abstractMark"/>
<element ref="dsig:Signature"/>
</sequence>
<attribute name="id" type="ID" use="required"/>
</extension>
</complexContent>
</complexType>
<complexType name="issuerInfoType">
<sequence>
<element name="org" type="token"/>
<element name="email" type="mark:minTokenType"/>
<element name="url" type="token" minOccurs="0"/>
<element name="voice" type="mark:e164Type" minOccurs="0"/>
</sequence>
<attribute name="issuerID" type="token" use="required"/>
</complexType>
<complexType name="encodedSignedMarkType">
<simpleContent>
<extension base="token">
<attribute name="encoding" default="base64"/>
</extension>
</simpleContent>
</complexType>
</schema>

View file

@ -22,6 +22,8 @@ import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import google.registry.monitoring.blackbox.connection.ProbingAction;
import google.registry.monitoring.blackbox.connection.Protocol;
import google.registry.monitoring.blackbox.exceptions.FailureException; import google.registry.monitoring.blackbox.exceptions.FailureException;
import google.registry.monitoring.blackbox.exceptions.UndeterminedStateException; import google.registry.monitoring.blackbox.exceptions.UndeterminedStateException;
import google.registry.monitoring.blackbox.exceptions.UnrecoverableStateException; import google.registry.monitoring.blackbox.exceptions.UnrecoverableStateException;
@ -38,22 +40,20 @@ import org.mockito.Mockito;
/** /**
* Unit Tests on {@link ProbingSequence} * Unit Tests on {@link ProbingSequence}
* *
* <p>First tests the construction of sequences and ensures the ordering is exactly how * <p>First tests the construction of sequences and ensures the ordering is exactly how we expect it
* we expect it to be.</p> * to be.
* *
* <p>Then tests the execution of each step, by ensuring the methods treatment of any kind * <p>Then tests the execution of each step, by ensuring the methods treatment of any kind of
* of response from the {@link ProbingStep}s or {@link ProbingAction}s is what is expected.</p> * response from the {@link ProbingStep}s or {@link ProbingAction}s is what is expected.
* *
* <p>On every test that runs the sequence, in order for the sequence to stop, we throw an * <p>On every test that runs the sequence, in order for the sequence to stop, we throw an {@link
* {@link UnrecoverableStateException}, using mocks of the steps or actions, as the sequences * UnrecoverableStateException}, using mocks of the steps or actions, as the sequences are run using
* are run using the main thread (with {@link EmbeddedChannel}).</p> * the main thread (with {@link EmbeddedChannel}).
*/ */
@RunWith(JUnit4.class) @RunWith(JUnit4.class)
public class ProbingSequenceTest { public class ProbingSequenceTest {
/** /** Default mock {@link ProbingAction} returned when generating an action with a mockStep. */
* Default mock {@link ProbingAction} returned when generating an action with a mockStep.
*/
private ProbingAction mockAction = Mockito.mock(ProbingAction.class); private ProbingAction mockAction = Mockito.mock(ProbingAction.class);
/** /**
@ -62,10 +62,7 @@ public class ProbingSequenceTest {
*/ */
private ProbingStep mockStep = Mockito.mock(ProbingStep.class); private ProbingStep mockStep = Mockito.mock(ProbingStep.class);
/** Default mock {@link Token} that is passed into each {@link ProbingSequence} tested. */
/**
* Default mock {@link Token} that is passed into each {@link ProbingSequence} tested.
*/
private Token mockToken = Mockito.mock(Token.class); private Token mockToken = Mockito.mock(Token.class);
/** /**
@ -104,7 +101,8 @@ public class ProbingSequenceTest {
ProbingStep secondStep = Mockito.mock(ProbingStep.class); ProbingStep secondStep = Mockito.mock(ProbingStep.class);
ProbingStep thirdStep = Mockito.mock(ProbingStep.class); ProbingStep thirdStep = Mockito.mock(ProbingStep.class);
ProbingSequence sequence = new ProbingSequence.Builder(mockToken) ProbingSequence sequence =
new ProbingSequence.Builder(mockToken)
.add(firstStep) .add(firstStep)
.add(secondStep) .add(secondStep)
.add(thirdStep) .add(thirdStep)
@ -128,7 +126,8 @@ public class ProbingSequenceTest {
ProbingStep secondStep = Mockito.mock(ProbingStep.class); ProbingStep secondStep = Mockito.mock(ProbingStep.class);
ProbingStep thirdStep = Mockito.mock(ProbingStep.class); ProbingStep thirdStep = Mockito.mock(ProbingStep.class);
ProbingSequence sequence = new ProbingSequence.Builder(mockToken) ProbingSequence sequence =
new ProbingSequence.Builder(mockToken)
.add(thirdStep) .add(thirdStep)
.add(secondStep) .add(secondStep)
.markFirstRepeated() .markFirstRepeated()
@ -145,47 +144,43 @@ public class ProbingSequenceTest {
sequence = sequence.next(); sequence = sequence.next();
assertThat(sequence.get()).isEqualTo(secondStep); assertThat(sequence.get()).isEqualTo(secondStep);
} }
@Test @Test
public void testRunStep_Success() throws UndeterminedStateException { public void testRunStep_Success() throws UndeterminedStateException {
//Always returns a succeeded future on call to mockAction. // Always returns a succeeded future on call to mockAction.
doReturn(channel.newSucceededFuture()).when(mockAction).call(); doReturn(channel.newSucceededFuture()).when(mockAction).call();
// Has mockStep always return mockAction on call to generateAction // Has mockStep always return mockAction on call to generateAction.
doReturn(mockAction).when(mockStep).generateAction(any(Token.class)); doReturn(mockAction).when(mockStep).generateAction(any(Token.class));
//Dummy step that server purpose of placeholder to test ability of ProbingSequence to move on. // Dummy step that server purpose of placeholder to test ability of ProbingSequence to move on.
ProbingStep secondStep = Mockito.mock(ProbingStep.class); ProbingStep secondStep = Mockito.mock(ProbingStep.class);
ProbingAction secondAction = Mockito.mock(ProbingAction.class); ProbingAction secondAction = Mockito.mock(ProbingAction.class);
doReturn(channel.newFailedFuture(new UnrecoverableStateException(""))).when(secondAction) doReturn(channel.newFailedFuture(new UnrecoverableStateException("")))
.when(secondAction)
.call(); .call();
doReturn(secondAction).when(secondStep).generateAction(mockToken); doReturn(secondAction).when(secondStep).generateAction(mockToken);
//Build testable sequence from mocked components. // Build testable sequence from mocked components.
ProbingSequence sequence = new ProbingSequence.Builder(mockToken) ProbingSequence sequence =
.add(mockStep) new ProbingSequence.Builder(mockToken).add(mockStep).add(secondStep).build();
.add(secondStep)
.build();
sequence.start(); sequence.start();
// We expect to have only generated actions from mockStep once, and we expect to have called // We expect to have only generated actions from mockStep once, and we expect to have called
// this generated action only once, as when we move on to secondStep, it terminates the // this generated action only once, as when we move on to secondStep, it terminates the
// sequence. // sequence.
verify(mockStep).generateAction(any(Token.class));
verify(mockStep).generateAction(mockToken); verify(mockStep).generateAction(mockToken);
verify(mockAction).call(); verify(mockAction).call();
// Similarly, we expect to generate actions and call the action from the secondStep once, as // Similarly, we expect to generate actions and call the action from the secondStep once, as
// after calling it, the sequence should be terminated // after calling it, the sequence should be terminated
verify(secondStep).generateAction(any(Token.class));
verify(secondStep).generateAction(mockToken); verify(secondStep).generateAction(mockToken);
verify(secondAction).call(); verify(secondAction).call();
//We should have modified the token's channel after the first, succeeded step. // We should have modified the token's channel after the first, succeeded step.
assertThat(mockToken.channel()).isEqualTo(channel); assertThat(mockToken.channel()).isEqualTo(channel);
} }
@ -201,7 +196,7 @@ public class ProbingSequenceTest {
ProbingStep secondStep = Mockito.mock(ProbingStep.class); ProbingStep secondStep = Mockito.mock(ProbingStep.class);
ProbingAction secondAction = Mockito.mock(ProbingAction.class); ProbingAction secondAction = Mockito.mock(ProbingAction.class);
// Necessary for success of ProbingSequence runStep method as it calls get().protocol() // Necessary for success of ProbingSequence runStep method as it calls get().protocol().
doReturn(mockProtocol).when(secondStep).protocol(); doReturn(mockProtocol).when(secondStep).protocol();
// We ensure that secondStep has necessary attributes to be successful step to pass on to // We ensure that secondStep has necessary attributes to be successful step to pass on to
@ -220,18 +215,15 @@ public class ProbingSequenceTest {
doReturn(channel.newFailedFuture(new UnrecoverableStateException(""))).when(thirdAction).call(); doReturn(channel.newFailedFuture(new UnrecoverableStateException(""))).when(thirdAction).call();
doReturn(thirdAction).when(mockStep).generateAction(secondToken); doReturn(thirdAction).when(mockStep).generateAction(secondToken);
//Build testable sequence from mocked components. // Build testable sequence from mocked components.
ProbingSequence sequence = new ProbingSequence.Builder(mockToken) ProbingSequence sequence =
.add(mockStep) new ProbingSequence.Builder(mockToken).add(mockStep).add(secondStep).build();
.add(secondStep)
.build();
sequence.start(); sequence.start();
// We expect to have generated actions from mockStep twice (once for mockToken and once for // We expect to have generated actions from mockStep twice (once for mockToken and once for
// secondToken), and we expectto have called each generated action only once, as when we move // secondToken), and we expectto have called each generated action only once, as when we move
// on to mockStep the second time, it will terminate the sequence after calling thirdAction. // on to mockStep the second time, it will terminate the sequence after calling thirdAction.
verify(mockStep, times(2)).generateAction(any(Token.class));
verify(mockStep).generateAction(mockToken); verify(mockStep).generateAction(mockToken);
verify(mockStep).generateAction(secondToken); verify(mockStep).generateAction(secondToken);
verify(mockAction).call(); verify(mockAction).call();
@ -239,23 +231,22 @@ public class ProbingSequenceTest {
// Similarly, we expect to generate actions and call the action from the secondStep once, as // Similarly, we expect to generate actions and call the action from the secondStep once, as
// after calling it, we move on to mockStep again, which terminates the sequence. // after calling it, we move on to mockStep again, which terminates the sequence.
verify(secondStep).generateAction(any(Token.class));
verify(secondStep).generateAction(mockToken); verify(secondStep).generateAction(mockToken);
verify(secondAction).call(); verify(secondAction).call();
//We should have modified the token's channel after the first, succeeded step. // We should have modified the token's channel after the first, succeeded step.
assertThat(mockToken.channel()).isEqualTo(channel); assertThat(mockToken.channel()).isEqualTo(channel);
} }
/** /**
* Test for when we expect Failure within try catch block of generating and calling a * Test for when we expect Failure within try catch block of generating and calling a {@link
* {@link ProbingAction}. * ProbingAction}.
* *
* @throws UndeterminedStateException - necessary for having mock return anything on a call to * @throws UndeterminedStateException - necessary for having mock return anything on a call to
* {@code generateAction}. * {@code generateAction}.
*/ */
private void testActionFailure() throws UndeterminedStateException { private void testActionFailure() throws UndeterminedStateException {
//Dummy step that server purpose of placeholder to test ability of ProbingSequence to move on. // Dummy step that server purpose of placeholder to test ability of ProbingSequence to move on.
ProbingStep secondStep = Mockito.mock(ProbingStep.class); ProbingStep secondStep = Mockito.mock(ProbingStep.class);
// We create a second token that when used to generate an action throws an // We create a second token that when used to generate an action throws an
@ -264,18 +255,15 @@ public class ProbingSequenceTest {
doReturn(secondToken).when(mockToken).next(); doReturn(secondToken).when(mockToken).next();
doThrow(new UnrecoverableStateException("")).when(mockStep).generateAction(secondToken); doThrow(new UnrecoverableStateException("")).when(mockStep).generateAction(secondToken);
//Build testable sequence from mocked components. // Build testable sequence from mocked components.
ProbingSequence sequence = new ProbingSequence.Builder(mockToken) ProbingSequence sequence =
.add(mockStep) new ProbingSequence.Builder(mockToken).add(mockStep).add(secondStep).build();
.add(secondStep)
.build();
sequence.start(); sequence.start();
// We expect that we have generated actions twice. First, when we actually test generateAction // We expect that we have generated actions twice. First, when we actually test generateAction
// with an actual call using mockToken, and second when we throw an // with an actual call using mockToken, and second when we throw an
// UnrecoverableStateException with secondToken. // UnrecoverableStateException with secondToken.
verify(mockStep, times(2)).generateAction(any(Token.class));
verify(mockStep).generateAction(mockToken); verify(mockStep).generateAction(mockToken);
verify(mockStep).generateAction(secondToken); verify(mockStep).generateAction(secondToken);
@ -296,17 +284,14 @@ public class ProbingSequenceTest {
// Returns mock action on call to generate action for ProbingStep. // Returns mock action on call to generate action for ProbingStep.
doReturn(mockAction).when(mockStep).generateAction(mockToken); doReturn(mockAction).when(mockStep).generateAction(mockToken);
//Tests generic behavior we expect when we fail in generating or calling an action. // Tests generic behavior we expect when we fail in generating or calling an action.
testActionFailure(); testActionFailure();
// We only expect to have called this action once, as we only get it from one generateAction // We only expect to have called this action once, as we only get it from one generateAction
// call. // call.
verify(mockAction).call(); verify(mockAction).call();
} }
@Test @Test
public void testRunStep_FailureGenerating() throws UndeterminedStateException { public void testRunStep_FailureGenerating() throws UndeterminedStateException {
// Create a mock first step that returns the dummy action when called to generate an action. // Create a mock first step that returns the dummy action when called to generate an action.

View file

@ -15,12 +15,14 @@
package google.registry.monitoring.blackbox; package google.registry.monitoring.blackbox;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static google.registry.monitoring.blackbox.ProbingAction.CONNECTION_FUTURE_KEY; import static google.registry.monitoring.blackbox.connection.ProbingAction.CONNECTION_FUTURE_KEY;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doReturn;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import google.registry.monitoring.blackbox.connection.ProbingAction;
import google.registry.monitoring.blackbox.connection.Protocol;
import google.registry.monitoring.blackbox.exceptions.UndeterminedStateException; import google.registry.monitoring.blackbox.exceptions.UndeterminedStateException;
import google.registry.monitoring.blackbox.handlers.ActionHandler; import google.registry.monitoring.blackbox.handlers.ActionHandler;
import google.registry.monitoring.blackbox.handlers.ConversionHandler; import google.registry.monitoring.blackbox.handlers.ConversionHandler;
@ -48,31 +50,26 @@ import org.mockito.Mockito;
*/ */
public class ProbingStepTest { public class ProbingStepTest {
/** /** Basic Constants necessary for tests */
* Basic Constants necessary for tests
*/
private static final String ADDRESS_NAME = "TEST_ADDRESS"; private static final String ADDRESS_NAME = "TEST_ADDRESS";
private static final String PROTOCOL_NAME = "TEST_PROTOCOL"; private static final String PROTOCOL_NAME = "TEST_PROTOCOL";
private static final int PROTOCOL_PORT = 0; private static final int PROTOCOL_PORT = 0;
private static final String TEST_MESSAGE = "TEST_MESSAGE"; private static final String TEST_MESSAGE = "TEST_MESSAGE";
private static final String SECONDARY_TEST_MESSAGE = "SECONDARY_TEST_MESSAGE"; private static final String SECONDARY_TEST_MESSAGE = "SECONDARY_TEST_MESSAGE";
private static final LocalAddress address = new LocalAddress(ADDRESS_NAME); private static final LocalAddress address = new LocalAddress(ADDRESS_NAME);
private final EventLoopGroup eventLoopGroup = new NioEventLoopGroup(1); private final EventLoopGroup eventLoopGroup = new NioEventLoopGroup(1);
private final Bootstrap bootstrap = new Bootstrap() private final Bootstrap bootstrap =
.group(eventLoopGroup) new Bootstrap().group(eventLoopGroup).channel(LocalChannel.class);
.channel(LocalChannel.class); /** Used for testing how well probing step can create connection to blackbox server */
/** @Rule public NettyRule nettyRule = new NettyRule(eventLoopGroup);
* Used for testing how well probing step can create connection to blackbox server
*/
@Rule
public NettyRule nettyRule = new NettyRule(eventLoopGroup);
/** /**
* The two main handlers we need in any test pipeline used that connects to {@link NettyRule's * The two main handlers we need in any test pipeline used that connects to {@link NettyRule's
* server} * server}
**/ */
private ActionHandler testHandler = new TestActionHandler(); private ActionHandler testHandler = new TestActionHandler();
private ChannelHandler conversionHandler = new ConversionHandler(); private ChannelHandler conversionHandler = new ConversionHandler();
/** /**
@ -92,7 +89,8 @@ public class ProbingStepTest {
@Test @Test
public void testProbingActionGenerate_embeddedChannel() throws UndeterminedStateException { public void testProbingActionGenerate_embeddedChannel() throws UndeterminedStateException {
// Sets up Protocol to represent existing channel connection. // Sets up Protocol to represent existing channel connection.
Protocol testProtocol = Protocol.builder() Protocol testProtocol =
Protocol.builder()
.setHandlerProviders(ImmutableList.of(() -> conversionHandler, () -> testHandler)) .setHandlerProviders(ImmutableList.of(() -> conversionHandler, () -> testHandler))
.setName(PROTOCOL_NAME) .setName(PROTOCOL_NAME)
.setPort(PROTOCOL_PORT) .setPort(PROTOCOL_PORT)
@ -109,7 +107,8 @@ public class ProbingStepTest {
doReturn(channel).when(testToken).channel(); doReturn(channel).when(testToken).channel();
// Sets up generic {@link ProbingStep} that we are testing. // Sets up generic {@link ProbingStep} that we are testing.
ProbingStep testStep = ProbingStep.builder() ProbingStep testStep =
ProbingStep.builder()
.setMessageTemplate(new TestMessage(TEST_MESSAGE)) .setMessageTemplate(new TestMessage(TEST_MESSAGE))
.setBootstrap(bootstrap) .setBootstrap(bootstrap)
.setDuration(Duration.ZERO) .setDuration(Duration.ZERO)
@ -123,14 +122,13 @@ public class ProbingStepTest {
assertThat(testAction.outboundMessage().toString()).isEqualTo(SECONDARY_TEST_MESSAGE); assertThat(testAction.outboundMessage().toString()).isEqualTo(SECONDARY_TEST_MESSAGE);
assertThat(testAction.host()).isEqualTo(SECONDARY_TEST_MESSAGE); assertThat(testAction.host()).isEqualTo(SECONDARY_TEST_MESSAGE);
assertThat(testAction.protocol()).isEqualTo(testProtocol); assertThat(testAction.protocol()).isEqualTo(testProtocol);
} }
@Test @Test
public void testProbingActionGenerate_newChannel() throws UndeterminedStateException { public void testProbingActionGenerate_newChannel() throws UndeterminedStateException {
// Sets up Protocol for when we create a new channel. // Sets up Protocol for when we create a new channel.
Protocol testProtocol = Protocol.builder() Protocol testProtocol =
Protocol.builder()
.setHandlerProviders(ImmutableList.of(() -> conversionHandler, () -> testHandler)) .setHandlerProviders(ImmutableList.of(() -> conversionHandler, () -> testHandler))
.setName(PROTOCOL_NAME) .setName(PROTOCOL_NAME)
.setPort(PROTOCOL_PORT) .setPort(PROTOCOL_PORT)
@ -138,7 +136,8 @@ public class ProbingStepTest {
.build(); .build();
// Sets up generic ProbingStep that we are testing. // Sets up generic ProbingStep that we are testing.
ProbingStep testStep = ProbingStep.builder() ProbingStep testStep =
ProbingStep.builder()
.setMessageTemplate(new TestMessage(TEST_MESSAGE)) .setMessageTemplate(new TestMessage(TEST_MESSAGE))
.setBootstrap(bootstrap) .setBootstrap(bootstrap)
.setDuration(Duration.ZERO) .setDuration(Duration.ZERO)
@ -162,7 +161,5 @@ public class ProbingStepTest {
assertThat(testAction.outboundMessage().toString()).isEqualTo(ADDRESS_NAME); assertThat(testAction.outboundMessage().toString()).isEqualTo(ADDRESS_NAME);
assertThat(testAction.host()).isEqualTo(ADDRESS_NAME); assertThat(testAction.host()).isEqualTo(ADDRESS_NAME);
assertThat(testAction.protocol()).isEqualTo(testProtocol); assertThat(testAction.protocol()).isEqualTo(testProtocol);
} }
} }

View file

@ -12,10 +12,10 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package google.registry.monitoring.blackbox; package google.registry.monitoring.blackbox.connection;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static google.registry.monitoring.blackbox.ProbingAction.CONNECTION_FUTURE_KEY; import static google.registry.monitoring.blackbox.connection.ProbingAction.CONNECTION_FUTURE_KEY;
import static java.nio.charset.StandardCharsets.US_ASCII; import static java.nio.charset.StandardCharsets.US_ASCII;
import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8;
@ -46,7 +46,7 @@ import org.junit.runners.JUnit4;
* Unit tests for {@link ProbingAction} subtypes * Unit tests for {@link ProbingAction} subtypes
* *
* <p>Attempts to test how well each {@link ProbingAction} works with an {@link ActionHandler} * <p>Attempts to test how well each {@link ProbingAction} works with an {@link ActionHandler}
* subtype when receiving to all possible types of responses</p> * subtype when receiving to all possible types of responses
*/ */
@RunWith(JUnit4.class) @RunWith(JUnit4.class)
public class ProbingActionTest { public class ProbingActionTest {
@ -58,29 +58,30 @@ public class ProbingActionTest {
private static final int TEST_PORT = 0; private static final int TEST_PORT = 0;
private static final EventLoopGroup eventLoopGroup = new NioEventLoopGroup(1); private static final EventLoopGroup eventLoopGroup = new NioEventLoopGroup(1);
/**
* Used for testing how well probing step can create connection to blackbox server /** Used for testing how well probing step can create connection to blackbox server */
*/ @Rule public NettyRule nettyRule = new NettyRule(eventLoopGroup);
@Rule
public NettyRule nettyRule = new NettyRule(eventLoopGroup);
/** /**
* We use custom Test {@link ActionHandler} and {@link ConversionHandler} so test depends only on * We use custom Test {@link ActionHandler} and {@link ConversionHandler} so test depends only on
* {@link ProbingAction} * {@link ProbingAction}
*/ */
private ActionHandler testHandler = new TestActionHandler(); private ActionHandler testHandler = new TestActionHandler();
private ChannelHandler conversionHandler = new ConversionHandler(); private ChannelHandler conversionHandler = new ConversionHandler();
//TODO - Currently, this test fails to receive outbound messages from the embedded channel, which // TODO - Currently, this test fails to receive outbound messages from the embedded channel, which
// we will fix in a later release. // we will fix in a later release.
@Ignore @Ignore
@Test @Test
public void testSuccess_existingChannel() { public void testSuccess_existingChannel() {
//setup // setup
EmbeddedChannel channel = new EmbeddedChannel(conversionHandler, testHandler); EmbeddedChannel channel = new EmbeddedChannel(conversionHandler, testHandler);
channel.attr(CONNECTION_FUTURE_KEY).set(channel.newSucceededFuture()); channel.attr(CONNECTION_FUTURE_KEY).set(channel.newSucceededFuture());
// Sets up a Protocol corresponding to when a connection exists. // Sets up a Protocol corresponding to when a connection exists.
Protocol protocol = Protocol.builder() Protocol protocol =
Protocol.builder()
.setHandlerProviders(ImmutableList.of(() -> conversionHandler, () -> testHandler)) .setHandlerProviders(ImmutableList.of(() -> conversionHandler, () -> testHandler))
.setName(PROTOCOL_NAME) .setName(PROTOCOL_NAME)
.setPort(TEST_PORT) .setPort(TEST_PORT)
@ -88,7 +89,8 @@ public class ProbingActionTest {
.build(); .build();
// Sets up a ProbingAction that creates a channel using test specified attributes. // Sets up a ProbingAction that creates a channel using test specified attributes.
ProbingAction action = ProbingAction.builder() ProbingAction action =
ProbingAction.builder()
.setChannel(channel) .setChannel(channel)
.setProtocol(protocol) .setProtocol(protocol)
.setDelay(Duration.ZERO) .setDelay(Duration.ZERO)
@ -96,15 +98,15 @@ public class ProbingActionTest {
.setHost("") .setHost("")
.build(); .build();
//tests main function of ProbingAction // tests main function of ProbingAction
ChannelFuture future = action.call(); ChannelFuture future = action.call();
//Obtains the outboundMessage passed through pipeline after delay // Obtains the outboundMessage passed through pipeline after delay
Object msg = null; Object msg = null;
while (msg == null) { while (msg == null) {
msg = channel.readOutbound(); msg = channel.readOutbound();
} }
//tests the passed message is exactly what we expect // tests the passed message is exactly what we expect
assertThat(msg).isInstanceOf(ByteBuf.class); assertThat(msg).isInstanceOf(ByteBuf.class);
String request = ((ByteBuf) msg).toString(UTF_8); String request = ((ByteBuf) msg).toString(UTF_8);
assertThat(request).isEqualTo(TEST_MESSAGE); assertThat(request).isEqualTo(TEST_MESSAGE);
@ -112,7 +114,8 @@ public class ProbingActionTest {
// Ensures that we haven't marked future as done until response is received. // Ensures that we haven't marked future as done until response is received.
assertThat(future.isDone()).isFalse(); assertThat(future.isDone()).isFalse();
//after writing inbound, we should have a success // After writing inbound, we should have a success as we use an EmbeddedChannel, ensuring all
// operations happen on the main thread - i.e. synchronously.
channel.writeInbound(Unpooled.wrappedBuffer(SECONDARY_TEST_MESSAGE.getBytes(US_ASCII))); channel.writeInbound(Unpooled.wrappedBuffer(SECONDARY_TEST_MESSAGE.getBytes(US_ASCII)));
assertThat(future.isSuccess()).isTrue(); assertThat(future.isSuccess()).isTrue();
@ -121,15 +124,14 @@ public class ProbingActionTest {
@Test @Test
public void testSuccess_newChannel() throws Exception { public void testSuccess_newChannel() throws Exception {
//setup // setup
LocalAddress address = new LocalAddress(ADDRESS_NAME); LocalAddress address = new LocalAddress(ADDRESS_NAME);
Bootstrap bootstrap = new Bootstrap() Bootstrap bootstrap = new Bootstrap().group(eventLoopGroup).channel(LocalChannel.class);
.group(eventLoopGroup)
.channel(LocalChannel.class);
// Sets up a Protocol corresponding to when a new connection is created. // Sets up a Protocol corresponding to when a new connection is created.
Protocol protocol = Protocol.builder() Protocol protocol =
Protocol.builder()
.setHandlerProviders(ImmutableList.of(() -> conversionHandler, () -> testHandler)) .setHandlerProviders(ImmutableList.of(() -> conversionHandler, () -> testHandler))
.setName(PROTOCOL_NAME) .setName(PROTOCOL_NAME)
.setPort(TEST_PORT) .setPort(TEST_PORT)
@ -139,7 +141,8 @@ public class ProbingActionTest {
nettyRule.setUpServer(address); nettyRule.setUpServer(address);
// Sets up a ProbingAction with existing channel using test specified attributes. // Sets up a ProbingAction with existing channel using test specified attributes.
ProbingAction action = ProbingAction.builder() ProbingAction action =
ProbingAction.builder()
.setBootstrap(bootstrap) .setBootstrap(bootstrap)
.setProtocol(protocol) .setProtocol(protocol)
.setDelay(Duration.ZERO) .setDelay(Duration.ZERO)
@ -147,16 +150,15 @@ public class ProbingActionTest {
.setHost(ADDRESS_NAME) .setHost(ADDRESS_NAME)
.build(); .build();
//tests main function of ProbingAction // tests main function of ProbingAction
ChannelFuture future = action.call(); ChannelFuture future = action.call();
//Tests to see if message is properly sent to remote server // Tests to see if message is properly sent to remote server
nettyRule.assertReceivedMessage(TEST_MESSAGE); nettyRule.assertReceivedMessage(TEST_MESSAGE);
future = future.syncUninterruptibly(); future = future.syncUninterruptibly();
//Tests to see that, since server responds, we have set future to true // Tests to see that, since server responds, we have set future to true
assertThat(future.isSuccess()).isTrue(); assertThat(future.isSuccess()).isTrue();
assertThat(((TestActionHandler) testHandler).getResponse().toString()).isEqualTo(TEST_MESSAGE); assertThat(((TestActionHandler) testHandler).getResponse().toString()).isEqualTo(TEST_MESSAGE);
} }
} }

View file

@ -31,24 +31,20 @@ import io.netty.channel.ChannelPromise;
* {@link ChannelHandler} used in tests to convert {@link OutboundMessageType} to to {@link * {@link ChannelHandler} used in tests to convert {@link OutboundMessageType} to to {@link
* ByteBuf}s and convert {@link ByteBuf}s to {@link InboundMessageType} * ByteBuf}s and convert {@link ByteBuf}s to {@link InboundMessageType}
* *
* <p>Specific type of {@link OutboundMessageType} and {@link InboundMessageType} * <p>Specific type of {@link OutboundMessageType} and {@link InboundMessageType} used for
* used for conversion is the {@link TestMessage} type.</p> * conversion is the {@link TestMessage} type.
*/ */
public class ConversionHandler extends ChannelDuplexHandler { public class ConversionHandler extends ChannelDuplexHandler {
/** /** Handles inbound conversion */
* Handles inbound conversion
*/
@Override @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf buf = (ByteBuf) msg; ByteBuf buf = (ByteBuf) msg;
ctx.fireChannelRead(new TestMessage(buf.toString(UTF_8))); ctx.fireChannelRead(new TestMessage(buf.toString(UTF_8)));
buf.release(); buf.release();
} }
/** /** Handles outbound conversion */
* Handles outbound conversion
*/
@Override @Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)
throws Exception { throws Exception {
@ -57,4 +53,3 @@ public class ConversionHandler extends ChannelDuplexHandler {
super.write(ctx, buf, promise); super.write(ctx, buf, promise);
} }
} }

View file

@ -0,0 +1,240 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// 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
package google.registry.monitoring.blackbox.handlers;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.testing.JUnitBackports.assertThrows;
import google.registry.monitoring.blackbox.exceptions.EppClientException;
import google.registry.monitoring.blackbox.exceptions.FailureException;
import google.registry.monitoring.blackbox.exceptions.UndeterminedStateException;
import google.registry.monitoring.blackbox.messages.EppRequestMessage;
import google.registry.monitoring.blackbox.messages.EppResponseMessage;
import google.registry.monitoring.blackbox.util.EppUtils;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.embedded.EmbeddedChannel;
import java.io.IOException;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
/**
* Unit tests for {@link EppActionHandler} and {@link EppMessageHandler} as well as integration
* tests for both of them.
*
* <p>Attempts to test how well {@link EppActionHandler} works when responding to all possible types
* of {@link EppResponseMessage}s with corresponding {@link EppRequestMessage} sent down channel
* pipeline.
*/
@RunWith(Parameterized.class)
public class EppActionHandlerTest {
private static final String USER_ID = "TEST_ID";
private static final String USER_PASSWORD = "TEST_PASSWORD";
private static final String USER_CLIENT_TRID = "prober-localhost-1234567891011-0";
private static final String FAILURE_TRID = "TEST_FAILURE_TRID";
private static final String DOMAIN_NAME = "TEST_DOMAIN_NAME.test";
private static final String SERVER_ID = "TEST_SERVER_ID";
@Parameter(0)
public EppRequestMessage message;
private EmbeddedChannel channel;
private EppActionHandler actionHandler = new EppActionHandler();
private EppMessageHandler messageHandler = new EppMessageHandler(EppUtils.getGreetingResponse());
// We test all relevant EPP actions
@Parameters(name = "{0}")
public static EppRequestMessage[] data() {
return new EppRequestMessage[] {
EppUtils.getHelloMessage(EppUtils.getGreetingResponse()),
EppUtils.getLoginMessage(EppUtils.getSuccessResponse(), USER_ID, USER_PASSWORD),
EppUtils.getCreateMessage(EppUtils.getSuccessResponse()),
EppUtils.getCreateMessage(EppUtils.getFailureResponse()),
EppUtils.getDeleteMessage(EppUtils.getSuccessResponse()),
EppUtils.getDeleteMessage(EppUtils.getFailureResponse()),
EppUtils.getLogoutMessage(EppUtils.getSuccessResponse()),
EppUtils.getCheckMessage(EppUtils.getDomainExistsResponse()),
EppUtils.getCheckMessage(EppUtils.getDomainNotExistsResponse())
};
}
/** Setup main three handlers to be used in pipeline. */
@Before
public void setup() throws EppClientException {
message.modifyMessage(USER_CLIENT_TRID, DOMAIN_NAME);
}
private void setupEmbeddedChannel(ChannelHandler... handlers) {
channel = new EmbeddedChannel(handlers);
}
private Document getResponse(EppResponseMessage response, boolean fail, String clTrid)
throws IOException, EppClientException {
if (response.name().equals("greeting")) {
if (fail) {
return EppUtils.getBasicResponse(true, clTrid, SERVER_ID);
} else {
return EppUtils.getGreeting();
}
} else if (response.name().equals("domainExists")) {
return EppUtils.getDomainCheck(!fail, clTrid, SERVER_ID, DOMAIN_NAME);
} else if (response.name().equals("domainNotExists")) {
return EppUtils.getDomainCheck(fail, clTrid, SERVER_ID, DOMAIN_NAME);
} else if (response.name().equals("success")) {
return EppUtils.getBasicResponse(!fail, clTrid, SERVER_ID);
} else {
return EppUtils.getBasicResponse(fail, clTrid, SERVER_ID);
}
}
@Test
public void testBasicAction_Success_Embedded()
throws SAXException, IOException, EppClientException, FailureException {
// We simply use an embedded channel in this instance
setupEmbeddedChannel(actionHandler);
ChannelFuture future = actionHandler.getFinishedFuture();
EppResponseMessage response = message.getExpectedResponse();
response.getDocument(
EppUtils.docToByteBuf(getResponse(message.getExpectedResponse(), false, USER_CLIENT_TRID)));
channel.writeInbound(response);
ChannelFuture unusedFuture = future.syncUninterruptibly();
assertThat(future.isSuccess()).isTrue();
}
@Test
public void testBasicAction_FailCode_Embedded()
throws SAXException, IOException, EppClientException, FailureException {
// We simply use an embedded channel in this instance
setupEmbeddedChannel(actionHandler);
ChannelFuture future = actionHandler.getFinishedFuture();
EppResponseMessage response = message.getExpectedResponse();
response.getDocument(
EppUtils.docToByteBuf(getResponse(message.getExpectedResponse(), true, USER_CLIENT_TRID)));
channel.writeInbound(response);
assertThrows(
FailureException.class,
() -> {
ChannelFuture unusedFuture = future.syncUninterruptibly();
});
}
@Test
public void testBasicAction_FailTRID_Embedded()
throws SAXException, IOException, EppClientException, FailureException {
// We simply use an embedded channel in this instance
setupEmbeddedChannel(actionHandler);
ChannelFuture future = actionHandler.getFinishedFuture();
EppResponseMessage response = message.getExpectedResponse();
response.getDocument(
EppUtils.docToByteBuf(getResponse(message.getExpectedResponse(), false, FAILURE_TRID)));
channel.writeInbound(response);
if (message.getExpectedResponse().name().equals("greeting")) {
ChannelFuture unusedFuture = future.syncUninterruptibly();
assertThat(future.isSuccess()).isTrue();
} else {
assertThrows(
FailureException.class,
() -> {
ChannelFuture unusedFuture = future.syncUninterruptibly();
});
}
}
@Test
public void testIntegratedAction_Success_Embedded()
throws IOException, SAXException, UndeterminedStateException {
// We simply use an embedded channel in this instance
setupEmbeddedChannel(messageHandler, actionHandler);
ChannelFuture future = actionHandler.getFinishedFuture();
channel.writeOutbound(message);
channel.writeInbound(
EppUtils.docToByteBuf(getResponse(message.getExpectedResponse(), false, USER_CLIENT_TRID)));
ChannelFuture unusedFuture = future.syncUninterruptibly();
assertThat(future.isSuccess()).isTrue();
}
@Test
public void testIntegratedAction_FailCode_Embedded()
throws IOException, SAXException, UndeterminedStateException {
// We simply use an embedded channel in this instance
setupEmbeddedChannel(messageHandler, actionHandler);
ChannelFuture future = actionHandler.getFinishedFuture();
channel.writeOutbound(message);
channel.writeInbound(
EppUtils.docToByteBuf(getResponse(message.getExpectedResponse(), true, USER_CLIENT_TRID)));
assertThrows(
FailureException.class,
() -> {
ChannelFuture unusedFuture = future.syncUninterruptibly();
});
}
@Test
public void testIntegratedAction_FailTRID_Embedded()
throws IOException, SAXException, UndeterminedStateException {
// We simply use an embedded channel in this instance
setupEmbeddedChannel(messageHandler, actionHandler);
ChannelFuture future = actionHandler.getFinishedFuture();
channel.writeOutbound(message);
channel.writeInbound(
EppUtils.docToByteBuf(getResponse(message.getExpectedResponse(), false, FAILURE_TRID)));
if (message.getExpectedResponse().name().equals("greeting")) {
ChannelFuture unusedFuture = future.syncUninterruptibly();
assertThat(future.isSuccess()).isTrue();
} else {
assertThrows(
FailureException.class,
() -> {
ChannelFuture unusedFuture = future.syncUninterruptibly();
});
}
}
}

View file

@ -16,8 +16,8 @@ package google.registry.monitoring.blackbox.handlers;
import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Preconditions.checkState;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static google.registry.monitoring.blackbox.ProbingAction.REMOTE_ADDRESS_KEY; import static google.registry.monitoring.blackbox.connection.ProbingAction.REMOTE_ADDRESS_KEY;
import static google.registry.monitoring.blackbox.Protocol.PROTOCOL_KEY; import static google.registry.monitoring.blackbox.connection.Protocol.PROTOCOL_KEY;
import static google.registry.testing.JUnitBackports.assertThrows; import static google.registry.testing.JUnitBackports.assertThrows;
import static java.nio.charset.StandardCharsets.US_ASCII; import static java.nio.charset.StandardCharsets.US_ASCII;
import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8;
@ -25,9 +25,9 @@ import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.Throwables; import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.truth.ThrowableSubject; import com.google.common.truth.ThrowableSubject;
import google.registry.monitoring.blackbox.ProbingActionTest;
import google.registry.monitoring.blackbox.ProbingStepTest; import google.registry.monitoring.blackbox.ProbingStepTest;
import google.registry.monitoring.blackbox.Protocol; import google.registry.monitoring.blackbox.connection.ProbingActionTest;
import google.registry.monitoring.blackbox.connection.Protocol;
import google.registry.monitoring.blackbox.testservers.TestServer; import google.registry.monitoring.blackbox.testservers.TestServer;
import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
@ -51,18 +51,18 @@ import org.junit.rules.ExternalResource;
/** /**
* Helper for setting up and testing client / server connection with netty. * Helper for setting up and testing client / server connection with netty.
* *
* <p>Code based on and almost identical to {@code NettyRule} in the proxy. * <p>Code based on and almost identical to {@code NettyRule} in the proxy. Used in {@link
* Used in {@link SslClientInitializerTest}, {@link ProbingActionTest}, and {@link ProbingStepTest} * SslClientInitializerTest}, {@link ProbingActionTest}, and {@link ProbingStepTest}
* </p>
*/ */
public final class NettyRule extends ExternalResource { public final class NettyRule extends ExternalResource {
private final EventLoopGroup eventLoopGroup; private final EventLoopGroup eventLoopGroup;
// Handler attached to server's channel to record the request received. // Handler attached to server's channel to record the request received.
private EchoHandler echoHandler; private EchoHandler echoHandler;
// Handler attached to client's channel to record the response received. // Handler attached to client's channel to record the response received.
private DumpHandler dumpHandler; private DumpHandler dumpHandler;
private Channel channel; private Channel channel;
// All I/O operations are done inside the single thread within this event loop group, which is // All I/O operations are done inside the single thread within this event loop group, which is
@ -81,25 +81,20 @@ public final class NettyRule extends ExternalResource {
channel.writeAndFlush(Unpooled.wrappedBuffer(data.getBytes(US_ASCII))); channel.writeAndFlush(Unpooled.wrappedBuffer(data.getBytes(US_ASCII)));
} }
/** /** Sets up a server channel bound to the given local address. */
* Sets up a server channel bound to the given local address.
*/
public void setUpServer(LocalAddress localAddress, ChannelHandler... handlers) { public void setUpServer(LocalAddress localAddress, ChannelHandler... handlers) {
checkState(echoHandler == null, "Can't call setUpServer twice"); checkState(echoHandler == null, "Can't call setUpServer twice");
echoHandler = new EchoHandler(); echoHandler = new EchoHandler();
new TestServer(eventLoopGroup, localAddress, new TestServer(
eventLoopGroup,
localAddress,
ImmutableList.<ChannelHandler>builder().add(handlers).add(echoHandler).build()); ImmutableList.<ChannelHandler>builder().add(handlers).add(echoHandler).build());
} }
/** /** Sets up a client channel connecting to the give local address. */
* Sets up a client channel connecting to the give local address.
*/
void setUpClient( void setUpClient(
LocalAddress localAddress, LocalAddress localAddress, Protocol protocol, String host, ChannelHandler handler) {
Protocol protocol,
String host,
ChannelHandler handler) {
checkState(echoHandler != null, "Must call setUpServer before setUpClient"); checkState(echoHandler != null, "Must call setUpServer before setUpClient");
checkState(dumpHandler == null, "Can't call setUpClient twice"); checkState(dumpHandler == null, "Can't call setUpClient twice");
dumpHandler = new DumpHandler(); dumpHandler = new DumpHandler();
@ -128,20 +123,17 @@ public final class NettyRule extends ExternalResource {
checkState(channel != null, "Must call setUpClient to finish NettyRule setup"); checkState(channel != null, "Must call setUpClient to finish NettyRule setup");
} }
/** /** Test that custom setup to send message to current server sends right message */
* Test that custom setup to send message to current server sends right message
*/
public void assertReceivedMessage(String message) throws Exception { public void assertReceivedMessage(String message) throws Exception {
assertThat(echoHandler.getRequestFuture().get()).isEqualTo(message); assertThat(echoHandler.getRequestFuture().get()).isEqualTo(message);
} }
/** /**
* Test that a message can go through, both inbound and outbound. * Test that a message can go through, both inbound and outbound.
* *
* <p>The client writes the message to the server, which echos it back and saves the string in * <p>The client writes the message to the server, which echos it back and saves the string in its
* its promise. The client receives the echo and saves it in its promise. All these activities * promise. The client receives the echo and saves it in its promise. All these activities happens
* happens in the I/O thread, and this call itself returns immediately. * in the I/O thread, and this call itself returns immediately.
*/ */
void assertThatMessagesWork() throws Exception { void assertThatMessagesWork() throws Exception {
checkReady(); checkReady();
@ -199,9 +191,7 @@ public final class NettyRule extends ExternalResource {
ctx.writeAndFlush(msg).addListener(f -> requestFuture.complete(request)); ctx.writeAndFlush(msg).addListener(f -> requestFuture.complete(request));
} }
/** /** Saves any inbound error as the cause of the promise failure. */
* Saves any inbound error as the cause of the promise failure.
*/
@Override @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ChannelFuture unusedFuture = ChannelFuture unusedFuture =
@ -209,9 +199,7 @@ public final class NettyRule extends ExternalResource {
} }
} }
/** /** A handler that dumps its inbound message to a promise that can be inspected later. */
* A handler that dumps its inbound message to a promise that can be inspected later.
*/
private static class DumpHandler extends ChannelInboundHandlerAdapter { private static class DumpHandler extends ChannelInboundHandlerAdapter {
private final CompletableFuture<String> responseFuture = new CompletableFuture<>(); private final CompletableFuture<String> responseFuture = new CompletableFuture<>();
@ -232,13 +220,10 @@ public final class NettyRule extends ExternalResource {
responseFuture.complete(response); responseFuture.complete(response);
} }
/** /** Saves any inbound error into the failure cause of the promise. */
* Saves any inbound error into the failure cause of the promise.
*/
@Override @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.channel().closeFuture().addListener(f -> responseFuture.completeExceptionally(cause)); ctx.channel().closeFuture().addListener(f -> responseFuture.completeExceptionally(cause));
} }
} }
} }

View file

@ -15,14 +15,14 @@
package google.registry.monitoring.blackbox.handlers; package google.registry.monitoring.blackbox.handlers;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static google.registry.monitoring.blackbox.ProbingAction.REMOTE_ADDRESS_KEY; import static google.registry.monitoring.blackbox.connection.ProbingAction.REMOTE_ADDRESS_KEY;
import static google.registry.monitoring.blackbox.Protocol.PROTOCOL_KEY; import static google.registry.monitoring.blackbox.connection.Protocol.PROTOCOL_KEY;
import static google.registry.monitoring.blackbox.handlers.SslInitializerTestUtils.getKeyPair; import static google.registry.monitoring.blackbox.handlers.SslInitializerTestUtils.getKeyPair;
import static google.registry.monitoring.blackbox.handlers.SslInitializerTestUtils.setUpSslChannel; import static google.registry.monitoring.blackbox.handlers.SslInitializerTestUtils.setUpSslChannel;
import static google.registry.monitoring.blackbox.handlers.SslInitializerTestUtils.signKeyPair; import static google.registry.monitoring.blackbox.handlers.SslInitializerTestUtils.signKeyPair;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import google.registry.monitoring.blackbox.Protocol; import google.registry.monitoring.blackbox.connection.Protocol;
import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelPipeline;
import io.netty.channel.embedded.EmbeddedChannel; import io.netty.channel.embedded.EmbeddedChannel;
@ -62,39 +62,33 @@ import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class) @RunWith(Parameterized.class)
public class SslClientInitializerTest { public class SslClientInitializerTest {
/** /** Fake host to test if the SSL engine gets the correct peer host. */
* Fake host to test if the SSL engine gets the correct peer host.
*/
private static final String SSL_HOST = "www.example.tld"; private static final String SSL_HOST = "www.example.tld";
/** /** Fake port to test if the SSL engine gets the correct peer port. */
* Fake port to test if the SSL engine gets the correct peer port.
*/
private static final int SSL_PORT = 12345; private static final int SSL_PORT = 12345;
/** /** Fake protocol saved in channel attribute. */
* Fake protocol saved in channel attribute. private static final Protocol PROTOCOL =
*/ Protocol.builder()
private static final Protocol PROTOCOL = Protocol.builder()
.setName("ssl") .setName("ssl")
.setPort(SSL_PORT) .setPort(SSL_PORT)
.setHandlerProviders(ImmutableList.of()) .setHandlerProviders(ImmutableList.of())
.setPersistentConnection(false) .setPersistentConnection(false)
.build(); .build();
@Rule
public NettyRule nettyRule = new NettyRule(); @Rule public NettyRule nettyRule = new NettyRule();
@Parameter(0) @Parameter(0)
public SslProvider sslProvider; public SslProvider sslProvider;
/** /** Saves the SNI hostname received by the server, if sent by the client. */
* Saves the SNI hostname received by the server, if sent by the client.
*/
private String sniHostReceived; private String sniHostReceived;
// We do our best effort to test all available SSL providers. // We do our best effort to test all available SSL providers.
@Parameters(name = "{0}") @Parameters(name = "{0}")
public static SslProvider[] data() { public static SslProvider[] data() {
return OpenSsl.isAvailable() return OpenSsl.isAvailable()
? new SslProvider[]{SslProvider.JDK, SslProvider.OPENSSL} ? new SslProvider[] {SslProvider.JDK, SslProvider.OPENSSL}
: new SslProvider[]{SslProvider.JDK}; : new SslProvider[] {SslProvider.JDK};
} }
private ChannelHandler getServerHandler(PrivateKey privateKey, X509Certificate certificate) private ChannelHandler getServerHandler(PrivateKey privateKey, X509Certificate certificate)
@ -170,7 +164,7 @@ public class SslClientInitializerTest {
// Set up the client to trust the self signed cert used to sign the cert that server provides. // Set up the client to trust the self signed cert used to sign the cert that server provides.
SslClientInitializer<LocalChannel> sslClientInitializer = SslClientInitializer<LocalChannel> sslClientInitializer =
new SslClientInitializer<>(sslProvider, new X509Certificate[]{ssc.cert()}); new SslClientInitializer<>(sslProvider, new X509Certificate[] {ssc.cert()});
nettyRule.setUpClient(localAddress, PROTOCOL, SSL_HOST, sslClientInitializer); nettyRule.setUpClient(localAddress, PROTOCOL, SSL_HOST, sslClientInitializer);
@ -199,7 +193,7 @@ public class SslClientInitializerTest {
// Set up the client to trust the self signed cert used to sign the cert that server provides. // Set up the client to trust the self signed cert used to sign the cert that server provides.
SslClientInitializer<LocalChannel> sslClientInitializer = SslClientInitializer<LocalChannel> sslClientInitializer =
new SslClientInitializer<>(sslProvider, new X509Certificate[]{ssc.cert()}); new SslClientInitializer<>(sslProvider, new X509Certificate[] {ssc.cert()});
nettyRule.setUpClient(localAddress, PROTOCOL, SSL_HOST, sslClientInitializer); nettyRule.setUpClient(localAddress, PROTOCOL, SSL_HOST, sslClientInitializer);
@ -211,4 +205,3 @@ public class SslClientInitializerTest {
assertThat(nettyRule.getChannel().isActive()).isFalse(); assertThat(nettyRule.getChannel().isActive()).isFalse();
} }
} }

View file

@ -33,9 +33,7 @@ import javax.security.auth.x500.X500Principal;
import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.x509.X509V3CertificateGenerator; import org.bouncycastle.x509.X509V3CertificateGenerator;
/** /** Utility class that provides methods used by {@link SslClientInitializerTest} */
* Utility class that provides methods used by {@link SslClientInitializerTest}
*/
public class SslInitializerTestUtils { public class SslInitializerTestUtils {
static { static {
@ -74,10 +72,7 @@ public class SslInitializerTestUtils {
* @param certs The certificate that the server should provide. * @param certs The certificate that the server should provide.
* @return The SSL session in current channel, can be used for further validation. * @return The SSL session in current channel, can be used for further validation.
*/ */
static SSLSession setUpSslChannel( static SSLSession setUpSslChannel(Channel channel, X509Certificate... certs) throws Exception {
Channel channel,
X509Certificate... certs)
throws Exception {
SslHandler sslHandler = channel.pipeline().get(SslHandler.class); SslHandler sslHandler = channel.pipeline().get(SslHandler.class);
// Wait till the handshake is complete. // Wait till the handshake is complete.
sslHandler.handshakeFuture().get(); sslHandler.handshakeFuture().get();
@ -92,4 +87,3 @@ public class SslInitializerTestUtils {
return sslHandler.engine().getSession(); return sslHandler.engine().getSession();
} }
} }

View file

@ -37,6 +37,4 @@ public class TestActionHandler extends ActionHandler {
public InboundMessageType getResponse() { public InboundMessageType getResponse() {
return receivedMessage; return receivedMessage;
} }
} }

View file

@ -15,14 +15,14 @@
package google.registry.monitoring.blackbox.handlers; package google.registry.monitoring.blackbox.handlers;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static google.registry.monitoring.blackbox.ProbingAction.CONNECTION_FUTURE_KEY; import static google.registry.monitoring.blackbox.connection.ProbingAction.CONNECTION_FUTURE_KEY;
import static google.registry.monitoring.blackbox.Protocol.PROTOCOL_KEY; import static google.registry.monitoring.blackbox.connection.Protocol.PROTOCOL_KEY;
import static google.registry.monitoring.blackbox.TestUtils.makeHttpGetRequest; import static google.registry.monitoring.blackbox.util.WebWhoisUtils.makeHttpGetRequest;
import static google.registry.monitoring.blackbox.TestUtils.makeHttpResponse; import static google.registry.monitoring.blackbox.util.WebWhoisUtils.makeHttpResponse;
import static google.registry.monitoring.blackbox.TestUtils.makeRedirectResponse; import static google.registry.monitoring.blackbox.util.WebWhoisUtils.makeRedirectResponse;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import google.registry.monitoring.blackbox.Protocol; import google.registry.monitoring.blackbox.connection.Protocol;
import google.registry.monitoring.blackbox.exceptions.FailureException; import google.registry.monitoring.blackbox.exceptions.FailureException;
import google.registry.monitoring.blackbox.messages.HttpRequestMessage; import google.registry.monitoring.blackbox.messages.HttpRequestMessage;
import google.registry.monitoring.blackbox.messages.HttpResponseMessage; import google.registry.monitoring.blackbox.messages.HttpResponseMessage;
@ -45,8 +45,8 @@ import org.junit.runners.JUnit4;
/** /**
* Unit tests for {@link WebWhoisActionHandler}. * Unit tests for {@link WebWhoisActionHandler}.
* *
* <p>Attempts to test how well {@link WebWhoisActionHandler} works * <p>Attempts to test how well {@link WebWhoisActionHandler} works when responding to all possible
* when responding to all possible types of responses </p> * types of responses
*/ */
@RunWith(JUnit4.class) @RunWith(JUnit4.class)
public class WebWhoisActionHandlerTest { public class WebWhoisActionHandlerTest {
@ -55,25 +55,22 @@ public class WebWhoisActionHandlerTest {
private static final String HTTP_REDIRECT = "http://"; private static final String HTTP_REDIRECT = "http://";
private static final String TARGET_HOST = "whois.nic.tld"; private static final String TARGET_HOST = "whois.nic.tld";
private static final String DUMMY_URL = "__WILL_NOT_WORK__"; private static final String DUMMY_URL = "__WILL_NOT_WORK__";
private final Protocol standardProtocol = Protocol.builder() private final Protocol standardProtocol =
.setHandlerProviders(ImmutableList.of(() -> new WebWhoisActionHandler( Protocol.builder()
null, null, null, null))) .setHandlerProviders(
ImmutableList.of(() -> new WebWhoisActionHandler(null, null, null, null)))
.setName("http") .setName("http")
.setPersistentConnection(false) .setPersistentConnection(false)
.setPort(HTTP_PORT) .setPort(HTTP_PORT)
.build(); .build();
private EmbeddedChannel channel; private EmbeddedChannel channel;
private ActionHandler actionHandler; private ActionHandler actionHandler;
private Provider<? extends ChannelHandler> actionHandlerProvider; private Provider<? extends ChannelHandler> actionHandlerProvider;
private Protocol initialProtocol; private Protocol initialProtocol;
private HttpRequestMessage msg; private HttpRequestMessage msg;
/** Creates default protocol with empty list of handlers and specified other inputs */
/**
* Creates default protocol with empty list of handlers and specified other inputs
*/
private Protocol createProtocol(String name, int port, boolean persistentConnection) { private Protocol createProtocol(String name, int port, boolean persistentConnection) {
return Protocol.builder() return Protocol.builder()
.setName(name) .setName(name)
@ -83,22 +80,14 @@ public class WebWhoisActionHandlerTest {
.build(); .build();
} }
/** /** Initializes new WebWhoisActionHandler */
* Initializes new WebWhoisActionHandler
*/
private void setupActionHandler(Bootstrap bootstrap, HttpRequestMessage messageTemplate) { private void setupActionHandler(Bootstrap bootstrap, HttpRequestMessage messageTemplate) {
actionHandler = new WebWhoisActionHandler( actionHandler =
bootstrap, new WebWhoisActionHandler(bootstrap, standardProtocol, standardProtocol, messageTemplate);
standardProtocol,
standardProtocol,
messageTemplate
);
actionHandlerProvider = () -> actionHandler; actionHandlerProvider = () -> actionHandler;
} }
/** /** Sets up testing channel with requisite attributes */
* Sets up testing channel with requisite attributes
*/
private void setupChannel(Protocol protocol) { private void setupChannel(Protocol protocol) {
channel = new EmbeddedChannel(actionHandler); channel = new EmbeddedChannel(actionHandler);
channel.attr(PROTOCOL_KEY).set(protocol); channel.attr(PROTOCOL_KEY).set(protocol);
@ -106,43 +95,39 @@ public class WebWhoisActionHandlerTest {
} }
private Bootstrap makeBootstrap(EventLoopGroup group) { private Bootstrap makeBootstrap(EventLoopGroup group) {
return new Bootstrap() return new Bootstrap().group(group).channel(LocalChannel.class);
.group(group)
.channel(LocalChannel.class);
} }
private void setup(String hostName, Bootstrap bootstrap, boolean persistentConnection) { private void setup(String hostName, Bootstrap bootstrap, boolean persistentConnection) {
msg = new HttpRequestMessage(makeHttpGetRequest(hostName, "")); msg = new HttpRequestMessage(makeHttpGetRequest(hostName, ""));
setupActionHandler(bootstrap, msg); setupActionHandler(bootstrap, msg);
initialProtocol = createProtocol("testProtocol", 0, persistentConnection); initialProtocol = createProtocol("testProtocol", 0, persistentConnection);
} }
@Test @Test
public void testBasic_responseOk() { public void testBasic_responseOk() {
//setup // setup
setup("", null, true); setup("", null, true);
setupChannel(initialProtocol); setupChannel(initialProtocol);
//stores future // stores future
ChannelFuture future = actionHandler.getFinishedFuture(); ChannelFuture future = actionHandler.getFinishedFuture();
channel.writeOutbound(msg); channel.writeOutbound(msg);
FullHttpResponse response = new HttpResponseMessage(makeHttpResponse(HttpResponseStatus.OK)); FullHttpResponse response = new HttpResponseMessage(makeHttpResponse(HttpResponseStatus.OK));
//assesses that future listener isn't triggered yet. // assesses that future listener isn't triggered yet.
assertThat(future.isDone()).isFalse(); assertThat(future.isDone()).isFalse();
channel.writeInbound(response); channel.writeInbound(response);
//assesses that we successfully received good response and protocol is unchanged // assesses that we successfully received good response and protocol is unchanged
assertThat(future.isSuccess()).isTrue(); assertThat(future.isSuccess()).isTrue();
} }
@Test @Test
public void testBasic_responseFailure_badRequest() { public void testBasic_responseFailure_badRequest() {
//setup // setup
setup("", null, false); setup("", null, false);
setupChannel(initialProtocol); setupChannel(initialProtocol);
@ -150,8 +135,8 @@ public class WebWhoisActionHandlerTest {
ChannelFuture future = actionHandler.getFinishedFuture(); ChannelFuture future = actionHandler.getFinishedFuture();
channel.writeOutbound(msg); channel.writeOutbound(msg);
FullHttpResponse response = new HttpResponseMessage( FullHttpResponse response =
makeHttpResponse(HttpResponseStatus.BAD_REQUEST)); new HttpResponseMessage(makeHttpResponse(HttpResponseStatus.BAD_REQUEST));
// Assesses that future listener isn't triggered yet. // Assesses that future listener isn't triggered yet.
assertThat(future.isDone()).isFalse(); assertThat(future.isDone()).isFalse();
@ -168,23 +153,24 @@ public class WebWhoisActionHandlerTest {
@Test @Test
public void testBasic_responseFailure_badURL() { public void testBasic_responseFailure_badURL() {
//setup // setup
setup("", null, false); setup("", null, false);
setupChannel(initialProtocol); setupChannel(initialProtocol);
//stores future // stores future
ChannelFuture future = actionHandler.getFinishedFuture(); ChannelFuture future = actionHandler.getFinishedFuture();
channel.writeOutbound(msg); channel.writeOutbound(msg);
FullHttpResponse response = new HttpResponseMessage( FullHttpResponse response =
new HttpResponseMessage(
makeRedirectResponse(HttpResponseStatus.MOVED_PERMANENTLY, DUMMY_URL, true)); makeRedirectResponse(HttpResponseStatus.MOVED_PERMANENTLY, DUMMY_URL, true));
//assesses that future listener isn't triggered yet. // assesses that future listener isn't triggered yet.
assertThat(future.isDone()).isFalse(); assertThat(future.isDone()).isFalse();
channel.writeInbound(response); channel.writeInbound(response);
//assesses that listener is triggered, and event is success // assesses that listener is triggered, and event is success
assertThat(future.isDone()).isTrue(); assertThat(future.isDone()).isTrue();
assertThat(future.isSuccess()).isFalse(); assertThat(future.isSuccess()).isFalse();
@ -204,7 +190,7 @@ public class WebWhoisActionHandlerTest {
// Initializes LocalAddress with unique String. // Initializes LocalAddress with unique String.
LocalAddress address = new LocalAddress(TARGET_HOST); LocalAddress address = new LocalAddress(TARGET_HOST);
//stores future // stores future
ChannelFuture future = actionHandler.getFinishedFuture(); ChannelFuture future = actionHandler.getFinishedFuture();
channel.writeOutbound(msg); channel.writeOutbound(msg);
@ -215,18 +201,19 @@ public class WebWhoisActionHandlerTest {
TestServer.webWhoisServer(group, address, "", TARGET_HOST, path); TestServer.webWhoisServer(group, address, "", TARGET_HOST, path);
FullHttpResponse response = FullHttpResponse response =
new HttpResponseMessage(makeRedirectResponse(HttpResponseStatus.MOVED_PERMANENTLY, new HttpResponseMessage(
HTTP_REDIRECT + TARGET_HOST + path, true)); makeRedirectResponse(
HttpResponseStatus.MOVED_PERMANENTLY, HTTP_REDIRECT + TARGET_HOST + path, true));
//checks that future has not been set to successful or a failure // checks that future has not been set to successful or a failure
assertThat(future.isDone()).isFalse(); assertThat(future.isDone()).isFalse();
channel.writeInbound(response); channel.writeInbound(response);
//makes sure old channel is shut down when attempting redirection // makes sure old channel is shut down when attempting redirection
assertThat(channel.isActive()).isFalse(); assertThat(channel.isActive()).isFalse();
//assesses that we successfully received good response and protocol is unchanged // assesses that we successfully received good response and protocol is unchanged
assertThat(future.syncUninterruptibly().isSuccess()).isTrue(); assertThat(future.syncUninterruptibly().isSuccess()).isTrue();
} }
} }

View file

@ -0,0 +1,223 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// 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
package google.registry.monitoring.blackbox.messages;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.testing.JUnitBackports.assertThrows;
import static java.nio.charset.StandardCharsets.UTF_8;
import google.registry.monitoring.blackbox.exceptions.EppClientException;
import google.registry.monitoring.blackbox.exceptions.FailureException;
import google.registry.monitoring.blackbox.util.EppUtils;
import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;
@RunWith(JUnit4.class)
public class EppMessageTest {
private Document xmlDoc = null;
private Document greeting = null;
private DocumentBuilder builder = null;
@Before
public void setUp() throws Exception {
String xmlString =
"<epp>"
+ "<textAndAttr myAttr1='1'>text1</textAndAttr>"
+ "<textNoAttr>text2</textNoAttr>"
+ "<attrNoText myAttr2='2'/>"
+ "<textAndAttrSplitRepeated>text3</textAndAttrSplitRepeated>"
+ "<textAndAttrSplitRepeated myAttr3='3'/>"
+ "</epp>";
ByteArrayInputStream byteStream = new ByteArrayInputStream(xmlString.getBytes(UTF_8));
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
builder = factory.newDocumentBuilder();
xmlDoc = builder.parse(byteStream);
greeting = EppUtils.getGreeting();
}
@Test
public void xmlDocToStringSuccess() throws Exception {
Document xml = builder.newDocument();
Element doc = xml.createElement("doc");
Element title = xml.createElement("title");
title.setTextContent("test");
Element meta = xml.createElement("meta");
meta.setAttribute("version", "1.0");
doc.appendChild(title);
doc.appendChild(meta);
xml.appendChild(doc);
// note that setting the version just ensures this will either be the same in the result,
// or the result won't support the version and this will throw an exception.
xml.setXmlVersion("1.0");
// setting stand alone to true removes this from the processing instructions
xml.setXmlStandalone(true);
String expected =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+ "<doc><title>test</title><meta version=\"1.0\"/></doc>";
String actual = EppMessage.xmlDocToString(xml);
assertThat(actual).isEqualTo(expected);
}
@Test
public void xmlDoctoByteArraySuccess() throws Exception {
Document xml = builder.newDocument();
Element doc = xml.createElement("doc");
Element title = xml.createElement("title");
title.setTextContent("test");
Element meta = xml.createElement("meta");
meta.setAttribute("version", "1.0");
doc.appendChild(title);
doc.appendChild(meta);
xml.appendChild(doc);
// note that setting the version just ensures this will either be the same in the result,
// or the result won't support the version and this will throw an exception.
xml.setXmlVersion("1.0");
// setting stand alone to true removes this from the processing instructions
xml.setXmlStandalone(true);
String expected =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+ "<doc><title>test</title><meta version=\"1.0\"/></doc>";
byte[] actual = EppMessage.xmlDocToByteArray(xml);
assertThat(actual).isEqualTo(expected.getBytes(UTF_8));
}
@Test
public void eppValidateSuccess() throws Exception {
EppMessage.eppValidate(greeting);
}
@Test
public void eppValidateFail() {
assertThrows(SAXException.class, () -> EppMessage.eppValidate(xmlDoc));
}
/**
* These test a "success" without validating a document. This is to make it easier to test the
* XPath part of the verify in isolation. See EppClientConnectionTest for tests that verify an
* actual (greeting) respoonse.
*/
@Test
public void verifyResponseSuccess() throws Exception {
ArrayList<String> list = new ArrayList<>();
list.add("epp");
list.add("//textAndAttr[@myAttr1='1'] | //textAndAttr[child::text()='text1']");
list.add("//textAndAttr[child::text()='text1']");
list.add("//textAndAttr/@myAttr1");
list.add("//textAndAttrSplitRepeated[@myAttr3='3']");
EppMessage.verifyEppResponse(xmlDoc, list, false);
}
@Test
public void verifyEppResponseSuccess() throws Exception {
ArrayList<String> list = new ArrayList<>();
list.add("*");
list.add("/eppns:epp");
list.add("/eppns:epp/eppns:greeting");
list.add("//eppns:greeting");
list.add("//eppns:svID");
EppMessage.verifyEppResponse(greeting, list, false);
}
@Test
public void verifyResponseMissingTextFail() {
ArrayList<String> list = new ArrayList<>();
list.add("epp");
list.add("//textAndAttr[child::text()='text2']");
assertThrows(FailureException.class, () -> EppMessage.verifyEppResponse(xmlDoc, list, false));
}
@Test
public void verifyResponseMissingAttrFail() {
ArrayList<String> list = new ArrayList<>();
list.add("epp");
list.add("//textAndAttr/@myAttr2");
assertThrows(FailureException.class, () -> EppMessage.verifyEppResponse(xmlDoc, list, false));
}
@Test
public void verifyResponseSplitTextAttrFail() {
ArrayList<String> list = new ArrayList<>();
list.add("epp");
list.add("//textAndAttrSplitRepeated[@myAttr3='3'][child::text()='text3']");
assertThrows(FailureException.class, () -> EppMessage.verifyEppResponse(xmlDoc, list, false));
}
@Test
public void getEppDocFromTemplateTest() throws Exception {
Map<String, String> replaceMap = new HashMap<>();
replaceMap.put("//eppns:expectedClTRID", "ABC-1234-CBA");
replaceMap.put("//domainns:name", "foo");
replaceMap.put("//domainns:pw", "bar");
Document epp = EppMessage.getEppDocFromTemplate("create.xml", replaceMap);
List<String> noList = Collections.emptyList();
EppMessage.verifyEppResponse(epp, noList, true);
}
@Test
public void getEppDocFromTemplateTestFail() {
Map<String, String> replaceMap = new HashMap<>();
replaceMap.put("//eppns:create", "ABC-1234-CBA");
replaceMap.put("//domainns:name", "foo");
replaceMap.put("//domainns:pw", "bar");
assertThrows(
EppClientException.class, () -> EppMessage.getEppDocFromTemplate("create.xml", replaceMap));
}
@Test
public void checkValidDomainName() {
String domainName = "good.tld";
assertThat(domainName.matches(EppMessage.VALID_SLD_LABEL_REGEX)).isTrue();
domainName = "good.tld.";
assertThat(domainName.matches(EppMessage.VALID_SLD_LABEL_REGEX)).isTrue();
domainName = "g-o-o-d.tld.";
assertThat(domainName.matches(EppMessage.VALID_SLD_LABEL_REGEX)).isTrue();
domainName = "good.cc.tld";
assertThat(domainName.matches(EppMessage.VALID_SLD_LABEL_REGEX)).isTrue();
domainName = "good.cc.tld.";
assertThat(domainName.matches(EppMessage.VALID_SLD_LABEL_REGEX)).isTrue();
domainName = "too-short";
assertThat(domainName.matches(EppMessage.VALID_SLD_LABEL_REGEX)).isFalse();
domainName = "too-short.";
assertThat(domainName.matches(EppMessage.VALID_SLD_LABEL_REGEX)).isFalse();
// TODO(rgr): sync up how many dots is actually too many
domainName = "too.many.dots.tld.";
assertThat(domainName.matches(EppMessage.VALID_SLD_LABEL_REGEX)).isFalse();
domainName = "too.many.dots.tld";
assertThat(domainName.matches(EppMessage.VALID_SLD_LABEL_REGEX)).isFalse();
}
}

View file

@ -14,17 +14,22 @@
package google.registry.monitoring.blackbox.testservers; package google.registry.monitoring.blackbox.testservers;
import static google.registry.monitoring.blackbox.TestUtils.makeHttpResponse; import static google.registry.monitoring.blackbox.util.WebWhoisUtils.makeHttpResponse;
import static google.registry.monitoring.blackbox.TestUtils.makeRedirectResponse; import static google.registry.monitoring.blackbox.util.WebWhoisUtils.makeRedirectResponse;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import google.registry.monitoring.blackbox.exceptions.FailureException;
import google.registry.monitoring.blackbox.messages.EppMessage;
import google.registry.monitoring.blackbox.messages.HttpResponseMessage; import google.registry.monitoring.blackbox.messages.HttpResponseMessage;
import io.netty.bootstrap.ServerBootstrap; import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandler.Sharable; import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPromise;
import io.netty.channel.EventLoopGroup; import io.netty.channel.EventLoopGroup;
import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.local.LocalAddress; import io.netty.channel.local.LocalAddress;
@ -34,6 +39,7 @@ import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpResponseStatus;
import org.w3c.dom.Document;
/** /**
* Mock Server Superclass whose subclasses implement specific behaviors we expect blackbox server to * Mock Server Superclass whose subclasses implement specific behaviors we expect blackbox server to
@ -45,10 +51,13 @@ public class TestServer {
this(new NioEventLoopGroup(1), localAddress, handlers); this(new NioEventLoopGroup(1), localAddress, handlers);
} }
public TestServer(EventLoopGroup eventLoopGroup, LocalAddress localAddress, public TestServer(
EventLoopGroup eventLoopGroup,
LocalAddress localAddress,
ImmutableList<? extends ChannelHandler> handlers) { ImmutableList<? extends ChannelHandler> handlers) {
//Creates ChannelInitializer with handlers specified // Creates ChannelInitializer with handlers specified
ChannelInitializer<LocalChannel> serverInitializer = new ChannelInitializer<LocalChannel>() { ChannelInitializer<LocalChannel> serverInitializer =
new ChannelInitializer<LocalChannel>() {
@Override @Override
protected void initChannel(LocalChannel ch) { protected void initChannel(LocalChannel ch) {
for (ChannelHandler handler : handlers) { for (ChannelHandler handler : handlers) {
@ -56,7 +65,7 @@ public class TestServer {
} }
} }
}; };
//Sets up serverBootstrap with specified initializer, eventLoopGroup, and using // Sets up serverBootstrap with specified initializer, eventLoopGroup, and using
// LocalServerChannel class // LocalServerChannel class
ServerBootstrap serverBootstrap = ServerBootstrap serverBootstrap =
new ServerBootstrap() new ServerBootstrap()
@ -65,22 +74,27 @@ public class TestServer {
.childHandler(serverInitializer); .childHandler(serverInitializer);
ChannelFuture unusedFuture = serverBootstrap.bind(localAddress).syncUninterruptibly(); ChannelFuture unusedFuture = serverBootstrap.bind(localAddress).syncUninterruptibly();
} }
public static TestServer webWhoisServer(EventLoopGroup eventLoopGroup, public static TestServer webWhoisServer(
LocalAddress localAddress, String redirectInput, String destinationInput, EventLoopGroup eventLoopGroup,
LocalAddress localAddress,
String redirectInput,
String destinationInput,
String destinationPath) { String destinationPath) {
return new TestServer( return new TestServer(
eventLoopGroup, eventLoopGroup,
localAddress, localAddress,
ImmutableList.of(new RedirectHandler(redirectInput, destinationInput, destinationPath)) ImmutableList.of(new RedirectHandler(redirectInput, destinationInput, destinationPath)));
);
} }
/** public static TestServer eppServer(EventLoopGroup eventLoopGroup, LocalAddress localAddress) {
* Handler that will wither redirect client, give successful response, or give error messge // TODO - add LengthFieldBasedFrameDecoder to handlers in pipeline if necessary for future
*/ // tests.
return new TestServer(eventLoopGroup, localAddress, ImmutableList.of(new EppHandler()));
}
/** Handler that will wither redirect client, give successful response, or give error messge */
@Sharable @Sharable
static class RedirectHandler extends SimpleChannelInboundHandler<HttpRequest> { static class RedirectHandler extends SimpleChannelInboundHandler<HttpRequest> {
@ -108,7 +122,8 @@ public class TestServer {
public void channelRead0(ChannelHandlerContext ctx, HttpRequest request) { public void channelRead0(ChannelHandlerContext ctx, HttpRequest request) {
HttpResponse response; HttpResponse response;
if (request.headers().get("host").equals(redirectInput)) { if (request.headers().get("host").equals(redirectInput)) {
response = new HttpResponseMessage( response =
new HttpResponseMessage(
makeRedirectResponse(HttpResponseStatus.MOVED_PERMANENTLY, destinationInput, true)); makeRedirectResponse(HttpResponseStatus.MOVED_PERMANENTLY, destinationInput, true));
} else if (request.headers().get("host").equals(destinationInput) } else if (request.headers().get("host").equals(destinationInput)
&& request.uri().equals(destinationPath)) { && request.uri().equals(destinationPath)) {
@ -117,7 +132,36 @@ public class TestServer {
response = new HttpResponseMessage(makeHttpResponse(HttpResponseStatus.BAD_REQUEST)); response = new HttpResponseMessage(makeHttpResponse(HttpResponseStatus.BAD_REQUEST));
} }
ChannelFuture unusedFuture = ctx.channel().writeAndFlush(response); ChannelFuture unusedFuture = ctx.channel().writeAndFlush(response);
}
}
private static class EppHandler extends ChannelDuplexHandler {
Document doc;
private ChannelPromise future;
@Override
public void handlerAdded(ChannelHandlerContext ctx) {
// TODO - pass EppMessage into future to easily read attributes of message.
future = ctx.newPromise();
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf buf = (ByteBuf) msg;
byte[] messageBytes = new byte[buf.readableBytes()];
buf.readBytes(messageBytes);
// TODO - Break ByteBuf into multiple pieces to test how well it works with
// LengthFieldBasedFrameDecoder.
try {
doc = EppMessage.byteArrayToXmlDoc(messageBytes);
ChannelFuture unusedFuture = future.setSuccess();
} catch (FailureException e) {
ChannelFuture unusedFuture = future.setFailure(e);
}
} }
} }
} }

View file

@ -0,0 +1,91 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// 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.
package google.registry.monitoring.blackbox.tokens;
import static com.google.common.truth.Truth.assertThat;
import google.registry.monitoring.blackbox.exceptions.UndeterminedStateException;
import google.registry.monitoring.blackbox.messages.EppRequestMessage;
import google.registry.monitoring.blackbox.util.EppUtils;
import io.netty.channel.Channel;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.Mockito;
/** Unit Tests for each {@link Token} subtype (just {@link WebWhoisToken} for now) */
@RunWith(JUnit4.class)
public class EppTokenTest {
private static String TEST_HOST = "host";
private static String TEST_TLD = "tld";
private EppToken persistentEppToken = new EppToken.Persistent(TEST_TLD, TEST_HOST);
private EppToken transientEppToken = new EppToken.Transient(TEST_TLD, TEST_HOST);
@Test
public void testMessageModificationSuccess_PersistentToken() throws UndeterminedStateException {
EppRequestMessage originalMessage = EppUtils.getCreateMessage(EppUtils.getSuccessResponse());
String domainName = persistentEppToken.getCurrentDomainName();
String clTrid = domainName.substring(0, domainName.indexOf('.'));
EppRequestMessage modifiedMessage =
(EppRequestMessage) persistentEppToken.modifyMessage(originalMessage);
// ensure element values are what they should be
assertThat(modifiedMessage.getElementValue("//domainns:name")).isEqualTo(domainName);
assertThat(modifiedMessage.getElementValue("//eppns:clTRID")).isNotEqualTo(clTrid);
}
@Test
public void testMessageModificationSuccess_TransientToken() throws UndeterminedStateException {
EppRequestMessage originalMessage = EppUtils.getCreateMessage(EppUtils.getSuccessResponse());
String domainName = transientEppToken.getCurrentDomainName();
String clTrid = domainName.substring(0, domainName.indexOf('.'));
EppRequestMessage modifiedMessage =
(EppRequestMessage) transientEppToken.modifyMessage(originalMessage);
// ensure element values are what they should be
assertThat(modifiedMessage.getElementValue("//domainns:name")).isEqualTo(domainName);
assertThat(modifiedMessage.getElementValue("//eppns:clTRID")).isNotEqualTo(clTrid);
}
@Test
public void testNext_persistentToken() {
String domainName = persistentEppToken.getCurrentDomainName();
Channel mockChannel = Mockito.mock(Channel.class);
persistentEppToken.setChannel(mockChannel);
EppToken nextToken = (EppToken) persistentEppToken.next();
assertThat(nextToken.getCurrentDomainName()).isNotEqualTo(domainName);
assertThat(nextToken.channel()).isEqualTo(mockChannel);
}
@Test
public void testNext_transientToken() {
String domainName = transientEppToken.getCurrentDomainName();
Channel mockChannel = Mockito.mock(Channel.class);
transientEppToken.setChannel(mockChannel);
EppToken nextToken = (EppToken) transientEppToken.next();
assertThat(nextToken.getCurrentDomainName()).isNotEqualTo(domainName);
assertThat(nextToken.channel()).isNull();
}
}

View file

@ -23,9 +23,7 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.junit.runners.JUnit4; import org.junit.runners.JUnit4;
/** /** Unit Tests for {@link WebWhoisToken} */
* Unit Tests for {@link WebWhoisToken}
*/
@RunWith(JUnit4.class) @RunWith(JUnit4.class)
public class WebWhoisTokenTest { public class WebWhoisTokenTest {
@ -35,19 +33,17 @@ public class WebWhoisTokenTest {
private static final String SECOND_TLD = "second_test"; private static final String SECOND_TLD = "second_test";
private static final String THIRD_TLD = "third_test"; private static final String THIRD_TLD = "third_test";
private final CircularList<String> testDomains = private final CircularList<String> testDomains =
new CircularList.Builder<String>() new CircularList.Builder<String>().add(FIRST_TLD, SECOND_TLD, THIRD_TLD).build();
.add(FIRST_TLD, SECOND_TLD, THIRD_TLD)
.build();
public Token webToken = new WebWhoisToken(testDomains); public Token webToken = new WebWhoisToken(testDomains);
@Test @Test
public void testMessageModification() throws UndeterminedStateException { public void testMessageModification() throws UndeterminedStateException {
//creates Request message with header // creates Request message with header
HttpRequestMessage message = new HttpRequestMessage(); HttpRequestMessage message = new HttpRequestMessage();
message.headers().set("host", HOST); message.headers().set("host", HOST);
//attempts to use Token's method for modifying the method based on its stored host // attempts to use Token's method for modifying the method based on its stored host
HttpRequestMessage secondMessage = (HttpRequestMessage) webToken.modifyMessage(message); HttpRequestMessage secondMessage = (HttpRequestMessage) webToken.modifyMessage(message);
assertThat(secondMessage.headers().get("host")).isEqualTo(PREFIX + FIRST_TLD); assertThat(secondMessage.headers().get("host")).isEqualTo(PREFIX + FIRST_TLD);
} }
@ -69,5 +65,4 @@ public class WebWhoisTokenTest {
assertThat(webToken.host()).isEqualTo(PREFIX + FIRST_TLD); assertThat(webToken.host()).isEqualTo(PREFIX + FIRST_TLD);
} }
} }

View file

@ -0,0 +1,197 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// 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.
package google.registry.monitoring.blackbox.util;
import static google.registry.monitoring.blackbox.messages.EppMessage.CLIENT_ID_KEY;
import static google.registry.monitoring.blackbox.messages.EppMessage.CLIENT_PASSWORD_KEY;
import static google.registry.monitoring.blackbox.messages.EppMessage.CLIENT_TRID_KEY;
import static google.registry.monitoring.blackbox.messages.EppMessage.DOMAIN_KEY;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import google.registry.monitoring.blackbox.exceptions.EppClientException;
import google.registry.monitoring.blackbox.messages.EppMessage;
import google.registry.monitoring.blackbox.messages.EppRequestMessage;
import google.registry.monitoring.blackbox.messages.EppResponseMessage;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import java.io.IOException;
import org.w3c.dom.Document;
/** Houses static utility functions for testing EPP components of Prober. */
public class EppUtils {
/** Return a simple default greeting as a {@link Document}. */
public static Document getGreeting() throws IOException, EppClientException {
return EppMessage.getEppDocFromTemplate("greeting.xml", ImmutableMap.of());
}
/**
* Return a basic response as a {@link Document}.
*
* @param success specifies if response shows a success or failure
* @param clTrid the client transaction ID
* @param svTrid the server transaction ID
* @return the EPP basic response as a {@link Document}.
*/
public static Document getBasicResponse(boolean success, String clTrid, String svTrid)
throws IOException, EppClientException {
String template = success ? "success_response.xml" : "failed_response.xml";
return EppMessage.getEppDocFromTemplate(
template,
ImmutableMap.of(
EppRequestMessage.CLIENT_TRID_KEY, clTrid, EppMessage.SERVER_TRID_KEY, svTrid));
}
/**
* Return a domain check response as a {@link Document}.
*
* @param exists specifies if response shows that domain name exists on server or doesn't
* @param clTrid the client transaction ID
* @param svTrid the server transaction ID
* @param domain the domain the check success is for
* @return the EPP check response as a {@link Document}.
*/
public static Document getDomainCheck(boolean exists, String clTrid, String svTrid, String domain)
throws IOException, EppClientException {
String template = exists ? "domain_exists.xml" : "domain_not_exists.xml";
return EppMessage.getEppDocFromTemplate(
template,
ImmutableMap.of(
EppRequestMessage.CLIENT_TRID_KEY, clTrid,
EppMessage.SERVER_TRID_KEY, svTrid,
EppMessage.DOMAIN_KEY, domain));
}
/** Converts {@link Document} to {@link ByteBuf}. */
public static ByteBuf docToByteBuf(Document message) throws EppClientException {
byte[] bytestream = EppMessage.xmlDocToByteArray(message);
ByteBuf buf = Unpooled.buffer(bytestream.length);
buf.writeBytes(bytestream);
return buf;
}
/** Returns standard hello request with supplied response. */
public static EppRequestMessage getHelloMessage(EppResponseMessage greetingResponse) {
return new EppRequestMessage(greetingResponse, null, (a, b) -> ImmutableMap.of());
}
/** Returns standard login request with supplied userId, userPassword, and response. */
public static EppRequestMessage getLoginMessage(
EppResponseMessage response, String userId, String userPassword) {
return new EppRequestMessage(
response,
"login.xml",
(clTrid, domain) ->
ImmutableMap.of(
CLIENT_TRID_KEY, clTrid,
CLIENT_ID_KEY, userId,
CLIENT_PASSWORD_KEY, userPassword));
}
/** Returns standard create request with supplied response. */
public static EppRequestMessage getCreateMessage(EppResponseMessage response) {
return new EppRequestMessage(
response,
"create.xml",
(clTrid, domain) ->
ImmutableMap.of(
CLIENT_TRID_KEY, clTrid,
DOMAIN_KEY, domain));
}
/** Returns standard delete request with supplied response. */
public static EppRequestMessage getDeleteMessage(EppResponseMessage response) {
return new EppRequestMessage(
response,
"delete.xml",
(clTrid, domain) ->
ImmutableMap.of(
CLIENT_TRID_KEY, clTrid,
DOMAIN_KEY, domain));
}
/** Returns standard logout request with supplied response. */
public static EppRequestMessage getLogoutMessage(EppResponseMessage successResponse) {
return new EppRequestMessage(
successResponse,
"logout.xml",
(clTrid, domain) -> ImmutableMap.of(CLIENT_TRID_KEY, clTrid));
}
/** Returns standard check request with supplied response. */
public static EppRequestMessage getCheckMessage(EppResponseMessage response) {
return new EppRequestMessage(
response,
"check.xml",
(clTrid, domain) ->
ImmutableMap.of(
CLIENT_TRID_KEY, clTrid,
DOMAIN_KEY, domain));
}
/** Returns standard success response. */
public static EppResponseMessage getSuccessResponse() {
return new EppResponseMessage(
"success",
(clTrid, domain) ->
ImmutableList.of(
String.format("//eppns:clTRID[.='%s']", clTrid), EppMessage.XPASS_EXPRESSION));
}
/** Returns standard failure response. */
public static EppResponseMessage getFailureResponse() {
return new EppResponseMessage(
"failure",
(clTrid, domain) ->
ImmutableList.of(
String.format("//eppns:clTRID[.='%s']", clTrid), EppMessage.XFAIL_EXPRESSION));
}
/** Returns standard domainExists response. */
public static EppResponseMessage getDomainExistsResponse() {
return new EppResponseMessage(
"domainExists",
(clTrid, domain) ->
ImmutableList.of(
String.format("//eppns:clTRID[.='%s']", clTrid),
String.format("//domainns:name[@avail='false'][.='%s']", domain),
EppMessage.XPASS_EXPRESSION));
}
/** Returns standard domainNotExists response. */
public static EppResponseMessage getDomainNotExistsResponse() {
return new EppResponseMessage(
"domainNotExists",
(clTrid, domain) ->
ImmutableList.of(
String.format("//eppns:clTRID[.='%s']", clTrid),
String.format("//domainns:name[@avail='true'][.='%s']", domain),
EppMessage.XPASS_EXPRESSION));
}
/** Returns standard greeting response. */
public static EppResponseMessage getGreetingResponse() {
return new EppResponseMessage(
"greeting", (clTrid, domain) -> ImmutableList.of("//eppns:greeting"));
}
}

View file

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package google.registry.monitoring.blackbox; package google.registry.monitoring.blackbox.util;
import static java.nio.charset.StandardCharsets.US_ASCII; import static java.nio.charset.StandardCharsets.US_ASCII;
@ -26,10 +26,8 @@ import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.codec.http.HttpVersion;
/** /** Houses static utility functions for testing WebWHOIS components of Prober. */
* Utility class for various helper methods used in testing. public class WebWhoisUtils {
*/
public class TestUtils {
public static FullHttpRequest makeHttpGetRequest(String host, String path) { public static FullHttpRequest makeHttpGetRequest(String host, String path) {
FullHttpRequest request = FullHttpRequest request =
@ -51,9 +49,7 @@ public class TestUtils {
return response; return response;
} }
/** /** Creates HttpResponse given status, redirection location, and other necessary inputs */
* Creates HttpResponse given status, redirection location, and other necessary inputs
*/
public static FullHttpResponse makeRedirectResponse( public static FullHttpResponse makeRedirectResponse(
HttpResponseStatus status, String location, boolean keepAlive) { HttpResponseStatus status, String location, boolean keepAlive) {
FullHttpResponse response = makeHttpResponse("", status); FullHttpResponse response = makeHttpResponse("", status);
@ -67,4 +63,3 @@ public class TestUtils {
return response; return response;
} }
} }

View file

@ -15,69 +15,51 @@
package google.registry.util; package google.registry.util;
/** /**
* Class that stores value {@param <T>}, and points in circle to other {@link CircularList} * Class that stores value {@param <T>}, and points in circle to other {@link CircularList} objects.
* objects. *
* <p>In its construction, we create a sequence of {@link CircularList} objects, each storing an *
* instance of T. They each point to each other in a circular manner, such that we can perform *
* circular iteration on the elements. Once finished building, we return this first {@link *
* CircularList} object in the sequence.
* *
* @param <T> - Element type stored in the {@link CircularList} * @param <T> - Element type stored in the {@link CircularList}
*
* <p>In its construction, we create a sequence of {@link CircularList} objects, each storing an
* instance of T. They each point to each other in a circular manner, such that we can perform
* circular iteration on the elements. Once finished building, we return this first {@link
* CircularList} object in the sequence.</p>
*/ */
public class CircularList<T> { public class CircularList<T> {
/** /** T instance stored in current node of list. */
* T instance stored in current node of list.
*/
private final T value; private final T value;
/** /** Pointer to next node of list. */
* Pointer to next node of list.
*/
private CircularList<T> next; private CircularList<T> next;
/** /** Standard constructor for {@link CircularList} that initializes its stored {@code value}. */
* Standard constructor for {@link CircularList} that initializes its stored {@code value}.
*/
protected CircularList(T value) { protected CircularList(T value) {
this.value = value; this.value = value;
} }
/** /** Standard get method to retrieve {@code value}. */
* Standard get method to retrieve {@code value}.
*/
public T get() { public T get() {
return value; return value;
} }
/** /** Standard method to obtain {@code next} node in list. */
* Standard method to obtain {@code next} node in list.
*/
public CircularList<T> next() { public CircularList<T> next() {
return next; return next;
} }
/** /** Setter method only used in builder to point one node to the next. */
* Setter method only used in builder to point one node to the next.
*/
public void setNext(CircularList<T> next) { public void setNext(CircularList<T> next) {
this.next = next; this.next = next;
} }
/** /** Default Builder to create a standard instance of a {@link CircularList}. */
* Default Builder to create a standard instance of a {@link CircularList}.
*/
public static class Builder<T> extends AbstractBuilder<T, CircularList<T>> { public static class Builder<T> extends AbstractBuilder<T, CircularList<T>> {
/** /** Simply calls on constructor to {@link CircularList} to create new instance. */
* Simply calls on constructor to {@link CircularList} to create new instance.
*/
@Override @Override
protected CircularList<T> create(T value) { protected CircularList<T> create(T value) {
return new CircularList<>(value); return new CircularList<>(value);
} }
} }
/** /**
@ -85,26 +67,20 @@ public class CircularList<T> {
* create the full list. * create the full list.
* *
* @param <T> - Matching element type of iterator * @param <T> - Matching element type of iterator
* * <p>Supports adding in element at a time, adding an {@link Iterable} of elements, and adding
* <p>Supports adding in element at a time, adding an {@link Iterable} * an variable number of elemetns.
* of elements, and adding an variable number of elemetns.</p>
*
* <p>Sets first element added to {@code first}, and when built, points last element to the * <p>Sets first element added to {@code first}, and when built, points last element to the
* {@code first} element.</p> * {@code first} element.
*/ */
public abstract static class AbstractBuilder<T, C extends CircularList<T>> { public abstract static class AbstractBuilder<T, C extends CircularList<T>> {
protected C first; protected C first;
protected C current; protected C current;
/** /** Necessary to instantiate each {@code C} object from {@code value}. */
* Necessary to instantiate each {@code C} object from {@code value}.
*/
protected abstract C create(T value); protected abstract C create(T value);
/** /** Sets current {@code C} to element added and points previous {@code C} to this one. */
* Sets current {@code C} to element added and points previous {@code C} to this one.
*/
public AbstractBuilder<T, C> add(T value) { public AbstractBuilder<T, C> add(T value) {
C c = create(value); C c = create(value);
if (current == null) { if (current == null) {
@ -116,9 +92,7 @@ public class CircularList<T> {
return this; return this;
} }
/** /** Simply calls {@code addElement}, for each element in {@code elements}. */
* Simply calls {@code addElement}, for each element in {@code elements}.
*/
public AbstractBuilder<T, C> add(T... values) { public AbstractBuilder<T, C> add(T... values) {
for (T element : values) { for (T element : values) {
add(element); add(element);
@ -126,21 +100,16 @@ public class CircularList<T> {
return this; return this;
} }
/** /** Simply calls {@code addElement}, for each element in {@code elements}. */
* Simply calls {@code addElement}, for each element in {@code elements}.
*/
public AbstractBuilder<T, C> add(Iterable<T> values) { public AbstractBuilder<T, C> add(Iterable<T> values) {
values.forEach(this::add); values.forEach(this::add);
return this; return this;
} }
/** /** Sets the next {@code C} of the list to be the first {@code C} in the list. */
* Sets the next {@code C} of the list to be the first {@code C} in the list.
*/
public C build() { public C build() {
current.setNext(first); current.setNext(first);
return first; return first;
} }
} }
} }