ONOS-4919: Implement RESTCONF client

Adding code to support RESTCONF protocol as one of
the supported SBI protocols of ONOS. This RESTCONF SBI extends
the current REST SBI protocl and adds some new APIs/functinalities
so that a provider can subscribe/register to an external restconf
server to receive notification stream.

Change-Id: I21bf0d0f0394cf788e066d743b3ade04735fe07e
diff --git a/protocols/rest/api/BUCK b/protocols/rest/api/BUCK
index 95edc2e..74f33b2 100644
--- a/protocols/rest/api/BUCK
+++ b/protocols/rest/api/BUCK
@@ -1,7 +1,16 @@
 COMPILE_DEPS = [
-    '//lib:CORE_DEPS',
     '//incubator/api:onos-incubator-api',
     '//utils/rest:onlab-rest',
+    '//lib:CORE_DEPS',
+    '//lib:jersey-client',
+    '//lib:jersey-common',
+    '//lib:httpclient-osgi',
+    '//lib:httpcore-osgi',
+    '//lib:javax.ws.rs-api',
+    '//lib:hk2-api',
+    '//lib:jersey-guava',
+    '//lib:aopalliance-repackaged',
+    '//lib:javax.inject',
 ]
 
 osgi_jar_with_tests (
diff --git a/protocols/rest/api/pom.xml b/protocols/rest/api/pom.xml
index 26c5456..f6f05cb 100644
--- a/protocols/rest/api/pom.xml
+++ b/protocols/rest/api/pom.xml
@@ -28,6 +28,29 @@
     <artifactId>onos-restsb-api</artifactId>
     <packaging>bundle</packaging>
 
+    <dependencies>
+	    <dependency>
+	        <groupId>org.glassfish.jersey.core</groupId>
+	        <artifactId>jersey-client</artifactId>
+	    </dependency>
+	    <dependency>
+	        <groupId>org.apache.httpcomponents</groupId>
+	        <artifactId>httpclient-osgi</artifactId>
+	        <version>4.5.1</version>
+	    </dependency>
+	    <dependency>
+	        <groupId>commons-io</groupId>
+	        <artifactId>commons-io</artifactId>
+	        <version>2.4</version>
+	    </dependency>
+	    <dependency>
+	      <groupId>junit</groupId>
+	      <artifactId>junit</artifactId>
+	      <version>3.8.1</version>
+	      <scope>test</scope>
+	    </dependency>
+    </dependencies>
+
     <description>ONOS Rest southbound plugin API</description>
 
 
diff --git a/protocols/rest/api/src/main/java/org/onosproject/protocol/http/HttpSBController.java b/protocols/rest/api/src/main/java/org/onosproject/protocol/http/HttpSBController.java
new file mode 100644
index 0000000..1694d2a
--- /dev/null
+++ b/protocols/rest/api/src/main/java/org/onosproject/protocol/http/HttpSBController.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * 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 org.onosproject.protocol.http;
+
+import org.onlab.packet.IpAddress;
+import org.onosproject.net.DeviceId;
+import org.onosproject.protocol.rest.RestSBDevice;
+
+import java.io.InputStream;
+import java.util.Map;
+
+/**
+ * Abstraction of an HTTP controller. Serves as a one stop shop for obtaining
+ * HTTP southbound devices and (un)register listeners.
+ */
+public interface HttpSBController {
+
+    /**
+     * Returns all the devices known to this controller.
+     *
+     * @return map of devices
+     */
+    Map<DeviceId, RestSBDevice> getDevices();
+
+    /**
+     * Returns a device by node identifier.
+     *
+     * @param deviceInfo node identifier
+     * @return RestSBDevice rest device
+     */
+    RestSBDevice getDevice(DeviceId deviceInfo);
+
+    /**
+     * Returns a device by Ip and Port.
+     *
+     * @param ip   device ip
+     * @param port device port
+     * @return RestSBDevice rest device
+     */
+    RestSBDevice getDevice(IpAddress ip, int port);
+
+    /**
+     * Adds a device to the device map.
+     *
+     * @param device to be added
+     */
+    void addDevice(RestSBDevice device);
+
+    /**
+     * Removes the device from the devices map.
+     *
+     * @param deviceId to be removed
+     */
+    void removeDevice(DeviceId deviceId);
+
+    /**
+     * Does a HTTP POST request with specified parameters to the device.
+     *
+     * @param device    device to make the request to
+     * @param request   url of the request
+     * @param payload   payload of the request as an InputStream
+     * @param mediaType type of content in the payload i.e. application/json
+     * @return true if operation returned 200, 201, 202, false otherwise
+     */
+    boolean post(DeviceId device, String request, InputStream payload, String mediaType);
+
+    /**
+     * Does a HTTP POST request with specified parameters to the device.
+     *
+     * @param <T>           post return type
+     * @param device        device to make the request to
+     * @param request       url of the request
+     * @param payload       payload of the request as an InputStream
+     * @param mediaType     type of content in the payload i.e. application/json
+     * @param responseClass the type of response object we are interested in,
+     *                      such as String, InputStream.
+     * @return Object of type requested via responseClass.
+     */
+    <T> T post(DeviceId device, String request, InputStream payload,
+               String mediaType, Class<T> responseClass);
+
+    /**
+     * Does a HTTP PUT request with specified parameters to the device.
+     *
+     * @param device    device to make the request to
+     * @param request   resource path of the request
+     * @param payload   payload of the request as an InputStream
+     * @param mediaType type of content in the payload i.e. application/json
+     * @return true if operation returned 200, 201, 202, false otherwise
+     */
+    boolean put(DeviceId device, String request, InputStream payload, String mediaType);
+
+    /**
+     * Does a HTTP GET request with specified parameters to the device.
+     *
+     * @param device    device to make the request to
+     * @param request   url of the request
+     * @param mediaType format to retrieve the content in
+     * @return an inputstream of data from the reply.
+     */
+    InputStream get(DeviceId device, String request, String mediaType);
+
+    /**
+     * Does a HTTP PATCH request with specified parameters to the device.
+     *
+     * @param device    device to make the request to
+     * @param request   url of the request
+     * @param payload   payload of the request as an InputStream
+     * @param mediaType format to retrieve the content in
+     * @return true if operation returned 200, 201, 202, false otherwise
+     */
+    boolean patch(DeviceId device, String request, InputStream payload, String mediaType);
+
+    /**
+     * Does a HTTP DELETE request with specified parameters to the device.
+     *
+     * @param device    device to make the request to
+     * @param request   url of the request
+     * @param payload   payload of the request as an InputStream
+     * @param mediaType type of content in the payload i.e. application/json
+     * @return true if operation returned 200 false otherwise
+     */
+    boolean delete(DeviceId device, String request, InputStream payload, String mediaType);
+
+}
diff --git a/protocols/rest/api/src/main/java/org/onosproject/protocol/http/ctl/HttpSBControllerImpl.java b/protocols/rest/api/src/main/java/org/onosproject/protocol/http/ctl/HttpSBControllerImpl.java
new file mode 100644
index 0000000..2c2d1a2
--- /dev/null
+++ b/protocols/rest/api/src/main/java/org/onosproject/protocol/http/ctl/HttpSBControllerImpl.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * 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 org.onosproject.protocol.http.ctl;
+
+import com.google.common.collect.ImmutableMap;
+import org.apache.commons.io.IOUtils;
+import org.apache.http.client.methods.HttpPatch;
+import org.apache.http.conn.ssl.AllowAllHostnameVerifier;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.ssl.SSLContextBuilder;
+import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature;
+import org.onlab.packet.IpAddress;
+import org.onosproject.net.DeviceId;
+import org.onosproject.protocol.http.HttpSBController;
+import org.onosproject.protocol.rest.RestSBDevice;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyManagementException;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Base64;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * The implementation of HttpSBController.
+ */
+public class HttpSBControllerImpl implements HttpSBController {
+
+    private static final Logger log =
+            LoggerFactory.getLogger(HttpSBControllerImpl.class);
+    private static final String XML = "xml";
+    private static final String JSON = "json";
+    private static final String DOUBLESLASH = "//";
+    private static final String COLON = ":";
+    private static final int STATUS_OK = Response.Status.OK.getStatusCode();
+    private static final int STATUS_CREATED = Response.Status.CREATED.getStatusCode();
+    private static final int STATUS_ACCEPTED = Response.Status.ACCEPTED.getStatusCode();
+    private static final String HTTPS = "https";
+    private static final String AUTHORIZATION_PROPERTY = "authorization";
+    private static final String BASIC_AUTH_PREFIX = "Basic ";
+
+    private final Map<DeviceId, RestSBDevice> deviceMap = new ConcurrentHashMap<>();
+    private final Map<DeviceId, Client> clientMap = new ConcurrentHashMap<>();
+
+    public Map<DeviceId, RestSBDevice> getDeviceMap() {
+        return deviceMap;
+    }
+
+    public Map<DeviceId, Client> getClientMap() {
+        return clientMap;
+    }
+
+    @Override
+    public Map<DeviceId, RestSBDevice> getDevices() {
+        return ImmutableMap.copyOf(deviceMap);
+    }
+
+    @Override
+    public RestSBDevice getDevice(DeviceId deviceInfo) {
+        return deviceMap.get(deviceInfo);
+    }
+
+    @Override
+    public RestSBDevice getDevice(IpAddress ip, int port) {
+        return deviceMap.values().stream().filter(v -> v.ip().equals(ip)
+                && v.port() == port).findFirst().get();
+    }
+
+    @Override
+    public void addDevice(RestSBDevice device) {
+        if (!deviceMap.containsKey(device.deviceId())) {
+            Client client = ignoreSslClient();
+            if (device.username() != null) {
+                String username = device.username();
+                String password = device.password() == null ? "" : device.password();
+                authenticate(client, username, password);
+            }
+            clientMap.put(device.deviceId(), client);
+            deviceMap.put(device.deviceId(), device);
+        } else {
+            log.warn("Trying to add a device that is already existing {}", device.deviceId());
+        }
+
+    }
+
+    @Override
+    public void removeDevice(DeviceId deviceId) {
+        clientMap.remove(deviceId);
+        deviceMap.remove(deviceId);
+    }
+
+    @Override
+    public boolean post(DeviceId device, String request, InputStream payload, String mediaType) {
+        Response response = getResponse(device, request, payload, mediaType);
+        return checkReply(response);
+    }
+
+    @Override
+    public <T> T post(DeviceId device, String request, InputStream payload,
+                      String mediaType, Class<T> responseClass) {
+        Response response = getResponse(device, request, payload, mediaType);
+        if (response.hasEntity()) {
+            return response.readEntity(responseClass);
+        }
+        log.error("Response from device {} for request {} contains no entity", device, request);
+        return null;
+    }
+
+    private Response getResponse(DeviceId device, String request, InputStream payload, String mediaType) {
+        String type = typeOfMediaType(mediaType);
+
+        WebTarget wt = getWebTarget(device, request);
+
+        Response response = null;
+        if (payload != null) {
+            try {
+                response = wt.request(type)
+                        .post(Entity.entity(IOUtils.toString(payload, StandardCharsets.UTF_8), type));
+            } catch (IOException e) {
+                log.error("Cannot do POST {} request on device {} because can't read payload",
+                          request, device);
+            }
+        } else {
+            response = wt.request(type).post(Entity.entity(null, type));
+        }
+        return response;
+    }
+
+    @Override
+    public boolean put(DeviceId device, String request, InputStream payload, String mediaType) {
+        String type = typeOfMediaType(mediaType);
+
+        WebTarget wt = getWebTarget(device, request);
+
+        Response response = null;
+        if (payload != null) {
+            try {
+                response = wt.request(type)
+                        .put(Entity.entity(IOUtils.toString(payload, StandardCharsets.UTF_8), type));
+            } catch (IOException e) {
+                log.error("Cannot do PUT {} request on device {} because can't read payload",
+                          request, device);
+            }
+        } else {
+            response = wt.request(type).put(Entity.entity(null, type));
+        }
+        return checkReply(response);
+    }
+
+    @Override
+    public InputStream get(DeviceId device, String request, String mediaType) {
+        String type = typeOfMediaType(mediaType);
+
+        WebTarget wt = getWebTarget(device, request);
+
+        Response s = wt.request(type).get();
+
+        if (checkReply(s)) {
+            return new ByteArrayInputStream(s.readEntity((String.class))
+                    .getBytes(StandardCharsets.UTF_8));
+        }
+        return null;
+    }
+
+    @Override
+    public boolean patch(DeviceId device, String request, InputStream payload, String mediaType) {
+        String type = typeOfMediaType(mediaType);
+
+        try {
+            log.debug("Url request {} ", getUrlString(device, request));
+            HttpPatch httprequest = new HttpPatch(getUrlString(device, request));
+            if (deviceMap.get(device).username() != null) {
+                String pwd = deviceMap.get(device).password() == null ? "" : COLON + deviceMap.get(device).password();
+                String userPassword = deviceMap.get(device).username() + pwd;
+                String base64string = Base64.getEncoder().encodeToString(userPassword.getBytes(StandardCharsets.UTF_8));
+                httprequest.addHeader(AUTHORIZATION_PROPERTY, BASIC_AUTH_PREFIX + base64string);
+            }
+            if (payload != null) {
+                StringEntity input = new StringEntity(IOUtils.toString(payload, StandardCharsets.UTF_8));
+                input.setContentType(type);
+                httprequest.setEntity(input);
+            }
+            CloseableHttpClient httpClient;
+            if (deviceMap.containsKey(device) && deviceMap.get(device).protocol().equals(HTTPS)) {
+                httpClient = getApacheSslBypassClient();
+            } else {
+                httpClient = HttpClients.createDefault();
+            }
+            int responseStatusCode = httpClient
+                    .execute(httprequest)
+                    .getStatusLine()
+                    .getStatusCode();
+            return checkStatusCode(responseStatusCode);
+        } catch (IOException | NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) {
+            log.error("Cannot do PATCH {} request on device {}",
+                      request, device, e);
+        }
+        return false;
+    }
+
+    @Override
+    public boolean delete(DeviceId device, String request, InputStream payload, String mediaType) {
+        String type = typeOfMediaType(mediaType);
+
+        WebTarget wt = getWebTarget(device, request);
+
+        // FIXME: do we need to delete an entry by enclosing data in DELETE request?
+        // wouldn't it be nice to use PUT to implement the similar concept?
+        Response response = wt.request(type).delete();
+
+        return checkReply(response);
+    }
+
+    private String typeOfMediaType(String mediaType) {
+        String type;
+        switch (mediaType) {
+            case XML:
+                type = MediaType.APPLICATION_XML;
+                break;
+            case JSON:
+                type = MediaType.APPLICATION_JSON;
+                break;
+            default:
+                throw new IllegalArgumentException("Unsupported media type " + mediaType);
+
+        }
+        return type;
+    }
+
+    private void authenticate(Client client, String username, String password) {
+        client.register(HttpAuthenticationFeature.basic(username, password));
+    }
+
+    protected WebTarget getWebTarget(DeviceId device, String request) {
+        log.debug("Sending request to URL {} ", getUrlString(device, request));
+        return clientMap.get(device).target(getUrlString(device, request));
+    }
+
+    //FIXME security issue: this trusts every SSL certificate, even if is self-signed. Also deprecated methods.
+    private CloseableHttpClient getApacheSslBypassClient() throws NoSuchAlgorithmException,
+            KeyManagementException, KeyStoreException {
+        return HttpClients.custom().
+                setHostnameVerifier(new AllowAllHostnameVerifier()).
+                setSslcontext(new SSLContextBuilder()
+                                      .loadTrustMaterial(null, (arg0, arg1) -> true)
+                                      .build()).build();
+    }
+
+    private String getUrlString(DeviceId device, String request) {
+        if (deviceMap.get(device).url() != null) {
+            return deviceMap.get(device).protocol() + COLON + DOUBLESLASH
+                    + deviceMap.get(device).url() + request;
+        } else {
+            return deviceMap.get(device).protocol() + COLON +
+                    DOUBLESLASH +
+                    deviceMap.get(device).ip().toString() +
+                    COLON + deviceMap.get(device).port() + request;
+        }
+    }
+
+    private boolean checkReply(Response response) {
+        if (response != null) {
+            return checkStatusCode(response.getStatus());
+        }
+        log.error("Null reply from device");
+        return false;
+    }
+
+    private boolean checkStatusCode(int statusCode) {
+        if (statusCode == STATUS_OK ||
+                statusCode == STATUS_CREATED ||
+                statusCode == STATUS_ACCEPTED) {
+            return true;
+        } else {
+            log.error("Failed request, HTTP error code : "
+                              + statusCode);
+            return false;
+        }
+    }
+
+    private Client ignoreSslClient() {
+        SSLContext sslcontext = null;
+
+        try {
+            sslcontext = SSLContext.getInstance("TLS");
+            sslcontext.init(null, new TrustManager[]{new X509TrustManager() {
+                public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
+                }
+
+                public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
+                }
+
+                public X509Certificate[] getAcceptedIssuers() {
+                    return new X509Certificate[0];
+                }
+            } }, new java.security.SecureRandom());
+        } catch (NoSuchAlgorithmException | KeyManagementException e) {
+            e.printStackTrace();
+        }
+
+        return ClientBuilder.newBuilder().sslContext(sslcontext).hostnameVerifier((s1, s2) -> true).build();
+    }
+}
diff --git a/protocols/rest/api/src/main/java/org/onosproject/protocol/http/ctl/package-info.java b/protocols/rest/api/src/main/java/org/onosproject/protocol/http/ctl/package-info.java
new file mode 100644
index 0000000..e6fe54d
--- /dev/null
+++ b/protocols/rest/api/src/main/java/org/onosproject/protocol/http/ctl/package-info.java
@@ -0,0 +1,20 @@
+/**
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * 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.
+ */
+/**
+ * @author onos
+ *
+ */
+package org.onosproject.protocol.http.ctl;
diff --git a/protocols/rest/api/src/main/java/org/onosproject/protocol/http/package-info.java b/protocols/rest/api/src/main/java/org/onosproject/protocol/http/package-info.java
new file mode 100644
index 0000000..c54b035
--- /dev/null
+++ b/protocols/rest/api/src/main/java/org/onosproject/protocol/http/package-info.java
@@ -0,0 +1,20 @@
+/**
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * 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.
+ */
+/**
+ * @author onos
+ *
+ */
+package org.onosproject.protocol.http;
diff --git a/protocols/rest/api/src/main/java/org/onosproject/protocol/rest/DefaultRestSBDevice.java b/protocols/rest/api/src/main/java/org/onosproject/protocol/rest/DefaultRestSBDevice.java
index 6a8e5a9..40816a4 100644
--- a/protocols/rest/api/src/main/java/org/onosproject/protocol/rest/DefaultRestSBDevice.java
+++ b/protocols/rest/api/src/main/java/org/onosproject/protocol/rest/DefaultRestSBDevice.java
@@ -16,15 +16,16 @@
 
 package org.onosproject.protocol.rest;
 
