diff --git a/lib/protocol/grpc/body/readable.rb b/lib/protocol/grpc/body/readable.rb index 6cf297f..ff47712 100644 --- a/lib/protocol/grpc/body/readable.rb +++ b/lib/protocol/grpc/body/readable.rb @@ -117,26 +117,20 @@ def read_exactly(n) def decompress(data) case @encoding when "gzip" - # Gzip format: zlib stream with gzip header (RFC 1952) - # Use MAX_WBITS + 32 to handle gzip header and CRC - inflater = Zlib::Inflate.new(Zlib::MAX_WBITS + 32) - result = inflater.inflate(data) - inflater.close - result + begin + Zlib.gunzip(data) + rescue => error + raise Error.new(Status::INTERNAL, "Failed to decompress message: #{error.message}") + end when "deflate" - # Zlib format (RFC 1950) - default window bits handle zlib header - # This matches HTTP's "deflate" content-encoding - inflater = Zlib::Inflate.new - result = inflater.inflate(data) - inflater.close - result + begin + Zlib::Inflate.inflate(data) + rescue => error + raise Error.new(Status::INTERNAL, "Failed to decompress message: #{error.message}") + end else raise Error.new(Status::UNIMPLEMENTED, "Unsupported compression encoding: #{@encoding.inspect}") end - rescue Error - raise - rescue StandardError => error - raise Error.new(Status::INTERNAL, "Failed to decompress message: #{error.message}") end end end diff --git a/lib/protocol/grpc/body/writable.rb b/lib/protocol/grpc/body/writable.rb index 2a02d47..6352a6c 100644 --- a/lib/protocol/grpc/body/writable.rb +++ b/lib/protocol/grpc/body/writable.rb @@ -6,7 +6,6 @@ require "protocol/http" require "protocol/http/body/writable" require "zlib" -require "stringio" require_relative "../error" require_relative "../status" @@ -89,21 +88,20 @@ def write(message, compressed: nil) def compress(data) case @encoding when "gzip" - # Use GzipWriter for proper gzip format (includes headers, CRC) - io = StringIO.new - gz = Zlib::GzipWriter.new(io, @level) - gz.write(data) - gz.close - io.string + begin + Zlib.gzip(data, level: @level) + rescue => error + raise Error.new(Status::INTERNAL, "Failed to compress message: #{error.message}") + end when "deflate" - # Use zlib format (RFC 1950) for HTTP compatibility - # This matches HTTP's "deflate" content-encoding - Zlib::Deflate.deflate(data, @level) + begin + Zlib::Deflate.deflate(data, @level) + rescue => error + raise Error.new(Status::INTERNAL, "Failed to compress message: #{error.message}") + end else - raise ArgumentError, "Unsupported compression encoding: #{@encoding.inspect}" + raise Error.new(Status::INTERNAL, "Unsupported compression encoding: #{@encoding.inspect}") end - rescue StandardError => error - raise Error.new(Status::INTERNAL, "Failed to compress message: #{error.message}") end end end diff --git a/test/protocol/grpc/body/readable.rb b/test/protocol/grpc/body/readable.rb index 1685d77..9b6e3cf 100644 --- a/test/protocol/grpc/body/readable.rb +++ b/test/protocol/grpc/body/readable.rb @@ -227,12 +227,23 @@ def source_body.read end end - it "raises a gRPC error for invalid compressed data" do + it "raises a gRPC error for invalid gzip data" do body = subject.new(source_body, encoding: "gzip") write_data("invalid", compressed: true) expect{body.read}.to raise_exception(Protocol::GRPC::Error) do |error| expect(error.status_code).to be == Protocol::GRPC::Status::INTERNAL + expect(error.message).to be =~ /Failed to decompress message/ + end + end + + it "raises a gRPC error for invalid deflate data" do + body = subject.new(source_body, encoding: "deflate") + write_data("invalid", compressed: true) + + expect{body.read}.to raise_exception(Protocol::GRPC::Error) do |error| + expect(error.status_code).to be == Protocol::GRPC::Status::INTERNAL + expect(error.message).to be =~ /Failed to decompress message/ end end end diff --git a/test/protocol/grpc/body/writable.rb b/test/protocol/grpc/body/writable.rb index 7be9ad2..12e97eb 100644 --- a/test/protocol/grpc/body/writable.rb +++ b/test/protocol/grpc/body/writable.rb @@ -213,6 +213,17 @@ def message.encode expect(compressed).to be == 0 end + it "compresses messages using gzip" do + body = subject.new(encoding: "gzip") + message = message_class.new(value: "Hello") + body.write(message) + body.close_write + + framed_data = body.join + expect(framed_data.getbyte(0)).to be == 1 + expect(Zlib.gunzip(framed_data.byteslice(5..))).to be == message.to_proto + end + it "compresses messages using deflate" do body = subject.new(encoding: "deflate") message = message_class.new(value: "Hello") @@ -234,11 +245,23 @@ def message.encode end end - it "raises a gRPC error when compression fails" do - body = subject.new(encoding: "deflate", level: 100) + it "raises a gRPC error when gzip compression fails" do + body = subject.new(encoding: "gzip") + expect(Zlib).to receive(:gzip).and_raise(Zlib::StreamError, "compression failed") + + expect{body.write("Hello")}.to raise_exception(Protocol::GRPC::Error) do |error| + expect(error.status_code).to be == Protocol::GRPC::Status::INTERNAL + expect(error.message).to be == "Failed to compress message: compression failed" + end + end + + it "raises a gRPC error when deflate compression fails" do + body = subject.new(encoding: "deflate") + expect(Zlib::Deflate).to receive(:deflate).and_raise(Zlib::StreamError, "compression failed") expect{body.write("Hello")}.to raise_exception(Protocol::GRPC::Error) do |error| expect(error.status_code).to be == Protocol::GRPC::Status::INTERNAL + expect(error.message).to be == "Failed to compress message: compression failed" end end end