diff --git a/Makefile b/Makefile index c481507ee34..5a0539968fe 100644 --- a/Makefile +++ b/Makefile @@ -84,10 +84,11 @@ PROTOS = $(shell git ls-files *.proto | grep -v vendor) PROJECT_FILES = $(shell git ls-files) IMAGES = peer orderer ccenv javaenv buildenv testenv zookeeper kafka couchdb tools RELEASE_PLATFORMS = windows-amd64 darwin-amd64 linux-amd64 linux-ppc64le linux-s390x -RELEASE_PKGS = configtxgen cryptogen +RELEASE_PKGS = configtxgen cryptogen configtxlator pkgmap.cryptogen := $(PKGNAME)/common/tools/cryptogen pkgmap.configtxgen := $(PKGNAME)/common/configtx/tool/configtxgen +pkgmap.configtxlator := $(PKGNAME)/common/tools/configtxlator pkgmap.peer := $(PKGNAME)/peer pkgmap.orderer := $(PKGNAME)/orderer pkgmap.block-listener := $(PKGNAME)/examples/events/block-listener @@ -129,6 +130,8 @@ orderer-docker: build/image/orderer/$(DUMMY) configtxgen: GO_TAGS+= nopkcs11 configtxgen: build/bin/configtxgen +configtxlator: build/bin/configtxlator + cryptogen: build/bin/cryptogen tools-docker: build/image/tools/$(DUMMY) @@ -152,7 +155,7 @@ test-cmd: @echo "go test -ldflags \"$(GO_LDFLAGS)\"" docker: $(patsubst %,build/image/%/$(DUMMY), $(IMAGES)) -native: peer orderer configtxgen cryptogen +native: peer orderer configtxgen cryptogen configtxlator behave-deps: docker peer build/bin/block-listener configtxgen cryptogen behave: behave-deps @@ -312,6 +315,11 @@ release/linux-s390x: DOCKER_ARCH=s390x release/linux-s390x: GO_TAGS+= nopkcs11 release/linux-s390x: $(patsubst %,release/linux-s390x/bin/%, $(RELEASE_PKGS)) release/linux-s390x/install +release/%/bin/configtxlator: $(PROJECT_FILES) + @echo "Building $@ for $(GOOS)-$(GOARCH)" + mkdir -p $(@D) + $(CGO_FLAGS) GOOS=$(GOOS) GOARCH=$(GOARCH) go build -o $(abspath $@) -tags "$(GO_TAGS)" -ldflags "$(GO_LDFLAGS)" $(pkgmap.$(@F)) + release/%/bin/configtxgen: $(PROJECT_FILES) @echo "Building $@ for $(GOOS)-$(GOARCH)" mkdir -p $(@D) diff --git a/common/tools/configtxlator/main.go b/common/tools/configtxlator/main.go new file mode 100644 index 00000000000..7f80c5b09be --- /dev/null +++ b/common/tools/configtxlator/main.go @@ -0,0 +1,41 @@ +/* +Copyright IBM Corp. 2017 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 main + +import ( + "flag" + "fmt" + "net/http" + + "github.com/hyperledger/fabric/common/tools/configtxlator/rest" + + "github.com/op/go-logging" +) + +var logger = logging.MustGetLogger("configtxlator") + +func main() { + var serverPort int + + flag.IntVar(&serverPort, "serverPort", 7059, "Specify the port for the REST server to listen on.") + flag.Parse() + + logger.Infof("Serving HTTP requests on port: %d", serverPort) + err := http.ListenAndServe(fmt.Sprintf(":%d", serverPort), rest.NewRouter()) + + logger.Fatal("Error runing http server:", err) +} diff --git a/common/tools/configtxlator/rest/protolator_handlers.go b/common/tools/configtxlator/rest/protolator_handlers.go new file mode 100644 index 00000000000..f84b0ab6465 --- /dev/null +++ b/common/tools/configtxlator/rest/protolator_handlers.go @@ -0,0 +1,109 @@ +/* +Copyright IBM Corp. 2017 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 rest + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "reflect" + + "github.com/hyperledger/fabric/common/tools/protolator" + + // Import these to register the proto types + _ "github.com/hyperledger/fabric/protos/common" + _ "github.com/hyperledger/fabric/protos/msp" + _ "github.com/hyperledger/fabric/protos/orderer" + _ "github.com/hyperledger/fabric/protos/peer" + + "github.com/golang/protobuf/proto" + "github.com/gorilla/mux" +) + +func getMsgType(r *http.Request) (proto.Message, error) { + vars := mux.Vars(r) + msgName := vars["msgName"] // Will not arrive is unset + + msgType := proto.MessageType(msgName) + if msgType == nil { + return nil, fmt.Errorf("message name not found") + } + return reflect.New(msgType.Elem()).Interface().(proto.Message), nil +} + +func Decode(w http.ResponseWriter, r *http.Request) { + msg, err := getMsgType(r) + if err != nil { + w.WriteHeader(http.StatusNotFound) + fmt.Fprintln(w, err) + return + } + + buf, err := ioutil.ReadAll(r.Body) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + fmt.Fprintln(w, err) + return + } + + err = proto.Unmarshal(buf, msg) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + fmt.Fprintln(w, err) + return + } + + var buffer bytes.Buffer + err = protolator.DeepMarshalJSON(&buffer, msg) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + fmt.Fprintln(w, err) + return + } + + w.WriteHeader(http.StatusOK) + w.Header().Set("Content-Type", "application/json") + buffer.WriteTo(w) +} + +func Encode(w http.ResponseWriter, r *http.Request) { + msg, err := getMsgType(r) + if err != nil { + w.WriteHeader(http.StatusNotFound) + fmt.Fprintln(w, err) + return + } + + err = protolator.DeepUnmarshalJSON(r.Body, msg) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + fmt.Fprintln(w, err) + return + } + + data, err := proto.Marshal(msg) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + fmt.Fprintln(w, err) + return + } + + w.WriteHeader(http.StatusOK) + w.Header().Set("Content-Type", "application/octet-stream") + w.Write(data) +} diff --git a/common/tools/configtxlator/rest/protolator_handlers_test.go b/common/tools/configtxlator/rest/protolator_handlers_test.go new file mode 100644 index 00000000000..04fa153e069 --- /dev/null +++ b/common/tools/configtxlator/rest/protolator_handlers_test.go @@ -0,0 +1,128 @@ +/* +Copyright IBM Corp. 2017 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 rest + +import ( + "bytes" + "fmt" + "net/http" + "net/http/httptest" + "strings" + "testing" + + cb "github.com/hyperledger/fabric/protos/common" + "github.com/hyperledger/fabric/protos/utils" + + "github.com/golang/protobuf/proto" + "github.com/stretchr/testify/assert" +) + +var ( + testProto = &cb.Block{ + Header: &cb.BlockHeader{ + PreviousHash: []byte("foo"), + }, + Data: &cb.BlockData{ + Data: [][]byte{ + utils.MarshalOrPanic(&cb.Envelope{ + Signature: []byte("bar"), + }), + }, + }, + } + + testOutput = `{"data":{"data":[{"signature":"YmFy"}]},"header":{"previous_hash":"Zm9v"}}` +) + +func TestProtolatorDecode(t *testing.T) { + data, err := proto.Marshal(testProto) + assert.NoError(t, err) + + url := fmt.Sprintf("/protolator/decode/%s", proto.MessageName(testProto)) + + req, _ := http.NewRequest("POST", url, bytes.NewReader(data)) + rec := httptest.NewRecorder() + r := NewRouter() + r.ServeHTTP(rec, req) + + assert.Equal(t, http.StatusOK, rec.Code) + + // Remove all the whitespace + compactJSON := strings.Replace(strings.Replace(strings.Replace(rec.Body.String(), "\n", "", -1), "\t", "", -1), " ", "", -1) + + assert.Equal(t, testOutput, compactJSON) +} + +func TestProtolatorEncode(t *testing.T) { + + url := fmt.Sprintf("/protolator/encode/%s", proto.MessageName(testProto)) + + req, _ := http.NewRequest("POST", url, bytes.NewReader([]byte(testOutput))) + rec := httptest.NewRecorder() + r := NewRouter() + r.ServeHTTP(rec, req) + + assert.Equal(t, http.StatusOK, rec.Code) + + outputMsg := &cb.Block{} + + err := proto.Unmarshal(rec.Body.Bytes(), outputMsg) + assert.NoError(t, err) + assert.Equal(t, testProto, outputMsg) +} + +func TestProtolatorDecodeNonExistantProto(t *testing.T) { + req, _ := http.NewRequest("POST", "/protolator/decode/NonExistantMsg", bytes.NewReader([]byte{})) + rec := httptest.NewRecorder() + r := NewRouter() + r.ServeHTTP(rec, req) + + assert.Equal(t, http.StatusNotFound, rec.Code) +} + +func TestProtolatorEncodeNonExistantProto(t *testing.T) { + req, _ := http.NewRequest("POST", "/protolator/encode/NonExistantMsg", bytes.NewReader([]byte{})) + rec := httptest.NewRecorder() + r := NewRouter() + r.ServeHTTP(rec, req) + + assert.Equal(t, http.StatusNotFound, rec.Code) +} + +func TestProtolatorDecodeBadData(t *testing.T) { + url := fmt.Sprintf("/protolator/decode/%s", proto.MessageName(testProto)) + + req, _ := http.NewRequest("POST", url, bytes.NewReader([]byte("Garbage"))) + + rec := httptest.NewRecorder() + r := NewRouter() + r.ServeHTTP(rec, req) + + assert.Equal(t, http.StatusBadRequest, rec.Code) +} + +func TestProtolatorEncodeBadData(t *testing.T) { + url := fmt.Sprintf("/protolator/encode/%s", proto.MessageName(testProto)) + + req, _ := http.NewRequest("POST", url, bytes.NewReader([]byte("Garbage"))) + + rec := httptest.NewRecorder() + r := NewRouter() + r.ServeHTTP(rec, req) + + assert.Equal(t, http.StatusBadRequest, rec.Code) +} diff --git a/common/tools/configtxlator/rest/router.go b/common/tools/configtxlator/rest/router.go new file mode 100644 index 00000000000..bba6284de6c --- /dev/null +++ b/common/tools/configtxlator/rest/router.go @@ -0,0 +1,37 @@ +/* +Copyright IBM Corp. 2017 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 rest + +import ( + "github.com/gorilla/mux" + "github.com/op/go-logging" +) + +var logger = logging.MustGetLogger("configtxlator/rest") + +func NewRouter() *mux.Router { + router := mux.NewRouter().StrictSlash(true) + router. + HandleFunc("/protolator/encode/{msgName}", Encode). + Methods("POST") + + router. + HandleFunc("/protolator/decode/{msgName}", Decode). + Methods("POST") + + return router +}