-import com.google.common.base.MoreObjects;
-import com.google.common.base.Preconditions;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Objects;
+
 import org.apache.commons.lang3.StringUtils;
 import org.onlab.packet.IpAddress;
 import org.onosproject.net.DeviceId;
 
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.Objects;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
 
 /**
  * Default implementation for Rest devices.
diff --git a/protocols/rest/api/src/main/java/org/onosproject/protocol/rest/RestSBController.java b/protocols/rest/api/src/main/java/org/onosproject/protocol/rest/RestSBController.java
index b51b12f..2fe3791 100644
--- a/protocols/rest/api/src/main/java/org/onosproject/protocol/rest/RestSBController.java
+++ b/protocols/rest/api/src/main/java/org/onosproject/protocol/rest/RestSBController.java
@@ -16,123 +16,11 @@
 
 package org.onosproject.protocol.rest;
 
-import org.onlab.packet.IpAddress;
-import org.onosproject.net.DeviceId;
-
-import java.io.InputStream;
-import java.util.Map;
+import org.onosproject.protocol.http.HttpSBController;
 
 /**
  * Abstraction of an REST controller. Serves as a one stop shop for obtaining
  * Rest southbound devices and (un)register listeners.
  */
-public interface RestSBController {
-
-    /**
-     * Returns all the devices known to this controller.
-     *
-     * @return map of devices
-     */
-    Map<DeviceId, RestSBDevice> getDevices();
-
-    /**
-     * Returns a device by node identifier.
-     *
-     * @param deviceInfo node identifier
-     * @return RestSBDevice rest device
-     */
-    RestSBDevice getDevice(DeviceId deviceInfo);
-
-    /**
-     * Returns a device by Ip and Port.
-     *
-     * @param ip   device ip
-     * @param port device port
-     * @return RestSBDevice rest device
-     */
-    RestSBDevice getDevice(IpAddress ip, int port);
-
-    /**
-     * Adds a device to the device map.
-     *
-     * @param device to be added
-     */
-    void addDevice(RestSBDevice device);
-
-    /**
-     * Removes the device from the devices map.
-     *
-     * @param deviceId to be removed
-     */
-    void removeDevice(DeviceId deviceId);
-
-    /**
-     * Does a REST POST request with specified parameters to the device.
-     *
-     * @param device    device to make the request to
-     * @param request   url of the request
-     * @param payload   payload of the request as an InputStream
-     * @param mediaType type of content in the payload i.e. application/json
-     * @return true if operation returned 200, 201, 202, false otherwise
-     */
-    boolean post(DeviceId device, String request, InputStream payload, String mediaType);
-
-    /**
-     * Does a REST POST request with specified parameters to the device.
-     *
-     * @param <T>           post return type
-     * @param device        device to make the request to
-     * @param request       url of the request
-     * @param payload       payload of the request as an InputStream
-     * @param mediaType     type of content in the payload i.e. application/json
-     * @param responseClass the type of response object we are interested in,
-     *                      such as String, InputStream.
-     * @return Object of type requested via responseClass.
-     */
-    <T> T post(DeviceId device, String request, InputStream payload,
-               String mediaType, Class<T> responseClass);
-
-    /**
-     * Does a REST PUT request with specified parameters to the device.
-     *
-     * @param device    device to make the request to
-     * @param request   resource path of the request
-     * @param payload   payload of the request as an InputStream
-     * @param mediaType type of content in the payload i.e. application/json
-     * @return true if operation returned 200, 201, 202, false otherwise
-     */
-    boolean put(DeviceId device, String request, InputStream payload, String mediaType);
-
-    /**
-     * Does a REST GET request with specified parameters to the device.
-     *
-     * @param device    device to make the request to
-     * @param request   url of the request
-     * @param mediaType format to retrieve the content in
-     * @return an inputstream of data from the reply.
-     */
-    InputStream get(DeviceId device, String request, String mediaType);
-
-    /**
-     * Does a REST PATCH request with specified parameters to the device.
-     *
-     * @param device    device to make the request to
-     * @param request   url of the request
-     * @param payload   payload of the request as an InputStream
-     * @param mediaType format to retrieve the content in
-     * @return true if operation returned 200, 201, 202, false otherwise
-     */
-    boolean patch(DeviceId device, String request, InputStream payload, String mediaType);
-
-    /**
-     * Does a REST DELETE request with specified parameters to the device.
-     *
-     * @param device    device to make the request to
-     * @param request   url of the request
-     * @param payload   payload of the request as an InputStream
-     * @param mediaType type of content in the payload i.e. application/json
-     * @return true if operation returned 200 false otherwise
-     */
-    boolean delete(DeviceId device, String request, InputStream payload, String mediaType);
-
+public interface RestSBController extends HttpSBController {
 }
diff --git a/protocols/rest/ctl/pom.xml b/protocols/rest/ctl/pom.xml
index a420fb7..e85ffcd 100644
--- a/protocols/rest/ctl/pom.xml
+++ b/protocols/rest/ctl/pom.xml
@@ -34,32 +34,10 @@
             <artifactId>org.apache.felix.scr.annotations</artifactId>
         </dependency>
         <dependency>
-            <groupId>org.osgi</groupId>
-            <artifactId>org.osgi.compendium</artifactId>
-        </dependency>
-        <dependency>
             <groupId>org.onosproject</groupId>
             <artifactId>onos-restsb-api</artifactId>
             <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.glassfish.jersey.core</groupId>
-            <artifactId>jersey-client</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.httpcomponents</groupId>
-            <artifactId>httpclient-osgi</artifactId>
-            <version>4.5.1</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.httpcomponents</groupId>
-            <artifactId>httpcore-osgi</artifactId>
-            <version>4.4.4</version>
-        </dependency>
-        <dependency>
-            <groupId>commons-io</groupId>
-            <artifactId>commons-io</artifactId>
-            <version>2.4</version>
+            <type>bundle</type>
         </dependency>
     </dependencies>
 
diff --git a/protocols/rest/ctl/src/main/java/org/onosproject/protocol/rest/ctl/RestSBControllerImpl.java b/protocols/rest/ctl/src/main/java/org/onosproject/protocol/rest/ctl/RestSBControllerImpl.java
index 5b0eca2..37dfc85 100644
--- a/protocols/rest/ctl/src/main/java/org/onosproject/protocol/rest/ctl/RestSBControllerImpl.java
+++ b/protocols/rest/ctl/src/main/java/org/onosproject/protocol/rest/ctl/RestSBControllerImpl.java
@@ -16,70 +16,24 @@
 
 package org.onosproject.protocol.rest.ctl;
 
-import com.google.common.collect.ImmutableMap;
-import org.apache.commons.io.IOUtils;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
 import org.apache.felix.scr.annotations.Service;
-import org.apache.http.client.methods.HttpPatch;
-import org.apache.http.conn.ssl.AllowAllHostnameVerifier;
-import org.apache.http.entity.StringEntity;
-import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.impl.client.HttpClients;
-import org.apache.http.ssl.SSLContextBuilder;
-import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature;
-import org.onlab.packet.IpAddress;
-import org.onosproject.net.DeviceId;
+import org.onosproject.protocol.http.ctl.HttpSBControllerImpl;
 import org.onosproject.protocol.rest.RestSBController;
-import org.onosproject.protocol.rest.RestSBDevice;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.X509TrustManager;
-import javax.ws.rs.client.Client;
-import javax.ws.rs.client.ClientBuilder;
-import javax.ws.rs.client.Entity;
-import javax.ws.rs.client.WebTarget;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.charset.StandardCharsets;
-import java.security.KeyManagementException;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.cert.CertificateException;
-import java.security.cert.X509Certificate;
-import java.util.Base64;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
 /**
  * The implementation of RestSBController.
  */
 @Component(immediate = true)
 @Service
-public class RestSBControllerImpl implements RestSBController {
+public class RestSBControllerImpl extends HttpSBControllerImpl implements RestSBController {
 
     private static final Logger log =
             LoggerFactory.getLogger(RestSBControllerImpl.class);
-    private static final String XML = "xml";
-    private static final String JSON = "json";
-    private static final String DOUBLESLASH = "//";
-    private static final String COLON = ":";
-    private static final int STATUS_OK = Response.Status.OK.getStatusCode();
-    private static final int STATUS_CREATED = Response.Status.CREATED.getStatusCode();
-    private static final int STATUS_ACCEPTED = Response.Status.ACCEPTED.getStatusCode();
-    private static final String HTTPS = "https";
-    private static final String AUTHORIZATION_PROPERTY = "authorization";
-    private static final String BASIC_AUTH_PREFIX = "Basic ";
-
-    private final Map<DeviceId, RestSBDevice> deviceMap = new ConcurrentHashMap<>();
-    private final Map<DeviceId, Client> clientMap = new ConcurrentHashMap<>();
 
     @Activate
     public void activate() {
@@ -88,259 +42,9 @@
 
     @Deactivate
     public void deactivate() {
-        clientMap.clear();
-        deviceMap.clear();
+        this.getClientMap().clear();
+        this.getDeviceMap().clear();
         log.info("Stopped");
     }
 
-    @Override
-    public Map<DeviceId, RestSBDevice> getDevices() {
-        return ImmutableMap.copyOf(deviceMap);
-    }
-
-    @Override
-    public RestSBDevice getDevice(DeviceId deviceInfo) {
-        return deviceMap.get(deviceInfo);
-    }
-
-    @Override
-    public RestSBDevice getDevice(IpAddress ip, int port) {
-        return deviceMap.values().stream().filter(v -> v.ip().equals(ip)
-                && v.port() == port).findFirst().get();
-    }
-
-    @Override
-    public void addDevice(RestSBDevice device) {
-        if (!deviceMap.containsKey(device.deviceId())) {
-            Client client = ignoreSslClient();
-            if (device.username() != null) {
-                String username = device.username();
-                String password = device.password() == null ? "" : device.password();
-                authenticate(client, username, password);
-            }
-            clientMap.put(device.deviceId(), client);
-            deviceMap.put(device.deviceId(), device);
-        } else {
-            log.warn("Trying to add a device that is already existing {}", device.deviceId());
-        }
-
-    }
-
-    @Override
-    public void removeDevice(DeviceId deviceId) {
-        clientMap.remove(deviceId);
-        deviceMap.remove(deviceId);
-    }
-
-    @Override
-    public boolean post(DeviceId device, String request, InputStream payload, String mediaType) {
-        Response response = getResponse(device, request, payload, mediaType);
-        return checkReply(response);
-    }
-
-    @Override
-    public <T> T post(DeviceId device, String request, InputStream payload,
-                      String mediaType, Class<T> responseClass) {
-        Response response = getResponse(device, request, payload, mediaType);
-        if (response.hasEntity()) {
-            return response.readEntity(responseClass);
-        }
-        log.error("Response from device {} for request {} contains no entity", device, request);
-        return null;
-    }
-
-    private Response getResponse(DeviceId device, String request, InputStream payload, String mediaType) {
-        String type = typeOfMediaType(mediaType);
-
-        WebTarget wt = getWebTarget(device, request);
-
-        Response response = null;
-        if (payload != null) {
-            try {
-                response = wt.request(type)
-                        .post(Entity.entity(IOUtils.toString(payload, StandardCharsets.UTF_8), type));
-            } catch (IOException e) {
-                log.error("Cannot do POST {} request on device {} because can't read payload",
-                          request, device);
-            }
-        } else {
-            response = wt.request(type).post(Entity.entity(null, type));
-        }
-        return response;
-    }
-
-    @Override
-    public boolean put(DeviceId device, String request, InputStream payload, String mediaType) {
-        String type = typeOfMediaType(mediaType);
-
-        WebTarget wt = getWebTarget(device, request);
-
-        Response response = null;
-        if (payload != null) {
-            try {
-                response = wt.request(type)
-                        .put(Entity.entity(IOUtils.toString(payload, StandardCharsets.UTF_8), type));
-            } catch (IOException e) {
-                log.error("Cannot do PUT {} request on device {} because can't read payload",
-                          request, device);
-            }
-        } else {
-            response = wt.request(type).put(Entity.entity(null, type));
-        }
-        return checkReply(response);
-    }
-
-    @Override
-    public InputStream get(DeviceId device, String request, String mediaType) {
-        String type = typeOfMediaType(mediaType);
-
-        WebTarget wt = getWebTarget(device, request);
-
-        Response s = wt.request(type).get();
-
-        if (checkReply(s)) {
-            return new ByteArrayInputStream(s.readEntity((String.class))
-                    .getBytes(StandardCharsets.UTF_8));
-        }
-        return null;
-    }
-
-    @Override
-    public boolean patch(DeviceId device, String request, InputStream payload, String mediaType) {
-        String type = typeOfMediaType(mediaType);
-
-        try {
-            log.debug("Url request {} ", getUrlString(device, request));
-            HttpPatch httprequest = new HttpPatch(getUrlString(device, request));
-            if (deviceMap.get(device).username() != null) {
-                String pwd = deviceMap.get(device).password() == null ? "" : COLON + deviceMap.get(device).password();
-                String userPassword = deviceMap.get(device).username() + pwd;
-                String base64string = Base64.getEncoder().encodeToString(userPassword.getBytes(StandardCharsets.UTF_8));
-                httprequest.addHeader(AUTHORIZATION_PROPERTY, BASIC_AUTH_PREFIX + base64string);
-            }
-            if (payload != null) {
-                StringEntity input = new StringEntity(IOUtils.toString(payload, StandardCharsets.UTF_8));
-                input.setContentType(type);
-                httprequest.setEntity(input);
-            }
-            CloseableHttpClient httpClient;
-            if (deviceMap.containsKey(device) && deviceMap.get(device).protocol().equals(HTTPS)) {
-                httpClient = getApacheSslBypassClient();
-            } else {
-                httpClient = HttpClients.createDefault();
-            }
-            int responseStatusCode = httpClient
-                    .execute(httprequest)
-                    .getStatusLine()
-                    .getStatusCode();
-            return checkStatusCode(responseStatusCode);
-        } catch (IOException | NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) {
-            log.error("Cannot do PATCH {} request on device {}",
-                      request, device, e);
-        }
-        return false;
-    }
-
-    @Override
-    public boolean delete(DeviceId device, String request, InputStream payload, String mediaType) {
-        String type = typeOfMediaType(mediaType);
-
-        WebTarget wt = getWebTarget(device, request);
-
-        // FIXME: do we need to delete an entry by enclosing data in DELETE request?
-        // wouldn't it be nice to use PUT to implement the similar concept?
-        Response response = wt.request(type).delete();
-
-        return checkReply(response);
-    }
-
-    private String typeOfMediaType(String mediaType) {
-        String type;
-        switch (mediaType) {
-            case XML:
-                type = MediaType.APPLICATION_XML;
-                break;
-            case JSON:
-                type = MediaType.APPLICATION_JSON;
-                break;
-            default:
-                throw new IllegalArgumentException("Unsupported media type " + mediaType);
-
-        }
-        return type;
-    }
-
-    private void authenticate(Client client, String username, String password) {
-        client.register(HttpAuthenticationFeature.basic(username, password));
-    }
-
-    protected WebTarget getWebTarget(DeviceId device, String request) {
-        log.debug("Sending request to URL {} ", getUrlString(device, request));
-        return clientMap.get(device).target(getUrlString(device, request));
-    }
-
-    //FIXME security issue: this trusts every SSL certificate, even if is self-signed. Also deprecated methods.
-    private CloseableHttpClient getApacheSslBypassClient() throws NoSuchAlgorithmException,
-            KeyManagementException, KeyStoreException {
-        return HttpClients.custom().
-                setHostnameVerifier(new AllowAllHostnameVerifier()).
-                setSslcontext(new SSLContextBuilder()
-                                      .loadTrustMaterial(null, (arg0, arg1) -> true)
-                                      .build()).build();
-    }
-
-    private String getUrlString(DeviceId device, String request) {
-        if (deviceMap.get(device).url() != null) {
-            return deviceMap.get(device).protocol() + COLON + DOUBLESLASH
-                    + deviceMap.get(device).url() + request;
-        } else {
-            return deviceMap.get(device).protocol() + COLON +
-                    DOUBLESLASH +
-                    deviceMap.get(device).ip().toString() +
-                    COLON + deviceMap.get(device).port() + request;
-        }
-    }
-
-    private boolean checkReply(Response response) {
-        if (response != null) {
-            return checkStatusCode(response.getStatus());
-        }
-        log.error("Null reply from device");
-        return false;
-    }
-
-    private boolean checkStatusCode(int statusCode) {
-        if (statusCode == STATUS_OK ||
-                statusCode == STATUS_CREATED ||
-                statusCode == STATUS_ACCEPTED) {
-            return true;
-        } else {
-            log.error("Failed request, HTTP error code : "
-                              + statusCode);
-            return false;
-        }
-    }
-
-    private Client ignoreSslClient() {
-        SSLContext sslcontext = null;
-
-        try {
-            sslcontext = SSLContext.getInstance("TLS");
-            sslcontext.init(null, new TrustManager[]{new X509TrustManager() {
-                public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
-                }
-
-                public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
-                }
-
-                public X509Certificate[] getAcceptedIssuers() {
-                    return new X509Certificate[0];
-                }
-            } }, new java.security.SecureRandom());
-        } catch (NoSuchAlgorithmException | KeyManagementException e) {
-            e.printStackTrace();
-        }
-
-        return ClientBuilder.newBuilder().sslContext(sslcontext).hostnameVerifier((s1, s2) -> true).build();
-    }
 }
diff --git a/protocols/rest/pom.xml b/protocols/rest/pom.xml
index ca42451..e62f956 100644
--- a/protocols/rest/pom.xml
+++ b/protocols/rest/pom.xml
@@ -39,4 +39,4 @@
     </dependencies>
 
 
-</project>
\ No newline at end of file
+</project>
diff --git a/protocols/restconf/client/api/BUCK b/protocols/restconf/client/api/BUCK
new file mode 100644
index 0000000..c1f3e2c
--- /dev/null
+++ b/protocols/restconf/client/api/BUCK
@@ -0,0 +1,10 @@
+COMPILE_DEPS = [
+    '//lib:CORE_DEPS',
+    '//incubator/api:onos-incubator-api',
+    '//utils/rest:onlab-rest',
+    '//protocols/rest/api:onos-protocols-rest-api',
+]
+
+osgi_jar_with_tests (
+    deps = COMPILE_DEPS,
+)
diff --git a/protocols/restconf/client/api/pom.xml b/protocols/restconf/client/api/pom.xml
new file mode 100644
index 0000000..5925337
--- /dev/null
+++ b/protocols/restconf/client/api/pom.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2016-present Open Networking Laboratory
+  ~
+  ~ 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.
+  -->
+
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.onosproject</groupId>
+    <artifactId>onos-restconf-client</artifactId>
+    <version>1.8.0-SNAPSHOT</version>
+    <relativePath>../pom.xml</relativePath>
+  </parent>
+  <artifactId>onos-restconf-client-api</artifactId>
+  <packaging>bundle</packaging>
+  <description>ONOS RESTCONF southbound plugin API</description>
+  <dependencies>
+      <dependency>
+          <groupId>org.onosproject</groupId>
+          <artifactId>onos-restsb-api</artifactId>
+          <version>${project.version}</version>
+          <type>bundle</type>
+      </dependency>
+  </dependencies>
+</project>
diff --git a/protocols/restconf/client/api/src/main/java/org/onosproject/protocol/restconf/RestConfNotificationEventListener.java b/protocols/restconf/client/api/src/main/java/org/onosproject/protocol/restconf/RestConfNotificationEventListener.java
new file mode 100644
index 0000000..dc49a72
--- /dev/null
+++ b/protocols/restconf/client/api/src/main/java/org/onosproject/protocol/restconf/RestConfNotificationEventListener.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * 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 org.onosproject.protocol.restconf;
+
+import org.onosproject.net.DeviceId;
+
+/**
+ * Notifies providers about incoming RESTCONF notification events.
+ */
+public interface RestConfNotificationEventListener {
+
+    /**
+     * Handles the notification event.
+     *
+     * @param <T>
+     *
+     * @param deviceId of the restconf device
+     * @param eventJsonString the json string representation of the event
+     */
+    <T> void handleNotificationEvent(DeviceId deviceId, T eventJsonString);
+
+}
diff --git a/protocols/restconf/client/api/src/main/java/org/onosproject/protocol/restconf/RestConfSBController.java b/protocols/restconf/client/api/src/main/java/org/onosproject/protocol/restconf/RestConfSBController.java
new file mode 100644
index 0000000..772aaea
--- /dev/null
+++ b/protocols/restconf/client/api/src/main/java/org/onosproject/protocol/restconf/RestConfSBController.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * 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 org.onosproject.protocol.restconf;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.protocol.http.HttpSBController;
+
+/**
+ * Abstraction of a RESTCONF controller. Serves as a one stop shop for obtaining
+ * RESTCONF southbound devices and (un)register listeners.
+ */
+public interface RestConfSBController extends HttpSBController {
+
+    /**
+     * This method is to be called by whoever is interested to receive
+     * Notifications from a specific device. It does a REST GET request
+     * with specified parameters to the device, and calls the provided
+     * callBackListener upon receiving notifications to notify the requester
+     * about notifications.
+     *
+     *
+     * @param device device to make the request to
+     * @param request url of the request
+     * @param mediaType format to retrieve the content in
+     * @param callBackListener method to call when notifications arrives
+     */
+    void enableNotifications(DeviceId device, String request, String mediaType,
+                          RestConfNotificationEventListener callBackListener);
+
+    /**
+     * Register a listener for notification events that occur to restconf
+     * devices.
+     *
+     * @param deviceId the deviceId
+     * @param listener the listener to notify
+     */
+    void addNotificationListener(DeviceId deviceId,
+                                 RestConfNotificationEventListener listener);
+
+    /**
+     * Unregister the listener for the device.
+     *
+     * @param deviceId the deviceId
+     */
+    void removeNotificationListener(DeviceId deviceId);
+}
diff --git a/protocols/restconf/client/api/src/main/java/org/onosproject/protocol/restconf/package-info.java b/protocols/restconf/client/api/src/main/java/org/onosproject/protocol/restconf/package-info.java
new file mode 100644
index 0000000..15f2308
--- /dev/null
+++ b/protocols/restconf/client/api/src/main/java/org/onosproject/protocol/restconf/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/**
+ * RESTCONF southbound protocols libraries.
+ */
+package org.onosproject.protocol.restconf;
diff --git a/protocols/restconf/client/ctl/BUCK b/protocols/restconf/client/ctl/BUCK
new file mode 100644
index 0000000..ce0292e
--- /dev/null
+++ b/protocols/restconf/client/ctl/BUCK
@@ -0,0 +1,19 @@
+COMPILE_DEPS = [
+    '//lib:CORE_DEPS',
+    '//lib:jersey-client',
+    '//lib:jersey-common',
+    '//lib:httpclient-osgi',
+    '//lib:httpcore-osgi',
+    '//lib:javax.ws.rs-api',
+    '//lib:hk2-api',
+    '//lib:jersey-guava',
+    '//lib:aopalliance-repackaged',
+    '//lib:javax.inject',
+    '//protocols/restconf/client/api:onos-protocols-restconf-client-api',
+    '//protocols/rest/api:onos-protocols-rest-api',
+]
+
+osgi_jar_with_tests (
+    deps = COMPILE_DEPS,
+)
+
diff --git a/protocols/restconf/client/ctl/pom.xml b/protocols/restconf/client/ctl/pom.xml
new file mode 100644
index 0000000..6878151
--- /dev/null
+++ b/protocols/restconf/client/ctl/pom.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2016-present Open Networking Laboratory
+  ~
+  ~ 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.
+  -->
+
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.onosproject</groupId>
+    <artifactId>onos-restconf-client</artifactId>
+    <version>1.8.0-SNAPSHOT</version>
+    <relativePath>../pom.xml</relativePath>
+  </parent>
+  <artifactId>onos-restconf-client-ctl</artifactId>
+
+  <packaging>bundle</packaging>
+
+  <dependencies>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.scr.annotations</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-restconf-client-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.jersey.core</groupId>
+            <artifactId>jersey-client</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient-osgi</artifactId>
+            <version>4.5.1</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpcore-osgi</artifactId>
+            <version>4.4.4</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>2.4</version>
+        </dependency>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-restsb-api</artifactId>
+            <version>${project.version}</version>
+            <type>bundle</type>
+        </dependency>
+  </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-scr-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/protocols/restconf/client/ctl/src/main/java/org/onosproject/protocol/restconf/ctl/RestConfSBControllerImpl.java b/protocols/restconf/client/ctl/src/main/java/org/onosproject/protocol/restconf/ctl/RestConfSBControllerImpl.java
new file mode 100644
index 0000000..e5fb377
--- /dev/null
+++ b/protocols/restconf/client/ctl/src/main/java/org/onosproject/protocol/restconf/ctl/RestConfSBControllerImpl.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * 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 org.onosproject.protocol.restconf.ctl;
+
+import java.io.InputStream;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.Response;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Service;
+import org.glassfish.jersey.client.ChunkedInput;
+import org.onlab.packet.IpAddress;
+import org.onosproject.net.DeviceId;
+import org.onosproject.protocol.http.ctl.HttpSBControllerImpl;
+import org.onosproject.protocol.rest.RestSBDevice;
+import org.onosproject.protocol.restconf.RestConfNotificationEventListener;
+import org.onosproject.protocol.restconf.RestConfSBController;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The implementation of RestConfSBController.
+ */
+@Component(immediate = true)
+@Service
+public class RestConfSBControllerImpl extends HttpSBControllerImpl
+        implements RestConfSBController {
+
+    private static final Logger log = LoggerFactory
+            .getLogger(RestConfSBControllerImpl.class);
+
+    // TODO: for the Ibis release when both RESTCONF server and RESTCONF client
+    // fully support root resource discovery, ROOT_RESOURCE constant will be
+    // removed and rather the value would get discovered dynamically.
+    private static final String ROOT_RESOURCE = "/onos/restconf";
+
+    private static final String RESOURCE_PATH_PREFIX = "/data/";
+    private static final String NOTIFICATION_PATH_PREFIX = "/data/";
+
+    private Map<DeviceId, RestConfNotificationEventListener>
+                                            restconfNotificationListenerMap = new ConcurrentHashMap<>();
+    private Map<DeviceId, GetChunksRunnable> runnableTable = new ConcurrentHashMap<>();
+
+    ExecutorService executor = Executors.newCachedThreadPool();
+
+    @Activate
+    public void activate() {
+        log.info("RESTCONF SBI Started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        log.info("RESTCONF SBI Stopped");
+        executor.shutdown();
+        this.getClientMap().clear();
+        this.getDeviceMap().clear();
+    }
+
+    @Override
+    public Map<DeviceId, RestSBDevice> getDevices() {
+        log.trace("RESTCONF SBI::getDevices");
+        return super.getDevices();
+    }
+
+    @Override
+    public RestSBDevice getDevice(DeviceId deviceInfo) {
+        log.trace("RESTCONF SBI::getDevice with deviceId");
+        return super.getDevice(deviceInfo);
+    }
+
+    @Override
+    public RestSBDevice getDevice(IpAddress ip, int port) {
+        log.trace("RESTCONF SBI::getDevice with ip and port");
+        return super.getDevice(ip, port);
+    }
+
+    @Override
+    public void addDevice(RestSBDevice device) {
+        log.trace("RESTCONF SBI::addDevice");
+        super.addDevice(device);
+    }
+
+    @Override
+    public void removeDevice(DeviceId deviceId) {
+        log.trace("RESTCONF SBI::removeDevice");
+        super.removeDevice(deviceId);
+    }
+
+    @Override
+    public boolean post(DeviceId device, String request, InputStream payload,
+                        String mediaType) {
+        request = discoverRootResource(device) + RESOURCE_PATH_PREFIX
+                + request;
+        return super.post(device, request, payload, mediaType);
+    }
+
+    @Override
+    public <T> T post(DeviceId device, String request, InputStream payload,
+                      String mediaType, Class<T> responseClass) {
+        request = discoverRootResource(device) + RESOURCE_PATH_PREFIX
+                + request;
+        return super.post(device, request, payload, mediaType, responseClass);
+    }
+
+    @Override
+    public boolean put(DeviceId device, String request, InputStream payload,
+                       String mediaType) {
+        request = discoverRootResource(device) + RESOURCE_PATH_PREFIX
+                + request;
+        return super.put(device, request, payload, mediaType);
+    }
+
+    @Override
+    public InputStream get(DeviceId device, String request, String mediaType) {
+        request = discoverRootResource(device) + RESOURCE_PATH_PREFIX
+                + request;
+        return super.get(device, request, mediaType);
+    }
+
+    @Override
+    public boolean patch(DeviceId device, String request, InputStream payload,
+                         String mediaType) {
+        request = discoverRootResource(device) + RESOURCE_PATH_PREFIX
+                + request;
+        return super.patch(device, request, payload, mediaType);
+    }
+
+    @Override
+    public boolean delete(DeviceId device, String request, InputStream payload,
+                          String mediaType) {
+        request = discoverRootResource(device) + RESOURCE_PATH_PREFIX
+                + request;
+        return super.delete(device, request, payload, mediaType);
+    }
+
+    @Override
+    public void enableNotifications(DeviceId device, String request,
+                                 String mediaType,
+                                 RestConfNotificationEventListener listener) {
+
+        request = discoverRootResource(device) + NOTIFICATION_PATH_PREFIX
+                + request;
+
+        addNotificationListener(device, listener);
+
+        GetChunksRunnable runnable = new GetChunksRunnable(request, mediaType,
+                                                           device);
+        runnableTable.put(device, runnable);
+        executor.execute(runnable);
+    }
+
+    public void stopNotifications(DeviceId device) {
+
+        runnableTable.get(device).terminate();
+        runnableTable.remove(device);
+        removeNotificationListener(device);
+        log.debug("Stop sending notifications for device URI: " + device.uri().toString());
+
+    }
+
+    public class GetChunksRunnable implements Runnable {
+        private String request;
+        private String mediaType;
+        private DeviceId device;
+
+        private volatile boolean running = true;
+
+        public void terminate() {
+            running = false;
+        }
+
+        /**
+         * @param request
+         * @param mediaType
+         * @param device
+         */
+        public GetChunksRunnable(String request, String mediaType,
+                                 DeviceId device) {
+            this.request = request;
+            this.mediaType = mediaType;
+            this.device = device;
+        }
+
+        @Override
+        public void run() {
+            WebTarget wt = getWebTarget(device, request);
+            Response clientResp = wt.request(mediaType).get();
+            RestConfNotificationEventListener listener = restconfNotificationListenerMap
+                    .get(device);
+            final ChunkedInput<String> chunkedInput = (ChunkedInput<String>) clientResp
+                    .readEntity(new GenericType<ChunkedInput<String>>() {
+                    });
+
+            String chunk;
+            // Note that the read() is a blocking operation and the invoking
+            // thread is blocked until a new chunk comes. Jersey implementation
+            // of this IO operation is in a way that it does not respond to
+            // interrupts.
+            while (running) {
+                chunk = chunkedInput.read();
+                if (chunk != null) {
+                    if (running) {
+                        listener.handleNotificationEvent(device, chunk);
+                    } else {
+                        log.trace("the requesting client is no more interested "
+                                + "to receive such notifications.");
+                    }
+                } else {
+                    log.trace("The received notification chunk is null. do not continue any more.");
+                    break;
+                }
+            }
+            log.trace("out of while loop -- end of run");
+        }
+    }
+
+    public String discoverRootResource(DeviceId device) {
+        // FIXME: send a GET command to the device to discover the root resource.
+        // The plan to fix this is for the Ibis release when the RESTCONF server and
+        // the RESTCONF client both support root resource discovery.
+        return ROOT_RESOURCE;
+    }
+
+    @Override
+    public void addNotificationListener(DeviceId deviceId,
+                                        RestConfNotificationEventListener listener) {
+        if (!restconfNotificationListenerMap.containsKey(deviceId)) {
+            this.restconfNotificationListenerMap.put(deviceId, listener);
+        }
+    }
+
+    @Override
+    public void removeNotificationListener(DeviceId deviceId) {
+        this.restconfNotificationListenerMap.remove(deviceId);
+    }
+
+}
diff --git a/protocols/restconf/client/ctl/src/main/java/org/onosproject/protocol/restconf/ctl/package-info.java b/protocols/restconf/client/ctl/src/main/java/org/onosproject/protocol/restconf/ctl/package-info.java
new file mode 100644
index 0000000..3e0043b
--- /dev/null
+++ b/protocols/restconf/client/ctl/src/main/java/org/onosproject/protocol/restconf/ctl/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/**
+ * RESTCONF southbound protocol implementation.
+ */
+package org.onosproject.protocol.restconf.ctl;
diff --git a/protocols/restconf/client/ctl/src/test/java/org/onosproject/protocol/restconf/ctl/RestConfSBControllerImplTest.java b/protocols/restconf/client/ctl/src/test/java/org/onosproject/protocol/restconf/ctl/RestConfSBControllerImplTest.java
new file mode 100644
index 0000000..1f7f363
--- /dev/null
+++ b/protocols/restconf/client/ctl/src/test/java/org/onosproject/protocol/restconf/ctl/RestConfSBControllerImplTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * 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 org.onosproject.protocol.restconf.ctl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.IpAddress;
+import org.onosproject.protocol.rest.DefaultRestSBDevice;
+import org.onosproject.protocol.rest.RestSBDevice;
+
+/**
+ * Basic testing for RestSBController.
+ */
+public class RestConfSBControllerImplTest {
+
+    RestConfSBControllerImpl restConfController;
+
+    RestSBDevice device3;
+
+    ExecutorService executor = Executors.newSingleThreadExecutor();
+
+    @Before
+    public void setUp() {
+        restConfController = new RestConfSBControllerImpl();
+        restConfController.activate();
+        device3 = new DefaultRestSBDevice(IpAddress.valueOf("127.0.0.1"), 8181,
+                                          "", "", "http", null, true);
+        restConfController.addDevice(device3);
+
+    }
+
+    @Test
+    public void basics() {
+        assertTrue("Device3 non added",
+                   restConfController.getDevices().containsValue(device3));
+        assertEquals("Device3 added but with wrong key",
+                     restConfController.getDevices().get(device3.deviceId()),
+                     device3);
+        assertEquals("Incorrect Get Device by ID",
+                     restConfController.getDevice(device3.deviceId()), device3);
+        assertEquals("Incorrect Get Device by IP, Port",
+                     restConfController.getDevice(device3.ip(), device3.port()),
+                     device3);
+        restConfController.removeDevice(device3.deviceId());
+        assertFalse("Device3 not removed",
+                    restConfController.getDevices().containsValue(device3));
+    }
+}
diff --git a/protocols/restconf/client/pom.xml b/protocols/restconf/client/pom.xml
new file mode 100644
index 0000000..84dc398
--- /dev/null
+++ b/protocols/restconf/client/pom.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2016-present Open Networking Laboratory
+  ~
+  ~ 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.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.onosproject</groupId>
+    <artifactId>onos-restconf</artifactId>
+    <version>1.8.0-SNAPSHOT</version>
+    <relativePath>../pom.xml</relativePath>
+  </parent>
+  <artifactId>onos-restconf-client</artifactId>
+  <packaging>pom</packaging>
+  <dependencies>
+      <dependency>
+          <groupId>org.onosproject</groupId>
+          <artifactId>onos-api</artifactId>
+          <version>${project.version}</version>
+      </dependency>
+  </dependencies>
+  <modules>
+    <module>api</module>
+    <module>ctl</module>
+  </modules>
+  <description>RESTCONF Client Module</description>
+</project>
diff --git a/protocols/restconf/pom.xml b/protocols/restconf/pom.xml
index 6abb229..9bde8d0 100644
--- a/protocols/restconf/pom.xml
+++ b/protocols/restconf/pom.xml
@@ -28,6 +28,7 @@
 
   <modules>
     <module>server</module>
+    <module>client</module>
   </modules>
 
 </project>